Files
Sprinter-SDCC/tests/fpsdiv/fpsdiv.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

108 lines
4.2 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.
/*
* fpsdiv — тест FPS-делителя (frame pacing, docs/sprite-api-design.md §9е).
*
* Проверяет:
* 1. коэффициент: gfx_set_fps_div(n) → gfx_wait_vsync() ждёт РОВНО n
* кадровых интервалов; 20 wait'ов копят ровно 20·n кадров (точное
* значение = отсутствие дрейфа фазы);
* 2. поглощение перерасхода: при n=2 с рендер-заглушкой ~1 кадр
* период держится 2 (wait добирает до границы слота), а не уезжает
* в 3 — то, чего наивный «жди n фронтов подряд» не умеет;
* 3. сосуществование со СВОИМ хендлером приложения: тест сам держит
* слот цепи (on_frame), делитель добавляет второй — оба тикают,
* gfx_set_fps_div(1) снимает ТОЛЬКО свой слот.
*
* Графический режим НЕ инициализируется: счётчиковый путь gfx_wait_vsync
* трогает лишь _gfx_frame_tick + halt (видео не нужно). Собственный
* счётчик кадров (on_frame) меряет реальные фронты независимо от делителя.
*/
#include <stdio.h>
#include <gfx.h>
#include <irq.h>
#include <errno.h>
static volatile uint16_t frames; /* реальные кадры (наш слот цепи) */
static void on_frame(void)
{
frames++;
}
/* Атомарное чтение счётчика, разделяемого с ISR. */
static uint16_t rd(void)
{
uint16_t v;
IRQ_DISABLE();
v = frames;
IRQ_ENABLE();
return v;
}
/* Занятая пауза без ESTEX — «рендер». */
static void busy(uint16_t loops)
{
while (loops--) {
__asm nop __endasm;
}
}
int main(void)
{
/* Свой слот цепи — независимый счётчик реальных кадров. */
if (irq_chain_add(on_frame) != 0) {
printf("chain_add(on_frame): errno=%d\n", errno);
return 1;
}
/* --- 1. коэффициент n=1/2/3 --- */
for (uint8_t n = 1; n <= 3; n++) {
if (gfx_set_fps_div(n) != 0) {
printf("set_fps_div(%u): errno=%d\n", n, errno);
return 1;
}
gfx_wait_vsync(); /* выровняться на границу слота */
uint16_t f0 = rd();
for (uint8_t i = 0; i < 20; i++)
gfx_wait_vsync();
uint16_t df = rd() - f0;
printf("n=%u: %u frames / 20 waits (expect %u; drift=%d)\n",
n, df, (unsigned)(20 * n), (int)df - (int)(20 * n));
}
/* --- 2. поглощение перерасхода при n=2 --- */
gfx_set_fps_div(2);
gfx_wait_vsync();
/* Откалибровать заглушку так, чтобы «рендер» пересекал ~1 фронт. */
uint16_t spin = 2000;
for (;;) {
uint16_t a = rd();
busy(spin);
if (rd() - a >= 1) break; /* заняли >= 1 интервала */
spin = (uint16_t)(spin + spin / 2 + 1000);
if (spin > 60000u) break;
}
printf("stub spin=%u (~1 frame of render)\n", spin);
gfx_wait_vsync(); /* чистая граница слота */
uint16_t p0 = rd();
for (uint8_t i = 0; i < 20; i++) {
busy(spin); /* рендер ~1 кадр */
gfx_wait_vsync(); /* слот n=2 должен добрать до 2 */
}
uint16_t pd = rd() - p0;
printf("n=2 +render: %u frames / 20 waits (expect 40; naive~60)\n", pd);
/* --- 3. снять ТОЛЬКО слот делителя, наш on_frame обязан жить --- */
gfx_set_fps_div(1);
uint16_t g0 = rd();
busy(40000); /* дать нескольким кадрам пройти */
uint16_t gd = rd() - g0;
printf("after fps_div(1): on_frame still ticks: %u (>0)\n", gd);
irq_chain_remove(on_frame);
puts("fpsdiv done.");
return 0;
}