gfx/time: vsync через polling бита кадра, sleep/delayms без halt-подсчёта
gfx_wait_vsync() и sleep() раньше предполагали, что КАЖДОЕ прерывание на векторе 0xFF — кадровый тик; с CBL/клавиатурой на том же векторе это уже не так. gfx_wait_vsync(): вместо halt — polling бита 5 порта 0xFE (реальная позиция луча, см. MAME kbd_fe_r), доступного пока включён CBL bit7 порта 0x004E. Разделяемое владение портом с CBL через _cbl_port_ref/unref (тот же ref-counting паттерн, что у IM2-таблицы) — cbl_close() возвращает "немой" режим вместо полного выключения, если gfx ещё держит ссылку. Fallback на halt при таймауте. sleep()/delayms(): калиброванный busy-wait по духу delayms.asm вместо подсчёта halt-пробуждений. Калибровка одна на кадр (не на секунду — не переполняет uint16_t и не требует умножения/32-бит арифметики), общий движок libc/time/_sleep_calib.c для обеих функций. Fallback на старое поведение при EBUSY (фрейм-хук занят другим irq_install()). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,73 @@
|
||||
/*
|
||||
* gfx_wait_vsync — ждать следующий кадровый IM2-прерыв (50 Гц, DSS).
|
||||
* gfx_wait_vsync — ждать переход бита 5 порта 0xFE из 1 в 0: выход из
|
||||
* нижнего blank/border (Y > 256 — см. MAME sprinter.cpp kbd_fe_r:
|
||||
* `vpos() >= BORDER_TOP+SCREEN_YSIZE`) — начало нового кадра, идеальный
|
||||
* момент для свопа страниц без разрывов. Это чтение РЕАЛЬНОГО
|
||||
* состояния луча (не событие/прерывание), поэтому не подвержено
|
||||
* путанице с другими источниками вектора 0xFF (клавиатура, CBL, CTC) —
|
||||
* подробнее см. memory/gfx_wait_vsync_redesign.
|
||||
*
|
||||
* HALT усыпляет Z80 до ближайшего IRQ; DSS обрабатывает его и
|
||||
* возвращается на инструкцию после HALT — это начало обратного хода
|
||||
* луча, идеальный момент для свопа страниц без разрывов.
|
||||
* Бит доступен ТОЛЬКО пока включён cbl_mode() (bit7 порта 0x004E) —
|
||||
* см. libc/cbl/_cbl_port.c: если приложение уже играет через
|
||||
* cbl_open(), берём бит бесплатно (порт не трогаем); иначе
|
||||
* лениво занимаем bit7 "немым" кодом частоты через _cbl_port_ref()
|
||||
* при первом вызове, освобождаем в atexit.
|
||||
*
|
||||
* Fallback: если ни разу не увидели ожидаемое состояние за разумное
|
||||
* число попыток (иное железо, бит ведёт себя не как в MAME) —
|
||||
* откатываемся на одно кадровое прерывание (halt), чтобы не
|
||||
* зависнуть намертво.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
#include "../cbl/_cbl.h"
|
||||
#include <sprinter_exit.h>
|
||||
|
||||
void gfx_wait_vsync(void) __naked
|
||||
static uint8_t vsync_ready;
|
||||
|
||||
static void vsync_cleanup(void)
|
||||
{
|
||||
_cbl_port_unref();
|
||||
}
|
||||
|
||||
void gfx_wait_vsync(void)
|
||||
{
|
||||
if (!vsync_ready) {
|
||||
vsync_ready = 1;
|
||||
_cbl_port_ref();
|
||||
atexit(vsync_cleanup);
|
||||
}
|
||||
|
||||
__asm
|
||||
;; --- фаза 1: дождаться bit5==1 (гарантированно в blank) ---
|
||||
ld bc, #0 ; ~65536 попыток на фазу — таймаут
|
||||
_gwv_wait1:
|
||||
in a, (#0xFE)
|
||||
bit 5, a
|
||||
jr nz, _gwv_have1
|
||||
dec bc
|
||||
ld a, b
|
||||
or c
|
||||
jr nz, _gwv_wait1
|
||||
jr _gwv_fallback ; bit5 ни разу не встал за таймаут
|
||||
_gwv_have1:
|
||||
|
||||
;; --- фаза 2: дождаться bit5==0 (сам переход — начало кадра) ---
|
||||
ld bc, #0
|
||||
_gwv_wait0:
|
||||
in a, (#0xFE)
|
||||
bit 5, a
|
||||
jr z, _gwv_done
|
||||
dec bc
|
||||
ld a, b
|
||||
or c
|
||||
jr nz, _gwv_wait0
|
||||
;; таймаут и здесь — переходим к тому же фолбэку
|
||||
|
||||
_gwv_fallback:
|
||||
ei
|
||||
halt
|
||||
ret
|
||||
halt ; аварийный путь: одно кадровое прерывание
|
||||
|
||||
_gwv_done:
|
||||
__endasm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user