Skip to content

feat(tr): host-DMA double-buffer for tear-free bitmap video on no-REU backends#42

Merged
kfox merged 1 commit into
mainfrom
feat/tr-hostdma-double-buffer
Jun 24, 2026
Merged

feat(tr): host-DMA double-buffer for tear-free bitmap video on no-REU backends#42
kfox merged 1 commit into
mainfrom
feat/tr-hostdma-double-buffer

Conversation

@kfox

@kfox kfox commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Problem

On the TeensyROM, single-buffered mhires video tears — the per-cell "sparkle" — because the per-frame screen/color/bitmap writes are non-atomic and the slow cycle-clean bus DMA lets the VIC scan a half-updated visible bank. Profiling this session showed the bottleneck is the bus DMA, not the link: serial and TCP both sustain ~106 KiB/s, and the TR's emulated REU moves bytes over that same bus DMA — so neither a faster transport nor REU staging can fix it. The U64's use_reu_staged double-buffer isn't available here.

Fix

Host-DMA double-buffering (page flip), no REU. Write each frame's bitmap+screen into the off-screen VIC bank over the normal host-DMA path, then flip $DD00 at vblank via a tiny raster IRQ. The visible bank is never written mid-display → every shown frame is whole, tear-free at the same ~10–12 fps. Because the IRQ does no in-IRQ DMA (unlike the REU handler's ~9000-cycle copy), the swap lands inside vblank with no shimmer — so it also works with text overlays, which the REU path can't claim.

What's here

  • modes.py: HOSTDMA_SWAP_IRQ_HANDLER — a 35-byte minimal handler at $C500 with a 3-byte tracker ([bg0, bank, ready]) at $C700, reusing the existing _install_bank_swap_irq / _uninstall_bank_swap_irq. double_buffer setup/push/teardown on Hires + MultiHires via shared BitmapDisplayMode helpers.
  • config.py: [video].double_buffer tri-state (true | false | "auto", default auto) + resolve_double_buffer. auto enables it for bitmap modes when REU staging is off (mutually exclusive — both flip $DD00) and the backend has no REU at all (the TR); the U64 is left on its existing paths. Threaded through build_scene (api.profile.supports_reu) and the display = "random" slideshow rebuild. force_host_dma (SID-audio scenes) disables it too.
  • c64.py: per-bank RegionIDs so each VIC bank diffs against its own prior content.
  • Example TOML + regenerated JSON schema + CLAUDE.md.

Residual (documented, not over-engineered)

mhires color RAM ($D800) isn't VIC-banked, so the c3 slot still tears in a brief (~9 ms) window before each flip; bitmap+screen (the structure + c1/c2) go tear-free. Hires (no color RAM) and static-palette mhires (cheap/grayscale) are fully tear-free.

Verification

  • New tests: resolve_double_buffer truth table + load-time validation (tests/test_config.py); structure tests for setup/push/teardown on the off-screen bank + 3-byte tracker (tests/test_bitmap_compose.py).
  • Full gate green: ruff, mypy --strict, pyright, 1581 tests; schema drift clean.
  • HW-verified on TeensyROM over serial: visibly smoother playback, no errors; log confirms the host-DMA double-buffer armed path resolved.

Independent of (and composes with) the bg0-hysteresis fix in the separate PR.

… backends

The TeensyROM's cycle-clean bus DMA can't rewrite a full mhires frame in the
visible VIC bank without tearing — the per-cell "sparkle." Profiling confirmed
it's the bus, not the link (serial and TCP both ~106 KiB/s), so a faster
transport doesn't help; the emulated REU moves bytes over the SAME bus DMA, so
REU staging can't help either. The fix that needs no REU: write each frame's
bitmap+screen into the OFF-screen VIC bank over the normal host-DMA path, then
flip $DD00 at vblank via a tiny raster IRQ. The visible bank is never written
mid-display, so every shown frame is whole — tear-free at the same ~10-12 fps.

- modes.py: HOSTDMA_SWAP_IRQ_HANDLER (35-byte minimal handler at $C500 + 3-byte
  tracker at $C700), reusing the existing _install/_uninstall_bank_swap_irq;
  double_buffer setup/push/teardown on Hires + MultiHires via shared base
  helpers. Unlike the REU path the IRQ does NO in-IRQ DMA, so the swap lands
  inside vblank with no shimmer — folded text overlays render crisply.
- config.py: [video].double_buffer tri-state (true|false|"auto", default auto)
  + resolve_double_buffer. auto enables it for bitmap modes when REU staging is
  off (mutually exclusive — both flip $DD00) AND the backend has no REU at all
  (the TR); the U64 is left untouched. Threaded through build_scene
  (api.profile.supports_reu) and the slideshow random-mode rebuild.
- c64.py: per-bank RegionIDs so each VIC bank diffs against its own content.

Residual: mhires color RAM ($D800) is not VIC-banked, so the c3 slot still
tears briefly before each flip; bitmap+screen (structure + c1/c2) go tear-free.
Hires (no color RAM) and static-palette mhires (cheap/grayscale) are fully
tear-free.

HW-verified on TeensyROM over serial: visibly smoother playback, no errors.
@kfox kfox merged commit 9368258 into main Jun 24, 2026
6 checks passed
@kfox kfox deleted the feat/tr-hostdma-double-buffer branch June 24, 2026 16:41
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.80519% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.68%. Comparing base (29487b0) to head (e50b415).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
c64cast/config.py 86.95% 2 Missing and 1 partial ⚠️
c64cast/scenes.py 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #42      +/-   ##
==========================================
+ Coverage   79.57%   79.68%   +0.11%     
==========================================
  Files          68       68              
  Lines       12861    12941      +80     
  Branches     1898     1909      +11     
==========================================
+ Hits        10234    10312      +78     
- Misses       2188     2190       +2     
  Partials      439      439              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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