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>
This commit is contained in:
+14
-6
@@ -27,12 +27,20 @@
|
|||||||
`irq_ctc_install` = EINVAL (нужна такая же W2-копия _irq_ctc_tramp);
|
`irq_ctc_install` = EINVAL (нужна такая же W2-копия _irq_ctc_tramp);
|
||||||
(б) small для мелких программ (BSS в W1) → irq_install EINVAL
|
(б) small для мелких программ (BSS в W1) → irq_install EINVAL
|
||||||
(safe; снять резервом area _IM2 в верхней W2).
|
(safe; снять резервом area _IM2 в верхней W2).
|
||||||
- [ ] **(2) FPS-делитель (frame pacing)** — gfx_set_fps_div(n): логический
|
- [x] **(2) FPS-делитель (frame pacing)** — СДЕЛАНО 2026-07-15.
|
||||||
кадр = ровно n кадровых интервалов (50/25/16.7 fps), при
|
gfx_set_fps_div(n): логический кадр = ровно n кадровых интервалов
|
||||||
переполнении слота — выравнивание на ближайший фронт; счётчик
|
(1=50/2=25/3=~16.7 fps); при переполнении слота — выравнивание на
|
||||||
кадров через irq_chain_add(_gfx_frame_isr) (только tiny/big в
|
ближайший фронт (без дрейфа фазы). Счётчик кадров _gfx_frame_tick
|
||||||
первой версии). ПОЛНЫЙ ПЛАН: docs/sprite-api-design.md §9е
|
через irq_chain_add(_gfx_frame_isr) — свой слот цепи (n<=1 снимает
|
||||||
(обсуждён 2026-07-14; ограничение EBUSY снято цепочкой).
|
его через irq_chain_remove, не трогая хендлер приложения).
|
||||||
|
gfx_wait_vsync: ветка n>=2 (счётчик+halt) перед лучевым поллингом.
|
||||||
|
Файлы: common/_gfx_fps_state.c, _gfx_frame_isr.c, gfx_set_fps_div.c,
|
||||||
|
правка gfx_wait_vsync.c. Работает 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.
|
||||||
|
ПЛАН: docs/sprite-api-design.md §9е.
|
||||||
- [ ] **П6/железо**: MAME-смоук всех тестов после libc-сплита (conio,
|
- [ ] **П6/железо**: MAME-смоук всех тестов после libc-сплита (conio,
|
||||||
ptime, stattest, mouse, gfx_demo/gfx_d16/gfx_text/gfx_mous —
|
ptime, stattest, mouse, gfx_demo/gfx_d16/gfx_text/gfx_mous —
|
||||||
трогался asm акселератора); затем прогон на реальном Sprinter
|
трогался asm акселератора); затем прогон на реальном Sprinter
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ errno 5966
|
|||||||
fbench 8085
|
fbench 8085
|
||||||
fdmax 6023
|
fdmax 6023
|
||||||
filetest 10547
|
filetest 10547
|
||||||
|
fpsdiv 4870
|
||||||
gets 509
|
gets 509
|
||||||
gfx_dbuf 5032
|
gfx_dbuf 5032
|
||||||
gfx_demo 4103
|
gfx_demo 4103
|
||||||
|
|||||||
|
@@ -842,7 +842,22 @@ putimage-заготовкой/маленьким bar.
|
|||||||
Тест: spranim T7 (порядок, слой поверх y, персистентный resort,
|
Тест: spranim T7 (порядок, слой поверх y, персистентный resort,
|
||||||
LAYER-only).
|
LAYER-only).
|
||||||
|
|
||||||
## 9е. FPS-делитель — frame pacing (ПЛАН, обсуждён 2026-07-14, НЕ реализовано)
|
## 9е. FPS-делитель — frame pacing (РЕАЛИЗОВАНО 2026-07-15, verified MAME)
|
||||||
|
|
||||||
|
**СТАТУС: сделано.** Файлы точно по плану ниже (common/_gfx_fps_state.c,
|
||||||
|
_gfx_frame_isr.c, gfx_set_fps_div.c, ветка в gfx_wait_vsync.c). Через
|
||||||
|
цепочку кадровых IRQ — работает tiny/big/huge (не только tiny/big);
|
||||||
|
small для мелких программ = EINVAL. tests/fpsdiv: n=1/2/3 → ровно
|
||||||
|
20/40/60 кадров на 20 wait'ов (drift=0); n=2 с рендер-заглушкой ~1 кадр
|
||||||
|
→ период 2 (поглощение перерасхода, наивный путь дал бы ~60); huge
|
||||||
|
идентично tiny; small = EINVAL graceful. Один нюанс реализации:
|
||||||
|
лучевой поллинг вынесен в static gfx_wait_vsync_beam() (функция,
|
||||||
|
кончающаяся непрозрачным __asm, не должна иметь переходов «через asm» —
|
||||||
|
SDCC не эмитит эпилог-метку; ранний return делителя живёт в чистом-C
|
||||||
|
gfx_wait_vsync). Ниже — исходный план (в силе).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
Цель (постановка пользователя): управляемые режимы 50/25/~16.7 fps —
|
Цель (постановка пользователя): управляемые режимы 50/25/~16.7 fps —
|
||||||
логический кадр занимает РОВНО n кадровых интервалов независимо от
|
логический кадр занимает РОВНО n кадровых интервалов независимо от
|
||||||
|
|||||||
@@ -313,4 +313,17 @@ void _bgi_poly_edge(int x0, int y0, int x1, int y1);
|
|||||||
extern void _cbl_port_ref(void);
|
extern void _cbl_port_ref(void);
|
||||||
extern void _cbl_port_unref(void);
|
extern void _cbl_port_unref(void);
|
||||||
|
|
||||||
|
/* ---- FPS-делитель (frame pacing) --------------------------------- *
|
||||||
|
* Фоновый счётчик кадров через цепочку кадровых IRQ (libc/irq). Данные
|
||||||
|
* в common/_gfx_fps_state.c; при _gfx_fps_div<=1 путь не активен и
|
||||||
|
* gfx_wait_vsync остаётся лучевым поллингом. Механику irq_chain_add
|
||||||
|
* дёргает ТОЛЬКО gfx_set_fps_div.c (DCE). */
|
||||||
|
extern uint8_t _gfx_fps_div; /* 0/1 = старый путь; >=2 — делитель */
|
||||||
|
extern volatile uint8_t _gfx_frame_tick; /* инкрементит _gfx_frame_isr */
|
||||||
|
extern uint8_t _gfx_fps_last; /* тик прошлого выхода из wait */
|
||||||
|
|
||||||
|
/* Кадровый ISR: чистый инкремент _gfx_frame_tick (правила irq.h —
|
||||||
|
* никаких ESTEX/gfx/банков). */
|
||||||
|
void _gfx_frame_isr(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* _gfx_fps_state — данные FPS-делителя (frame pacing, docs/sprite-api-
|
||||||
|
* design.md §9е). Только данные; НЕ инициализировать (crt0 зануляет
|
||||||
|
* _DATA — див=0 трактуется как «делитель выключен»).
|
||||||
|
*
|
||||||
|
* _gfx_fps_div — n: логический кадр = n кадровых интервалов; 0/1 =
|
||||||
|
* старый лучевой поллинг gfx_wait_vsync (путь не активен);
|
||||||
|
* _gfx_frame_tick— счётчик кадров, инкрементит _gfx_frame_isr; вся
|
||||||
|
* арифметика ожидания разностная по модулю 256 (wrap
|
||||||
|
* безопасен);
|
||||||
|
* _gfx_fps_last — значение счётчика на прошлом выходе из wait (база
|
||||||
|
* отсчёта интервалов).
|
||||||
|
*
|
||||||
|
* Модуль отделён от сеттера/ISR/wait, чтобы программа без вызова
|
||||||
|
* gfx_set_fps_div() тянула только эти 3 байта, но не irq-механику.
|
||||||
|
*/
|
||||||
|
#include "../_bgi.h"
|
||||||
|
|
||||||
|
uint8_t _gfx_fps_div;
|
||||||
|
volatile uint8_t _gfx_frame_tick;
|
||||||
|
uint8_t _gfx_fps_last;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* _gfx_frame_isr — кадровый обработчик FPS-делителя (ставится в цепочку
|
||||||
|
* через irq_chain_add из gfx_set_fps_div). Правила irq.h: вызывается
|
||||||
|
* ~50 раз/с, никаких ESTEX/BIOS/gfx/банков — только инкремент счётчика.
|
||||||
|
*
|
||||||
|
* Разностная семантика ожидания в gfx_wait_vsync терпит wrap uint8,
|
||||||
|
* поэтому счётчик 8-битный (одна INC (nn) — минимальный хвост в ISR).
|
||||||
|
*/
|
||||||
|
#include "../_bgi.h"
|
||||||
|
|
||||||
|
void _gfx_frame_isr(void)
|
||||||
|
{
|
||||||
|
_gfx_frame_tick++;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -17,6 +17,12 @@
|
|||||||
* число попыток (иное железо, бит ведёт себя не как в MAME) —
|
* число попыток (иное железо, бит ведёт себя не как в MAME) —
|
||||||
* откатываемся на одно кадровое прерывание (halt), чтобы не
|
* откатываемся на одно кадровое прерывание (halt), чтобы не
|
||||||
* зависнуть намертво.
|
* зависнуть намертво.
|
||||||
|
*
|
||||||
|
* FPS-ДЕЛИТЕЛЬ (docs/sprite-api-design.md §9е): при _gfx_fps_div>=2
|
||||||
|
* (выставлен gfx_set_fps_div) идём СЧЁТЧИКОВЫМ путём — ждём, пока
|
||||||
|
* фоновый ISR насчитает n кадровых фронтов от прошлого выхода. В
|
||||||
|
* отличие от поллинга это ловит фронты, прошедшие ВО ВРЕМЯ рендера
|
||||||
|
* (elapsed>=n → выравнивание на ближайший фронт, без накопления фазы).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "../_bgi.h"
|
#include "../_bgi.h"
|
||||||
@@ -29,7 +35,22 @@ static void vsync_cleanup(void)
|
|||||||
_cbl_port_unref();
|
_cbl_port_unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gfx_wait_vsync(void)
|
/* EI + HALT: разбудит ЛЮБОЕ прерывание (кадр/клавиатура/CBL), вызывающий
|
||||||
|
* цикл перепроверит счётчик. EI обязателен (как в fallback ниже). */
|
||||||
|
static void gfx_frame_halt(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ei
|
||||||
|
halt
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Лучевой поллинг (n<=1) — выделен в отдельную функцию: её тело
|
||||||
|
* заканчивается непрозрачным __asm-блоком, поэтому в НЕЙ не должно быть
|
||||||
|
* переходов «через asm» (SDCC не эмитит эпилог-метку за таким блоком).
|
||||||
|
* Ранний return делителя живёт в gfx_wait_vsync (чистый C). */
|
||||||
|
static void gfx_wait_vsync_beam(void)
|
||||||
{
|
{
|
||||||
if (!vsync_ready) {
|
if (!vsync_ready) {
|
||||||
vsync_ready = 1;
|
vsync_ready = 1;
|
||||||
@@ -70,3 +91,26 @@ void gfx_wait_vsync(void)
|
|||||||
_gwv_done:
|
_gwv_done:
|
||||||
__endasm;
|
__endasm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gfx_wait_vsync(void)
|
||||||
|
{
|
||||||
|
/* --- счётчиковый путь делителя (n>=2) --- */
|
||||||
|
if (_gfx_fps_div >= 2) {
|
||||||
|
uint8_t n = _gfx_fps_div;
|
||||||
|
if ((uint8_t)(_gfx_frame_tick - _gfx_fps_last) >= n) {
|
||||||
|
/* Слот истёк (рендер занял >= n интервалов) — ближайший
|
||||||
|
* фронт, чтобы не копить фазовую ошибку. */
|
||||||
|
uint8_t t = _gfx_frame_tick;
|
||||||
|
while (_gfx_frame_tick == t)
|
||||||
|
gfx_frame_halt();
|
||||||
|
} else {
|
||||||
|
while ((uint8_t)(_gfx_frame_tick - _gfx_fps_last) < n)
|
||||||
|
gfx_frame_halt();
|
||||||
|
}
|
||||||
|
_gfx_fps_last = _gfx_frame_tick;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- n<=1: обычный лучевой поллинг (поведение бит-в-бит прежнее) --- */
|
||||||
|
gfx_wait_vsync_beam();
|
||||||
|
}
|
||||||
|
|||||||
@@ -105,6 +105,17 @@ void gfx_heal(int x, int y, int w, int h);
|
|||||||
* gfx_set_visible_page(hidden); // tear-free flip */
|
* gfx_set_visible_page(hidden); // tear-free flip */
|
||||||
void gfx_wait_vsync(void);
|
void gfx_wait_vsync(void);
|
||||||
|
|
||||||
|
/* FPS-делитель (frame pacing): логический кадр = РОВНО n кадровых
|
||||||
|
* интервалов, независимо от плавания длительности рендера. n: 1 = 50 fps
|
||||||
|
* (дефолт), 2 = 25, 3 = ~16.7, 4 = 12.5, ...; 0 трактуется как 1.
|
||||||
|
* Меняет ТОЛЬКО поведение gfx_wait_vsync() (call-sites не трогаются):
|
||||||
|
* при n>=2 vsync ждёт n фронтов по фоновому счётчику кадров (ISR),
|
||||||
|
* выравниваясь на ближайший фронт при переполнении слота (без дрейфа);
|
||||||
|
* при n<=1 — обычный лучевой поллинг.
|
||||||
|
* Возврат 0 / -1+errno: EINVAL (неподходящий memory mode — данные не в
|
||||||
|
* W2), ENOMEM (заняты все слоты цепочки кадровых обработчиков). */
|
||||||
|
int gfx_set_fps_div(uint8_t n);
|
||||||
|
|
||||||
/* ---- Bitmap-font text -------------------------------------------- *
|
/* ---- Bitmap-font text -------------------------------------------- *
|
||||||
* Шрифт 256 глифов × 8 рядов × 1 байт (ZX-Spectrum формат), 2 КБ.
|
* Шрифт 256 глифов × 8 рядов × 1 байт (ZX-Spectrum формат), 2 КБ.
|
||||||
* Грузится лениво при первом использовании BGI-текста; gfx_set_font
|
* Грузится лениво при первом использовании BGI-текста; gfx_set_font
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Build fpsdiv.exe — тест FPS-делителя (frame pacing, §9е).
|
||||||
|
|
||||||
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
EXAMPLE := fpsdiv
|
||||||
|
EXTRA_FLAGS ?= --gfx 256
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user