libbgi: idle-хук в ожидании кадра; им лечится потеря нажатий с Shift

Причина потерь (замеры — applications/PoP/roomtest/TASKS.md, KBD-1): при
зажатом Shift PS/2 обрамляет расширенный код «фиктивным шифтом», нажатие
стрелки становится 5 байтами вместо 2, а импульс запроса прерывания здесь
теряется примерно в 44 % случаев — трёхбайтовый FIFO SIO переполняется, и
байт пропадает ДО чтения порта.  Лечится только плотным вычерпыванием: раз
в ~0.5 мс.  Столько времени есть даром — при пейсинге «3 растровых кадра на
логический тик» процессор проводит ~42 мс из 60 в gfx_wait_vsync, крутя
опрос луча и больше ничего не делая.

- gfx_set_idle_hook(fn) — что вызывать, пока gfx_wait_vsync ждёт луч.
  Состояние в отдельном data-модуле (_gfx_idle_state.c), чтобы не тянуть
  сеттер в программы, которые хук не ставят.
- Лучевой цикл зовёт хук в обеих фазах.  BC (счётчик таймаута)
  сохраняется, косвенный вызов — push адреса возврата + jp (hl), так как
  `call (hl)` в Z80 нет; без хука это ret по нулевому указателю, порядка
  двух десятков тактов в цикле, который и так сжигает время.
- Путь FPS-делителя не затронут: там ожидание через HALT.
- roomtest вешает на хук kbd_raw_poll.

Проверка в MAME счётчиками (брейкпоинты с { b@ADDR = b@ADDR+1 ; g } на
чтении порта 0x18 и на установке make-бита): 35 нажатий Shift+Home → 35
make, ноль потерь; до фикса было 9 из 10.  Боевой сценарий: четыре Shift+→
подряд дали четыре осторожных шага (Kid.x 114 -> 147).  _CODE +170 Б,
кадровый бюджет не затронут.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-01 16:05:24 +03:00
parent b56f2b4582
commit 4b498d171b
7 changed files with 125 additions and 19 deletions
+12
View File
@@ -150,6 +150,18 @@ void gfx_heal(int x, int y, int w, int h);
* gfx_set_visible_page(hidden); // tear-free flip */
void gfx_wait_vsync(void);
/* Что вызывать, ПОКА gfx_wait_vsync ждёт луч. Ожидание кадра — самый
* длинный простой в кадре (при пейсинге «3 растровых кадра на логический
* тик» это ~42 мс из 60), и хук занимает его, не отнимая тактов у
* полезной работы. Повод — вычерпывание FIFO клавиатуры по опросу
* (`kbd_raw_poll`, см. <kbd_raw.h>): трёхбайтовый приёмник переполняется
* на пачках «fake shift», а плотность нужна ~раз в 0.5 мс, которую иначе
* взять негде. Хук зовётся в тесном цикле: он обязан быть дешёвым, НЕ
* рисовать (окно W3 сейчас не за графикой) и не ждать сам. fn = 0 —
* снять. На путь FPS-делителя (gfx_set_fps_div n>=2) не влияет: там
* ожидание идёт через HALT, и процессор не крутит цикл. */
void gfx_set_idle_hook(void (*fn)(void));
/* FPS-делитель (frame pacing): логический кадр = РОВНО n кадровых
* интервалов, независимо от плавания длительности рендера. n: 1 = 50 fps
* (дефолт), 2 = 25, 3 = ~16.7, 4 = 12.5, ...; 0 трактуется как 1.