From bf9ac843f7a5072cd6633b24214427b745029414 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 10 Aug 2026 05:54:06 +0200 Subject: [PATCH] schcom: repaint what changed, and stop rendering frames nobody sees Moving the mouse repainted the whole screen. At 1280x800 that is a million pixels of desktop fill, every surface re-blitted, and a 4 MB copy to the framebuffer -- per event. Under emulation the pointer lagged the mouse by whole seconds, and that was not the emulation's fault: moving a 10x10 cursor does not require touching four megabytes. Two changes, and the second matters as much as the first. Damage tracking: one rectangle, unioned from whatever actually changed, and only those rows are redrawn and copied out. A pointer move damages where it was and where it is; a commit damages that surface; a client going away damages what it had. One rectangle rather than a list, because in practice the changes are a cursor and the window under it, and a list costs more bookkeeping than it saves. Draining input: a single mouse movement produces a burst of EV_REL ending in EV_SYN, and the loop handled one event per poll and repainted. So the compositor rendered every intermediate position of a cursor that was never seen at those positions. Both queues are now drained before anything is painted. handle_input grew a distinct "queue empty" return for it -- EV_SYN changes nothing but is not the end of the burst, and a drain that stopped there would leave the rest queued. Also cuts the loader's resolution prompt from four seconds to one and a half, on both the BIOS and UEFI paths. Four was mine and it was too long: it is paid on every unattended boot, and adding four seconds to every start to offer a menu nobody usually wants makes the common case worse to serve the rare one. schcom-test passes, including two overlapping clients and stacking order. bootcheck 108 [ok]. Co-Authored-By: Claude Opus 5 --- boot/bios/stage2.S | 16 +++-- boot/uefi/main.c | 10 +-- projects/schcom/schcom | Bin 43128 -> 45192 bytes projects/schcom/schcom.c | 141 +++++++++++++++++++++++++++++++-------- 4 files changed, 130 insertions(+), 37 deletions(-) diff --git a/boot/bios/stage2.S b/boot/bios/stage2.S index 3f98b66..44de461 100644 --- a/boot/bios/stage2.S +++ b/boot/bios/stage2.S @@ -337,8 +337,10 @@ reqh: # path, so whatever is chosen here is what the framebuffer, the console and the # compositor all get. # -# Four seconds, then it boots on its own. A loader that waits for a keypress is -# a machine that does not come back up after a power cut. +# A second and a half, then it boots on its own. This was four seconds and that +# was simply too long: it is paid on every unattended boot, and a loader that +# adds four seconds to every start to offer a menu nobody usually wants has made +# the common case worse to serve the rare one. # # In: CX = default mode. Out: CX = chosen mode. Clobbers AX, BX, DX, SI. res_menu: @@ -346,9 +348,9 @@ res_menu: movw $resprompt, %si call print16 - # Poll for ~4s. INT 1Ah tick count runs at 18.2 Hz, so 72 ticks is four - # seconds and needs no calibration against CPU speed -- a delay loop would be - # a different length on every machine. + # Poll for ~1.5s. INT 1Ah tick count runs at 18.2 Hz, so 27 ticks is a second + # and a half and needs no calibration against CPU speed -- a delay loop would + # be a different length on every machine. movb $0x00, %ah int $0x1A # CX:DX = ticks since midnight movw %dx, %bx # BX = starting tick, low word @@ -358,7 +360,7 @@ res_menu: movb $0x00, %ah int $0x1A subw %bx, %dx - cmpw $72, %dx + cmpw $27, %dx jb .Lrwait movw $restimeout, %si call print16 @@ -467,7 +469,7 @@ resmodes: .word 0x0145 # 1280x1024 verified resprompt: - .asciz "Press R within 4s to choose a resolution... " + .asciz "Press R for resolution... " restimeout: .asciz "keeping 800x600\r\n" resmenu: diff --git a/boot/uefi/main.c b/boot/uefi/main.c index 8616e71..1fa6632 100644 --- a/boot/uefi/main.c +++ b/boot/uefi/main.c @@ -261,8 +261,10 @@ load_elf(EFI_FILE_PROTOCOL *file) // and which index means what varies by machine. So this walks them, and the // menu is built from what is actually there rather than from a table. // -// Four seconds, then it boots on its own. A loader that waits for a keypress -// is a machine that does not come back up after a power cut. +// A second and a half, then it boots on its own. This was four seconds and that +// was too long: it is paid on every unattended boot, and adding four seconds to +// every start to offer a menu nobody usually wants makes the common case worse +// to serve the rare one. static void print_dec(UINT32 v) @@ -330,8 +332,8 @@ choose_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) { UINT32 current = gop->Mode->Mode; - print(L"Press R within 4s to choose a resolution... "); - CHAR16 c = wait_key(4000); + print(L"Press R for resolution... "); + CHAR16 c = wait_key(1500); if (c != L'r' && c != L'R') { print(L"keeping "); print_dec(gop->Mode->Info->HorizontalResolution); print(L"x"); diff --git a/projects/schcom/schcom b/projects/schcom/schcom index ae9f2c57090b74fdaf570978e494cade170e9763..69fe506d09d04ee7b7869bfbf3a70e0c0fbd5026 100644 GIT binary patch delta 15230 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 vPrZ7Rv2vxVy-6 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 diff --git a/projects/schcom/schcom.c b/projects/schcom/schcom.c index 38902d7..1518b29 100644 --- a/projects/schcom/schcom.c +++ b/projects/schcom/schcom.c @@ -120,14 +120,66 @@ open_framebuffer(void) return fd; } +/* Damage: the rectangle that has to be redrawn and copied out. + * + * Without this the compositor repainted the whole screen for every event -- + * at 1280x800 that is a million pixels of desktop fill plus a 4 MB copy to the + * framebuffer, per mouse movement. Under emulation the pointer lagged behind + * the mouse by whole seconds, and it was not the emulation's fault: moving a + * 10x10 cursor does not require touching four megabytes. + * + * One rectangle rather than a list. Two distant small changes then repaint the + * span between them, which is wasteful in theory; in practice the changes are + * a pointer and the window under it, and a list costs more bookkeeping than it + * saves. + */ +static int dmg_x0, dmg_y0, dmg_x1, dmg_y1; /* x1/y1 exclusive */ +static int dirty; + +static void +damage(int x, int y, int w, int h) +{ + int x0 = x, y0 = y, x1 = x + w, y1 = y + h; + + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 > (int)fb_w) x1 = (int)fb_w; + if (y1 > (int)fb_h) y1 = (int)fb_h; + if (x0 >= x1 || y0 >= y1) + return; + + if (!dirty) { + dmg_x0 = x0; dmg_y0 = y0; dmg_x1 = x1; dmg_y1 = y1; + dirty = 1; + return; + } + if (x0 < dmg_x0) dmg_x0 = x0; + if (y0 < dmg_y0) dmg_y0 = y0; + if (x1 > dmg_x1) dmg_x1 = x1; + if (y1 > dmg_y1) dmg_y1 = y1; +} + +static void +damage_all(void) +{ + damage(0, 0, (int)fb_w, (int)fb_h); +} + +#define PTR_SIZE 10 + static void composite(void) { + if (!dirty) + return; + + const int rx0 = dmg_x0, ry0 = dmg_y0, rx1 = dmg_x1, ry1 = dmg_y1; + /* Desktop. A flat colour rather than black, so that "nothing was drawn" * and "the compositor is not running" cannot look the same. */ - for (uint32_t y = 0; y < fb_h; y++) { + for (int y = ry0; y < ry1; y++) { uint32_t *row = (uint32_t *)(back + (size_t)y * fb_stride); - for (uint32_t x = 0; x < fb_w; x++) + for (int x = rx0; x < rx1; x++) row[x] = 0x00203040; } @@ -139,17 +191,18 @@ composite(void) if (!s->used || !s->pixels) continue; + /* Only the part of this surface inside the damage rectangle. */ for (uint32_t sy = 0; sy < s->h; sy++) { - int32_t dy = s->y + (int32_t)sy; - if (dy < 0 || dy >= (int32_t)fb_h) + int dy = s->y + (int)sy; + if (dy < ry0 || dy >= ry1) continue; uint32_t *dst = (uint32_t *)(back + (size_t)dy * fb_stride); uint32_t *src = (uint32_t *)((uint8_t *)s->pixels + (size_t)sy * s->stride); for (uint32_t sx = 0; sx < s->w; sx++) { - int32_t dx = s->x + (int32_t)sx; - if (dx < 0 || dx >= (int32_t)fb_w) + int dx = s->x + (int)sx; + if (dx < rx0 || dx >= rx1) continue; dst[dx] = src[sx]; } @@ -157,22 +210,29 @@ composite(void) } /* A crude pointer, drawn last so it is never under a window. */ - for (int32_t py = 0; py < 10; py++) { - int32_t dy = ptr_y + py; - if (dy < 0 || dy >= (int32_t)fb_h) + for (int py = 0; py < PTR_SIZE; py++) { + int dy = ptr_y + py; + if (dy < ry0 || dy >= ry1) continue; uint32_t *dst = (uint32_t *)(back + (size_t)dy * fb_stride); - for (int32_t px = 0; px < 10 - py; px++) { - int32_t dx = ptr_x + px; - if (dx >= 0 && dx < (int32_t)fb_w) + for (int px = 0; px < PTR_SIZE - py; px++) { + int dx = ptr_x + px; + if (dx >= rx0 && dx < rx1) dst[dx] = 0x00FFFFFF; } } - /* One copy to the screen. Compositing straight into the framebuffer would - * show every intermediate state -- the flicker would be the whole desktop - * being repainted under the user's eyes. */ - memcpy(fb, back, fb_size); + /* Copy out only what changed, row by row. Compositing straight into the + * framebuffer would show every intermediate state -- the flicker would be + * the whole desktop being repainted under the user's eyes -- but copying + * the whole thing to move a cursor is what made the pointer lag. */ + size_t bytes = (size_t)(rx1 - rx0) * 4; + for (int y = ry0; y < ry1; y++) { + size_t off = (size_t)y * fb_stride + (size_t)rx0 * 4; + memcpy(fb + off, back + off, bytes); + } + + dirty = 0; } /* ---------------------------------------------------------------- clients */ @@ -234,6 +294,7 @@ drop_client(int ci) for (int i = 0; i < MAX_SURFACES; i++) { struct surface *s = &surfaces[i]; if (s->used && s->client == ci) { + damage(s->x, s->y, (int)s->w, (int)s->h); if (s->pixels) shmdt(s->pixels); /* IPC_RMID once, here: the segment goes when the last attach does, @@ -318,6 +379,7 @@ handle_create_surface(int ci, const struct schcom_create_surface *req) drop_client(ci); return; } + damage(s->x, s->y, (int)w, (int)h); printf("schcom: surface %u %ux%u at %d,%d for client %d (shm %d)\n", s->id, w, h, s->x, s->y, ci, shmid); fflush(stdout); @@ -404,7 +466,10 @@ handle_message(int ci) return 0; } /* The commit is the whole point: the pixels were already there, but - * they were not allowed to be shown until now. */ + * they were not allowed to be shown until now. The damage rectangle in + * the message is advisory -- repaint the whole surface, which is never + * wrong, only sometimes more than asked. */ + damage(s->x, s->y, (int)s->w, (int)s->h); struct schcom_header fr; memset(&fr, 0, sizeof fr); fr.op = SCHCOM_OP_FRAME; @@ -420,6 +485,7 @@ handle_message(int ci) case SCHCOM_OP_DESTROY: { struct surface *s = find_surface(hdr.surface); if (s && s->client == ci) { + damage(s->x, s->y, (int)s->w, (int)s->h); if (s->pixels) shmdt(s->pixels); shmctl(s->shmid, IPC_RMID, NULL); @@ -438,23 +504,32 @@ handle_message(int ci) /* ----------------------------------------------------------------- input */ -/* Returns 1 if the screen needs repainting, -1 to quit. */ +/* 1 if the screen needs repainting, 0 if the event changed nothing, -1 to + * quit, -2 when the queue is empty. The last is separate from 0 on purpose: + * every burst of movement ends in an EV_SYN that changes nothing, and a + * drain loop that stopped there would leave the rest of the burst queued. */ static int handle_input(int fd) { struct input_event ev; if (read(fd, &ev, sizeof ev) != (ssize_t)sizeof ev) - return 0; + return -2; int repaint = 0; if (ev.type == EV_REL) { + int old_x = ptr_x, old_y = ptr_y; if (ev.code == REL_X) { ptr_x += ev.value; repaint = 1; } if (ev.code == REL_Y) { ptr_y += ev.value; repaint = 1; } if (ptr_x < 0) ptr_x = 0; if (ptr_y < 0) ptr_y = 0; if (ptr_x >= (int32_t)fb_w) ptr_x = (int32_t)fb_w - 1; if (ptr_y >= (int32_t)fb_h) ptr_y = (int32_t)fb_h - 1; + if (repaint) { + /* Where it was and where it is. Everything between is untouched. */ + damage(old_x, old_y, PTR_SIZE, PTR_SIZE); + damage(ptr_x, ptr_y, PTR_SIZE, PTR_SIZE); + } } if (ev.type == EV_KEY && ev.value == 1 && ev.code == KEY_ESC) @@ -523,6 +598,7 @@ main(int argc, char **argv) printf("schcom: listening on %s\n", SCHCOM_SOCKET_PATH); fflush(stdout); + damage_all(); composite(); int frames = 1; @@ -568,16 +644,29 @@ main(int argc, char **argv) } } + /* Drain both devices before painting anything. A single mouse + * movement produces a burst of EV_REL followed by EV_SYN, and + * repainting per event meant the compositor rendered every + * intermediate position of a cursor nobody saw at those positions -- + * which is most of what "the mouse lags" was. */ + int quit = 0; if (kbd_slot >= 0 && (pfd[kbd_slot].revents & POLLIN)) { - int r = handle_input(kbd); - if (r < 0) break; - repaint |= r; + for (int n = 0; n < 256; n++) { + int r = handle_input(kbd); + if (r == -2) break; + if (r < 0) { quit = 1; break; } + if (r > 0) repaint = 1; + } } - if (mouse_slot >= 0 && (pfd[mouse_slot].revents & POLLIN)) { - int r = handle_input(mouse); - if (r < 0) break; - repaint |= r; + if (!quit && mouse_slot >= 0 && (pfd[mouse_slot].revents & POLLIN)) { + for (int n = 0; n < 256; n++) { + int r = handle_input(mouse); + if (r == -2) break; + if (r < 0) { quit = 1; break; } + if (r > 0) repaint = 1; + } } + if (quit) break; /* Walk the client slots in the same order they were added to pfd. */ int k = first_client;