diff --git a/gui/gui_desktop.c b/gui/gui_desktop.c index 928c4d9..0e4aeea 100644 --- a/gui/gui_desktop.c +++ b/gui/gui_desktop.c @@ -75,6 +75,24 @@ static bool g_show_grid = true; static int g_selected_icon = -1; static int g_hover_menu_item = -1; static uint64_t g_frames = 0; + +/* + * Cached wallpaper. + * + * The background is a full-screen vertical gradient with an optional blended + * grid and three strings on top. None of it changes between frames, but it was + * being recomputed pixel by pixel on every pass of the compositor loop. It is + * drawn once into this buffer instead and blitted back each frame; the cache + * is dropped whenever the resolution or the grid setting changes. + */ +/* Rolling count of pixels actually written to the framebuffer, so the cost of + * the compositor is visible on the console without a profiler attached. */ +static uint64_t g_pushed_pixels = 0; + +static uint32_t* g_wallpaper = NULL; +static int g_wallpaper_w = 0; +static int g_wallpaper_h = 0; +static bool g_wallpaper_grid = false; static uint8_t g_cpu_history[48]; // The launcher geometry below is authored against an 800x600 work area. @@ -128,6 +146,10 @@ void gui_desktop_init(void) { int h = (fb && fb->height > 0) ? fb->height : GUI_DEFAULT_HEIGHT; gui_gfx_init(fb_addr, w, h); + /* The console owned the framebuffer until now, so nothing on screen + * matches the compositor's idea of it; drop both caches. */ + gui_gfx_invalidate(); + gui_desktop_invalidate_background(); gui_icons_init(); gui_cursor_init(); gui_dialog_init(); @@ -149,10 +171,8 @@ void gui_desktop_init(void) { // Wallpaper and desktop icons // =========================================================================== -void gui_desktop_render_background(void) { - int sw = gui_gfx_get_width(); - int wh = gui_desktop_workarea_height(); - +/* Paint the wallpaper straight into the back buffer. */ +static void desktop_paint_background(int sw, int wh) { gui_gfx_draw_gradient_v(0, 0, sw, wh, 0xFF0B132B, 0xFF1C2541); if (g_show_grid) { @@ -175,6 +195,56 @@ void gui_desktop_render_background(void) { gui_gfx_draw_string(bx - 76, by + 36, "F5 cycle F6 cascade F7 tile F8 max", 0xFF475569); } +void gui_desktop_invalidate_background(void) { + if (g_wallpaper) { + kfree(g_wallpaper); + g_wallpaper = NULL; + } + g_wallpaper_w = 0; + g_wallpaper_h = 0; +} + +void gui_desktop_render_background(void) { + int sw = gui_gfx_get_width(); + int wh = gui_desktop_workarea_height(); + uint32_t* back = gui_gfx_get_backbuffer(); + + if (sw <= 0 || wh <= 0 || !back) return; + + bool stale = (!g_wallpaper || g_wallpaper_w != sw || g_wallpaper_h != wh || + g_wallpaper_grid != g_show_grid); + + if (stale) { + if (g_wallpaper) { + kfree(g_wallpaper); + g_wallpaper = NULL; + } + /* Paint it once through the normal primitives, then keep a copy. The + * back buffer is the scratch space, which is safe because the caller + * redraws everything above the wallpaper straight afterwards. */ + desktop_paint_background(sw, wh); + + g_wallpaper = (uint32_t*)kmalloc((size_t)sw * (size_t)wh * sizeof(uint32_t)); + if (g_wallpaper) { + for (int y = 0; y < wh; y++) { + memcpy(g_wallpaper + (size_t)y * sw, + back + (size_t)y * sw, + (size_t)sw * sizeof(uint32_t)); + } + g_wallpaper_w = sw; + g_wallpaper_h = wh; + g_wallpaper_grid = g_show_grid; + } + return; /* the back buffer already holds the freshly painted copy */ + } + + for (int y = 0; y < wh; y++) { + memcpy(back + (size_t)y * sw, + g_wallpaper + (size_t)y * sw, + (size_t)sw * sizeof(uint32_t)); + } +} + void gui_desktop_render_icons(void) { for (int i = 0; i < DESKTOP_ICON_COUNT; i++) { const desktop_app_t* a = &g_apps[g_desktop_icons[i]]; @@ -676,6 +746,18 @@ int gui_desktop_run(void) { gui_gfx_present(); g_frames++; + g_pushed_pixels += gui_gfx_last_present_pixels(); + + if ((g_frames & 0xFF) == 0) { + uint64_t full = (uint64_t)gui_gfx_get_width() * (uint64_t)gui_gfx_get_height(); + uint64_t avg = g_pushed_pixels / 256; + printk(KERN_INFO "GUI: %llu frames, %llu px/frame pushed of %llu (%llu%%)\n", + (unsigned long long)g_frames, + (unsigned long long)avg, + (unsigned long long)full, + (unsigned long long)(full ? (avg * 100) / full : 0)); + g_pushed_pixels = 0; + } } // Hand the screen to the framebuffer console. Without this the shell writes @@ -684,6 +766,11 @@ int gui_desktop_run(void) { // the host terminal, not the emulator window. fbcon_enable(true); + /* The console is about to draw over the framebuffer, so the shadow copy + * stops describing the screen; release the wallpaper cache with it. */ + gui_gfx_invalidate(); + gui_desktop_invalidate_background(); + printk(KERN_INFO "GUI: Desktop session ended after %llu frames, TTY restored\n", (unsigned long long)g_frames); return 0; diff --git a/gui/gui_gfx.c b/gui/gui_gfx.c index cdec212..ec6149c 100644 --- a/gui/gui_gfx.c +++ b/gui/gui_gfx.c @@ -6,18 +6,56 @@ static uint32_t* g_frontbuffer = NULL; static uint32_t* g_backbuffer = NULL; +/* + * Mirror of what the framebuffer currently holds. + * + * The front buffer lives in device memory, where a write costs far more than + * the same write to RAM and a read costs more still. Keeping a RAM copy of the + * last presented frame lets gui_gfx_present() work out which pixels actually + * changed without ever reading the framebuffer back, and push only those. + */ +static uint32_t* g_shadow = NULL; +static bool g_shadow_valid = false; static int g_width = 800; static int g_height = 600; +static uint64_t g_present_pixels = 0; void gui_gfx_init(uint32_t* fb, int width, int height) { - g_frontbuffer = fb; - g_width = (width > 0) ? width : 800; - g_height = (height > 0) ? height : 600; + int new_w = (width > 0) ? width : 800; + int new_h = (height > 0) ? height : 600; - if (!g_backbuffer) { - g_backbuffer = (uint32_t*)kzalloc(g_width * g_height * sizeof(uint32_t)); + /* A resolution change invalidates both RAM buffers. */ + if (g_backbuffer && (new_w != g_width || new_h != g_height)) { + kfree(g_backbuffer); + g_backbuffer = NULL; + if (g_shadow) { + kfree(g_shadow); + g_shadow = NULL; + } } + + g_frontbuffer = fb; + g_width = new_w; + g_height = new_h; + + size_t pixels = (size_t)g_width * (size_t)g_height; + if (!g_backbuffer) { + g_backbuffer = (uint32_t*)kzalloc(pixels * sizeof(uint32_t)); + } + if (!g_shadow) { + /* Optional: without it present() just falls back to a full copy. */ + g_shadow = (uint32_t*)kzalloc(pixels * sizeof(uint32_t)); + } + g_shadow_valid = false; +} + +void gui_gfx_invalidate(void) { + g_shadow_valid = false; +} + +uint64_t gui_gfx_last_present_pixels(void) { + return g_present_pixels; } int gui_gfx_get_width(void) { return g_width; } @@ -234,8 +272,55 @@ void gui_gfx_blit(int dx, int dy, int w, int h, const uint32_t* src_buf) { } } +/* + * Push the back buffer to the screen, writing only what changed. + * + * Every frame is still composited in full, so the cheapest reliable way to + * find the damage is to diff against the shadow copy: a RAM read is orders of + * magnitude cheaper than the framebuffer write it avoids. A typical idle + * desktop frame only moves the cursor and the clock, which turns a 3.6 MB + * blit at 1280x720 into a few kilobytes. + * + * Each row is narrowed to the span between its first and last differing pixel + * rather than tracked as a set of runs: one memcpy per row keeps the inner + * loop tight, and a row that changed in two places is rare enough that + * bridging the gap costs less than the extra bookkeeping. + */ void gui_gfx_present(void) { - if (g_frontbuffer && g_backbuffer) { - memcpy(g_frontbuffer, g_backbuffer, g_width * g_height * sizeof(uint32_t)); + if (!g_frontbuffer || !g_backbuffer) return; + + size_t pixels = (size_t)g_width * (size_t)g_height; + + /* No shadow (allocation failed) or it does not describe the screen yet: + * push the whole frame and start tracking from there. */ + if (!g_shadow || !g_shadow_valid) { + memcpy(g_frontbuffer, g_backbuffer, pixels * sizeof(uint32_t)); + if (g_shadow) { + memcpy(g_shadow, g_backbuffer, pixels * sizeof(uint32_t)); + g_shadow_valid = true; + } + g_present_pixels = pixels; + return; } + + size_t pushed = 0; + for (int y = 0; y < g_height; y++) { + size_t row = (size_t)y * (size_t)g_width; + const uint32_t* back = g_backbuffer + row; + uint32_t* shadow = g_shadow + row; + + int x0 = 0; + while (x0 < g_width && back[x0] == shadow[x0]) x0++; + if (x0 == g_width) continue; /* row is untouched */ + + int x1 = g_width - 1; + while (x1 > x0 && back[x1] == shadow[x1]) x1--; + + size_t span = (size_t)(x1 - x0 + 1); + memcpy(g_frontbuffer + row + x0, back + x0, span * sizeof(uint32_t)); + memcpy(shadow + x0, back + x0, span * sizeof(uint32_t)); + pushed += span; + } + + g_present_pixels = pushed; } diff --git a/include/gui/gui_desktop.h b/include/gui/gui_desktop.h index 5e143f5..e77db9e 100644 --- a/include/gui/gui_desktop.h +++ b/include/gui/gui_desktop.h @@ -11,6 +11,10 @@ void gui_desktop_init(void); void gui_desktop_render_background(void); + +// Drop the cached wallpaper so the next background render repaints it. Needed +// when the resolution changes or the framebuffer is handed to another owner. +void gui_desktop_invalidate_background(void); void gui_desktop_render_icons(void); void gui_desktop_render_taskbar(void); void gui_desktop_render_start_menu(void); diff --git a/include/gui/gui_gfx.h b/include/gui/gui_gfx.h index f6059bc..4496e12 100644 --- a/include/gui/gui_gfx.h +++ b/include/gui/gui_gfx.h @@ -42,6 +42,18 @@ void gui_gfx_draw_string_shadow(int x, int y, const char* str, uint32_t fg, uint void gui_gfx_draw_string_16_shadow(int x, int y, const char* str, uint32_t fg, uint32_t shadow); void gui_gfx_draw_shadow(int x, int y, int w, int h, int blur); void gui_gfx_blit(int dx, int dy, int w, int h, const uint32_t* src_buf); + +// Push the composited frame to the display. Only the pixels that differ from +// the previous frame are written, so an idle desktop costs almost no +// framebuffer traffic. void gui_gfx_present(void); +// Force the next present to push the whole screen. Needed whenever something +// outside the compositor has drawn over the framebuffer, such as the kernel +// console before the desktop takes the display back. +void gui_gfx_invalidate(void); + +// Pixels written to the framebuffer by the last present. Diagnostic only. +uint64_t gui_gfx_last_present_pixels(void); + #endif // _GUI_GFX_H