1b4fbeaa6b
Логический кадр = ровно n кадровых интервалов (1=50/2=25/3=~16.7 fps); при переполнении слота — выравнивание на ближайший фронт (без дрейфа фазы, в отличие от наивного «жди n фронтов»). Механика: фоновый счётчик _gfx_frame_tick инкрементит _gfx_frame_isr, поставленный в СВОЙ слот цепи (irq_chain_add); gfx_set_fps_div(1) снимает только этот слот (irq_chain_remove), не трогая хендлер приложения. gfx_wait_vsync: ветка n>=2 (счётчик + halt) перед лучевым поллингом; поллинг вынесен в static gfx_wait_vsync_beam (функция с хвостовым __asm не должна иметь переходов через asm — SDCC не эмитит эпилог-метку; ранний return делителя в чистом-C gfx_wait_vsync). Файлы: common/_gfx_fps_state.c (данные), _gfx_frame_isr.c (ISR), gfx_set_fps_div.c (сеттер, единственная ссылка на irq-механику → DCE). Работает tiny/big/huge (цепочка all-modes); small для мелких программ = EINVAL. Проверено MAME (tests/fpsdiv): n=1/2/3 → 20/40/60 кадров на 20 wait'ов (drift=0); n=2 с рендер-заглушкой ~1 кадр → период держится 2 (поглощение перерасхода, наивный путь дал бы ~60); huge идентично; small = EINVAL graceful. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
43 lines
2.1 KiB
C
43 lines
2.1 KiB
C
/*
|
||
* gfx_set_fps_div — задать делитель кадровой частоты (frame pacing).
|
||
* n: логический кадр = РОВНО n кадровых интервалов (1=50 fps дефолт,
|
||
* 2=25, 3=~16.7, 4=12.5, ...; 0 трактуется как 1). gfx_wait_vsync()
|
||
* при n>=2 переключается со лучевого поллинга на счётчиковый путь
|
||
* (docs/sprite-api-design.md §9е).
|
||
*
|
||
* n>=2: однократно ставит _gfx_frame_isr в ЦЕПОЧКУ кадровых IRQ
|
||
* (irq_chain_add — свой слот, не мешает собственному хендлеру
|
||
* приложения); повторные вызовы только меняют делитель.
|
||
* n<=1: снимает ТОЛЬКО свой слот (irq_chain_remove, НЕ irq_remove —
|
||
* чтобы не снести хендлер приложения) и возвращает старый путь.
|
||
*
|
||
* Возврат 0 / -1+errno: EINVAL (данные не в W2 — неподходящий memory
|
||
* mode, из irq_chain_add), ENOMEM (все слоты цепи заняты). ТОЛЬКО этот
|
||
* модуль ссылается на irq-механику и _gfx_frame_isr — программа без
|
||
* вызова сеттера не линкует ни цепочку, ни ISR (DCE).
|
||
*/
|
||
#include "../_bgi.h"
|
||
#include <irq.h>
|
||
|
||
int gfx_set_fps_div(uint8_t n)
|
||
{
|
||
if (n < 2) {
|
||
/* Выключение: снять свой слот, если ставили. */
|
||
if (_gfx_fps_div >= 2) {
|
||
irq_chain_remove(_gfx_frame_isr);
|
||
_gfx_fps_div = 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Первое включение — занять слот цепи под свой ISR. */
|
||
if (_gfx_fps_div < 2) {
|
||
if (irq_chain_add(_gfx_frame_isr) != 0)
|
||
return -1; /* errno уже выставлен (EINVAL/ENOMEM) */
|
||
/* База отсчёта — с текущего тика, чтобы первый wait не «опоздал». */
|
||
_gfx_fps_last = _gfx_frame_tick;
|
||
}
|
||
_gfx_fps_div = n;
|
||
return 0;
|
||
}
|