Files
Sprinter-SDCC/libbgi/common/gfx_set_fps_div.c
T
snark13 1b4fbeaa6b libbgi: FPS-делитель gfx_set_fps_div(n) поверх цепочки кадровых IRQ
Логический кадр = ровно 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>
2026-07-15 10:36:37 +03:00

43 lines
2.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}