Skip to content

blitter: narrow the BLTPRI BLS fence; denise: scroll-aware off-grid DDFSTRT placement#166

Merged
LinuxJedi merged 2 commits into
mainfrom
fix/bltpri-warmup-fence
Jul 11, 2026
Merged

blitter: narrow the BLTPRI BLS fence; denise: scroll-aware off-grid DDFSTRT placement#166
LinuxJedi merged 2 commits into
mainfrom
fix/bltpri-warmup-fence

Conversation

@LinuxJedi

@LinuxJedi LinuxJedi commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Two regression fixes for demo-scene display/timing behavior, both bisected from save states and verified against vAmiga/FS-UAE references.

1. BLTPRI BLS fence narrowed to the blit's warm-up window

PR #162's whole-blit BLS fence (BLTPRI holds the CPU off the chip bus until BLTDONE, bus-free micro-cycles included) fixed Jim Power's MFM-decode trackloader but overshot the hardware. Bisected from a save state: straight after the Rampage "present" scene, the overlay text vanished in a metronomic 4-frame flicker and the music tick dropped to 44-45 of 50 vblanks (~12% tempo slowdown). The scene runs ~55 line blits per frame under BLTPRI, and line mode leaves 2 of its 4 cycles per pixel bus-free (Bresenham cycles) -- CPU time the demo's frame handler depends on. Neither vAmiga nor FS-UAE shows the slowdown: both arbitrate per slot and have no whole-blit fence.

With BLTPRI set, the CPU is now fenced only through the blit's warm-up window: the startup ladder (register commit, BLT_STRT1/2, Init) and, for D-writing blits, the first word's body cycles including the empty first-D bubble. That is all the trackloader ordering needs -- the restore write and its prefetches queue behind the fenced startup/first-D holes and land after the blit -- while primed bus-free micro-cycles (line Bresenham, fill idle, disabled-channel gaps) return to the CPU.

Verification:

2. Scroll-aware FMODE=0 off-grid DDFSTRT placement

