Timings work on all four platforms.

This commit is contained in:
Scott Duensing 2026-05-01 01:19:43 -05:00
parent 91fcd49f6f
commit 2eaa16a815
3 changed files with 88 additions and 22 deletions

View file

@ -24,6 +24,8 @@
#include <string.h>
#include <exec/types.h>
#include <exec/interrupts.h>
#include <hardware/intbits.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <graphics/copper.h>
@ -45,6 +47,13 @@
extern struct Custom custom;
// Frame-counter VBL server lives at end of file; forward-declare so
// halInit / halShutdown can install / remove it without C inferring
// implicit non-static linkage at the call sites.
static void installVblServer(void);
static void removeVblServer(void);
// ----- Constants -----
#define AMIGA_BITPLANES 4
@ -444,6 +453,7 @@ bool halInit(const JoeyConfigT *config) {
// do nothing -- their palette[0] writes the same COLOR00 once the
// first LoadRGB4 fires from uploadScbAndPalette.
SetRGB4(&gScreen->ViewPort, 0, 0, 0, 0);
installVblServer();
return true;
}
@ -509,26 +519,55 @@ void halWaitVBL(void) {
}
// VPOSR ($DFF004) upper byte: low 3 bits = vertical scanline bits
// 8..10. The bit-8 transition from 1 -> 0 marks "vertical wrap" --
// a fresh frame. Edge-detected per call so caller (UBER, etc.)
// just polls; no IRQ server needed.
#define AMIGA_VPOSR ((volatile uint16_t *)0xDFF004UL)
// Frame counter via a VBL interrupt server (AddIntServer on
// INTB_VERTB). Polling VPOSR/VHPOSR is unreliable across chipsets
// and OS versions -- the bit positions vary and the polling rate has
// to win against the high-bit window per frame, which it doesn't
// when the caller's loop body is long. The interrupt server fires
// exactly once per VBlank regardless of caller cadence.
//
// halFrameCount just reads the volatile counter -- no edge detection
// needed in the polling path.
static volatile uint16_t gFrameCount = 0;
static struct Interrupt gVblIntServer;
static bool gVblInstalled = false;
// Server protocol: called by the interrupt dispatcher with A1 =
// is_Data, A6 = ExecBase. __saveds gives us the libnix data base in
// A4 so we can reference gFrameCount through the small-data ABI.
// Return Z=0 (non-zero result) to keep the chain going so other
// VBL servers further down the priority list still fire.
static __saveds ULONG vblServer(void) {
gFrameCount = (uint16_t)(gFrameCount + 1u);
return 0;
}
static void installVblServer(void) {
if (gVblInstalled) {
return;
}
gVblIntServer.is_Node.ln_Type = NT_INTERRUPT;
gVblIntServer.is_Node.ln_Pri = -60;
gVblIntServer.is_Node.ln_Name = (char *)"joeyFrameCount";
gVblIntServer.is_Data = NULL;
gVblIntServer.is_Code = (void (*)())vblServer;
AddIntServer(INTB_VERTB, &gVblIntServer);
gVblInstalled = true;
}
static void removeVblServer(void) {
if (!gVblInstalled) {
return;
}
RemIntServer(INTB_VERTB, &gVblIntServer);
gVblInstalled = false;
}
static uint16_t gFrameCount = 0;
static uint8_t gPrevVbHi = 0;
uint16_t halFrameCount(void) {
uint8_t now;
/* Bit 0 of the upper byte = scanline bit 8. PAL frame is ~313
* lines, NTSC ~263 -- both wrap bit 8 once per frame, which is
* what we want as the "frame edge" signal. */
now = (uint8_t)((*AMIGA_VPOSR >> 8) & 1u);
if (gPrevVbHi && !now) {
gFrameCount++;
}
gPrevVbHi = now;
return gFrameCount;
}
@ -541,6 +580,9 @@ uint16_t halFrameHz(void) {
void halShutdown(void) {
// Tear down the VBL server before closing the screen so the
// interrupt chain is clean if anything else is watching.
removeVblServer();
if (gScreen != NULL) {
// CloseScreen should free attached UCopList, but be explicit
// to catch any case where the screen close path skips it.

View file

@ -323,15 +323,28 @@ void halWaitVBL(void) {
// caller (UBER, animation loops) polls fast enough that we never
// miss a VBL transition. No IRQ involvement; safe in the S16 takeover
// context where ToolBox interrupt setup would be intrusive.
//
// gFrameCount uses an explicit lda+adc+sta read-modify-write rather
// than `gFrameCount++` because ORCA-C lowers the post-increment to
// `inc |gFrameCount` (the only INC abs form on 65816 -- there is no
// INC long-abs). With this file in the DRAWPRIMS load segment but
// halFrameCount called from CORESYS via JSL, DBR isn't pointing at
// DRAWPRIMS's data bank, so the abs INC silently mutates the wrong
// byte and the counter never advances. The explicit lda > / sta >
// pattern uses long-mode addressing throughout, which is
// DBR-independent.
static uint16_t gFrameCount = 0;
static uint8_t gPrevInVbl = 0;
uint16_t halFrameCount(void) {
uint8_t now;
uint8_t now;
uint16_t cnt;
now = (*IIGS_VBL_STATUS & VBL_BAR_BIT) == 0;
if (now && !gPrevInVbl) {
gFrameCount++;
cnt = gFrameCount;
cnt = (uint16_t)(cnt + 1u);
gFrameCount = cnt;
}
gPrevInVbl = now;
return gFrameCount;

View file

@ -322,11 +322,22 @@ static void pollJoystick(void) {
// Update auto-disconnect counter. Both axes failing => probably no
// stick. One resolves => stick is present, reset the counter.
//
// gJoyConsecutiveTimeouts uses a local-var read-modify-write rather
// than `++`. ORCA-C lowers `++` to `inc abs` (the only INC abs form
// on 65816), which depends on DBR pointing at this static's bank.
// Cross-segment JSL doesn't update DBR, so a caller in a different
// load segment would silently mutate the wrong byte. Long-mode
// lda+sta is DBR-independent.
if (!xResolved && !yResolved) {
if (gJoyConsecutiveTimeouts < 0xFFFFu) {
gJoyConsecutiveTimeouts++;
uint16_t timeouts;
timeouts = gJoyConsecutiveTimeouts;
if (timeouts < 0xFFFFu) {
timeouts = (uint16_t)(timeouts + 1u);
gJoyConsecutiveTimeouts = timeouts;
}
if (gJoyConsecutiveTimeouts >= JOY_DISCONNECT_THRESHOLD) {
if (timeouts >= JOY_DISCONNECT_THRESHOLD) {
gJoyDisconnectLatched = true;
}
gJoyAxisX[JOYSTICK_0] = 0;