perf(gui): Present only changed pixels and cache the wallpaper

The compositor recomposited and re-uploaded the entire screen on every pass of
its loop. At the 1280x720 the desktop actually boots into, gui_gfx_present()
memcpy'd 3.6 MB into the framebuffer per frame, and the background underneath
it was recomputed pixel by pixel every time.

Present only the damage
- Keep a RAM shadow of what the framebuffer currently holds and diff the back
  buffer against it, pushing just the changed span of each row. Reading the
  shadow costs a RAM access; the framebuffer write it avoids costs far more,
  and the framebuffer is never read back.
- Each row is narrowed to its first and last differing pixel rather than
  tracked as a set of runs: one memcpy per row keeps the loop tight, and a row
  that changed in two distant places is rare enough that bridging the gap is
  cheaper than the bookkeeping.
- gui_gfx_invalidate() forces a full push for when something outside the
  compositor owns the screen, which the desktop calls on entry and on exit
  around the framebuffer console.

Cache the wallpaper
- The vertical gradient, the blended grid and the branding text never change
  between frames, so they are painted once into a cached buffer and blitted
  back per frame. The cache is keyed on resolution and the grid setting, so
  toggling the grid from the context menu still repaints it.

No drawing code changed: the damage is derived from the frame itself, so every
widget, window and app keeps rendering exactly as before.

Measured in QEMU at 1280x720: an idle desktop with two windows open went from
921600 pixels per frame to 3609, about 0.4% of a full blit. A screendump of
the running desktop is unchanged -- wallpaper, grid, icons, both windows,
taskbar and cursor all render identically.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0161Jh7uCEMLYEMNPsjC1LKf
This commit is contained in:
subhobhai943andClaude Opus 5 committed 2026-08-25 07:06:08 +00:00
1 parent 65b7a709cc
commit 2136b6fb4c
4 files changed
+199 -11

No files matched your search

+91 -4
View File
@@ -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;