Bisected from a save state (PR #107 good, PR #109 first bad): Rampage's dot-cube part jumped ~16 lo-res px a few times a second instead of panning smoothly. The demo pans by walking DDFSTRT $66->$68->...->$6E against a BPLCON1 fine-scroll wrap $FF->$00 with the bitplane pointers on a constant ring stride; PR #109's unconditional round-up-to-reload-grid placement makes the wrap frame not move.

The hardware rule is scroll-aware, established with two new probes (timing-test/ddfprobe-phase.asm, ddfprobe-phase2.asm: copper re-anchors the same marker bitmap every line while walking (DDFSTRT, BPLCON1) per band) run against vAmiga: an off-grid fetch whose phase lateness is covered by the playfield's BPLCON1 delay (lateness <= S) catches its own floor reload slot, one full gulp left of the rounded-up origin. vAmiga: ($66, S=15) sits exactly 1 lo-res px left of ($68, S=0) -- the smooth pair -- while ($66, S=0) rounds up, on-grid starts never move, and the arosddf1 $3C photo case is unchanged. Copperline now matches vAmiga on every band of both probes.

Implementation (src/video/bitplane.rs): reload_advance_for_scroll / row_reload_advance extend the row's fetch span left by the largest per-playfield advance; sample_delay_for_plane rebases each plane's (now signed) sample delay against it. Scroll-0 rendering is bit-identical by construction: no golden re-bless needed.

Verification:

  • Rampage dot cube (save-state oracle): frame-centroid jump count back to the pre-agnus: model the DDF sequencer start/stop flip-flops for FMODE=0 fetches #109 baseline (34 -> 2, matching the demo's own scene cuts).
  • Rampage "present" cadence and Jim Power screenshots unchanged.
  • New unit test covers covered/uncovered/split-scroll/on-grid cases, including the OCS (4-cck) vs ECS (2-cck) DDFSTRT masking difference.

Full cargo test --release (unit + golden probe suite), cargo clippy, cargo fmt --check clean on both commits.

The whole-blit BLS fence (BLTPRI holds the CPU off the chip bus until
BLTDONE, bus-free micro-cycles included) fixed Jim Power's MFM-decode
trackloader but overshot the hardware: line-heavy demo main loops lost
the CPU time real Agnus leaves them in the line pipeline's bus-free
Bresenham cycles. Rampage's vector parts run ~55 line blits per frame
under BLTPRI; with 2 of the 4 cycles per pixel fenced off, the per-frame
handler overran every 4th frame -- the "present" overlay vanished in a
metronomic 4-frame flicker and the music tick dropped to 44-45 of 50
vblanks (~12% tempo slowdown). Neither vAmiga nor FS-UAE shows this:
both arbitrate per slot and have no whole-blit fence (vAmiga's BUSIDLE
and BLT_STRT micro-cycles never allocate the bus).

With BLTPRI set, the CPU is now fenced only through the blit's warm-up:
the startup ladder (register commit, BLT_STRT1/2, Init) and, for
D-writing blits, the first word's body cycles including the empty
first-D bubble, while the sequencer's bus request is held asserted by
its queued back-to-back first fetches. That is all the trackloader
ordering needs -- the restore write and its prefetches queue behind the
fenced startup/first-D holes and land after the blit -- while primed
bus-free micro-cycles (line Bresenham, fill idle, disabled-channel
gaps) return to the CPU.

Verified from save states on both regression titles: Jim Power loads
level 1 (guru 00000003 with the holes open), and Rampage's frame
cadence and music tick match the pre-fence run exactly.

- bltpri_stalls_cpu_chip_access_through_blitter_access_cycles now
  encodes the trackloader property: the first-D bubble is denied and
  the CPU first wins the internal E cycle after BBUSY drops (4 cck).
- blithog_set_blocks_cpu_slowdown_back_pressure_until_blitter_finishes
  returns to 14 cck: a source-only blit has no bus-free cycles, and the
  CPU rides the terminal E/F clocks as before.
- New bltpri_line_blit_bresenham_cycles_stay_cpu_available_after_warmup
  guards the line-mode property.
- timing-test golden row 26 re-blessed 25161 -> 25089 (0x6201), within
  8 cck of the FS-UAE 25095 / vAmiga 25097 references (pre-fence was
  25073/-22; the whole-blit fence read +66). Row 27 shifts a few cck as
  its poll-catch phase rides on row 26's completion position.
- docs/internals/timing.md and timing-test/README.md updated to
  describe the warm-up fence.
… reload slot

The FMODE=0 placement quantization (an off-grid fetch waits for the NEXT
shifter reload slot) is only correct for BPLCON1 = 0. The playfield's
scroll taps the shifter S pixels late, so when the scroll covers the
fetch's phase lateness (phase * px-per-cck <= S, tie covers), the data
catches its own floor slot and the picture sits one full gulp left of
the rounded-up origin.

vAmiga-verified with two new probes (timing-test/ddfprobe-phase.asm,
ddfprobe-phase2.asm): copper re-anchors the same marker bitmap every
line while walking (DDFSTRT, BPLCON1) per band. On A500_ECS_1MB, lo-res
DDFSTRT $66 with scroll 15 sits exactly 1 lo-res px left of $68 with
scroll 0, while $66 with scroll 0 rounds up to the $68 slot; on-grid
starts never move, and the arosddf1 $3C photo case is unchanged.
Copperline now matches vAmiga on every band of both probes.

Regression example (bisected: PR #107 good, PR #109 first bad): the
Rampage dot-cube part pans by walking DDFSTRT $66->$68->...->$6E against
a BPLCON1 wrap $FF->$00, bitplane pointers on a constant ring stride.
The unconditional round-up made the wrap frame not move, so the picture
jumped ~16 lo-res px a few times a second; with the covered-phase
advance the pan is smooth again (frame-centroid jump count back to the
pre-#109 baseline from the same save state).

Implementation: reload_advance_for_scroll / row_reload_advance extend
the row's fetch span left by the largest per-playfield advance, and
sample_delay_for_plane rebases each plane's (now signed) sample delay
against it, so covered planes land one gulp early while uncovered
planes and every scroll-0 row render bit-identically to before (no
golden re-bless needed). Note OCS masks DDFSTRT to 4-cck resolution
(0xFC) vs ECS 2-cck (0xFE), so the phase differs per Agnus revision.
@LinuxJedi LinuxJedi changed the title blitter: narrow the BLTPRI BLS fence to the blit's warm-up window blitter: narrow the BLTPRI BLS fence; denise: scroll-aware off-grid DDFSTRT placement Jul 11, 2026
@LinuxJedi LinuxJedi merged commit 26caea5 into main Jul 11, 2026
8 checks passed
@LinuxJedi LinuxJedi deleted the fix/bltpri-warmup-fence branch July 11, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant