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 ae9f2c5..69fe506 100644 Binary files a/projects/schcom/schcom and b/projects/schcom/schcom differ 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;