feat: vblank IRQ and double-buffered page flip for Intel GPU
Add tear-free scanout to the Intel GPU driver: a second kernel-allocated scanout buffer, DSPASURF flips latched at vblank, and a vblank interrupt delivered over MSI (Gen 11+ master/display/pipe IRQ chain) with a monotonic vblank counter and WaitVblank(). Expose it as SYS_FBFLIP (150): index selects the front buffer, -1 queries support, flags bit0 waits for the flip to latch. fb_map() now maps buffer 1 right after buffer 0 when flipping is available, and gui::Framebuffer draws to the off-screen buffer and flips with vsync, falling back to the direct copy when unsupported. Scanout is restored to buffer 0 when the flip-owning process exits and on panic, so the terminal and panic box never land on the invisible buffer. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -203,6 +203,9 @@ namespace montauk::abi {
|
||||
// CPU power/thermal status
|
||||
static constexpr uint64_t SYS_POWERINFO = 149; // (PowerInfo*) -> 0, -1 unsupported
|
||||
|
||||
// Framebuffer page flip (double-buffered scanout)
|
||||
static constexpr uint64_t SYS_FBFLIP = 150; // (index, flags) -> new front index; index=-1 queries support (1/0); flags bit0 = wait vsync
|
||||
|
||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
||||
|
||||
@@ -14,10 +14,12 @@ namespace gui {
|
||||
|
||||
class Framebuffer {
|
||||
uint32_t* hw_fb;
|
||||
uint32_t* hw_fb2; // second scanout buffer (page flip), nullptr if unsupported
|
||||
uint32_t* back_buf;
|
||||
int fb_width;
|
||||
int fb_height;
|
||||
int fb_pitch; // in bytes
|
||||
int hw_next; // scanout buffer the next flip() will present (page flip only)
|
||||
|
||||
static inline void fill_pixels(uint32_t* dst, int count, uint32_t pixel) {
|
||||
if (!dst || count <= 0) return;
|
||||
@@ -60,7 +62,8 @@ class Framebuffer {
|
||||
}
|
||||
|
||||
public:
|
||||
Framebuffer() : hw_fb(nullptr), back_buf(nullptr), fb_width(0), fb_height(0), fb_pitch(0) {
|
||||
Framebuffer() : hw_fb(nullptr), hw_fb2(nullptr), back_buf(nullptr),
|
||||
fb_width(0), fb_height(0), fb_pitch(0), hw_next(1) {
|
||||
montauk::abi::FbInfo info;
|
||||
montauk::fb_info(&info);
|
||||
|
||||
@@ -70,6 +73,13 @@ public:
|
||||
|
||||
hw_fb = (uint32_t*)montauk::fb_map();
|
||||
back_buf = (uint32_t*)montauk::alloc((uint64_t)fb_height * fb_pitch);
|
||||
|
||||
// Hardware page flipping: fb_map() maps the second scanout buffer
|
||||
// directly after the first when the kernel supports flipping.
|
||||
if (hw_fb && montauk::fb_flip(-1, 0) == 1) {
|
||||
uint64_t pages = ((uint64_t)fb_height * fb_pitch + 0xFFF) / 0x1000;
|
||||
hw_fb2 = (uint32_t*)((uint8_t*)hw_fb + pages * 0x1000);
|
||||
}
|
||||
}
|
||||
|
||||
int width() const { return fb_width; }
|
||||
@@ -250,13 +260,28 @@ public:
|
||||
inline void flip() {
|
||||
if (!hw_fb || !back_buf) return;
|
||||
|
||||
// With hardware page flipping, copy into the scanout buffer that is
|
||||
// currently OFF screen, then ask the display engine to present it at
|
||||
// the next vblank (tear-free). Without it, fall back to copying
|
||||
// straight into the visible framebuffer.
|
||||
uint32_t* dst_fb = hw_fb;
|
||||
if (hw_fb2) dst_fb = (hw_next == 1) ? hw_fb2 : hw_fb;
|
||||
|
||||
// Copy back buffer to hardware framebuffer, row by row (pitch may differ)
|
||||
uint64_t row_bytes = (uint64_t)fb_width * sizeof(uint32_t);
|
||||
for (int y = 0; y < fb_height; y++) {
|
||||
uint32_t* src = (uint32_t*)((uint8_t*)back_buf + y * fb_pitch);
|
||||
uint32_t* dst = (uint32_t*)((uint8_t*)hw_fb + y * fb_pitch);
|
||||
uint32_t* dst = (uint32_t*)((uint8_t*)dst_fb + y * fb_pitch);
|
||||
montauk::memcpy(dst, src, row_bytes);
|
||||
}
|
||||
|
||||
if (hw_fb2) {
|
||||
// Wait for the latch (vsync): the buffer we just left becomes
|
||||
// safe to draw into only once the new one is actually scanning.
|
||||
if (montauk::fb_flip(hw_next, 1) == hw_next) {
|
||||
hw_next ^= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -173,6 +173,7 @@ extern "C" {
|
||||
#define MTK_SYS_SDR_SETPARAM 147
|
||||
#define MTK_SYS_SDR_GETPARAM 148
|
||||
#define MTK_SYS_POWERINFO 149
|
||||
#define MTK_SYS_FBFLIP 150
|
||||
/* @SYSCALLS-END */
|
||||
|
||||
#define MTK_SOCK_TCP 1
|
||||
|
||||
@@ -327,6 +327,15 @@ namespace montauk {
|
||||
inline void fb_info(montauk::abi::FbInfo* info) { syscall1(montauk::abi::SYS_FBINFO, (uint64_t)info); }
|
||||
inline void* fb_map() { return (void*)syscall0(montauk::abi::SYS_FBMAP); }
|
||||
|
||||
// Page flip between the two scanout buffers. fb_map() maps buffer 1
|
||||
// directly after buffer 0 (at +page_align(height*pitch)). index selects
|
||||
// the buffer to show; the hardware latches it at vblank (tear-free).
|
||||
// flags bit0 = block until the flip has been latched (vsync).
|
||||
// fb_flip(-1, 0) returns 1 when page flipping is available, 0 when not.
|
||||
inline int64_t fb_flip(int64_t index, uint64_t flags) {
|
||||
return syscall2(montauk::abi::SYS_FBFLIP, (uint64_t)index, flags);
|
||||
}
|
||||
|
||||
// Arguments
|
||||
inline int getargs(char* buf, uint64_t maxLen) {
|
||||
return (int)syscall2(montauk::abi::SYS_GETARGS, (uint64_t)buf, maxLen);
|
||||
|
||||
Reference in New Issue
Block a user