video: correct programmable-scan (31 kHz VARBEAMEN) display placement and presentation#247
Merged
Merged
Conversation
Under BEAMCON0 VARBEAMEN the horizontal comparators were evaluated with the standard 15 kHz counter phase (H_COUNTER_LINE_ORIGIN at the line start). The DIW values 31 kHz modes program only make sense against a counter that restarts at 0 with the programmable line: Linux/m68k amifb opens its display window at $45 against a programmed HSYNC of $0C..$1A, which is the documented back porch under a zero origin but would open the window inside the sync pulse under the standard phase - and the window comparator then matched off the left canvas edge, so the fb console lost its leading column and sat pinned to the canvas edge with no border. The KS3.1 DblPAL screen's DIW $5B places its picture flush under the same zero origin, which accounts for the last ~14 px of its long-standing left-edge offset against the FS-UAE reference. A render-scoped comparator-origin shift (set from the frame geometry like the debug layer masks) now moves every comparator-anchored position - the DIW window flip-flop scan and sprite HSTART matches - by H_COUNTER_LINE_ORIGIN on programmable frames. Beam-anchored positions (fetch slots, register-write landings, colour-write landings) are physical line positions and do not move. Standard scans use shift 0 and render byte-identically: the probe-golden suite and the standard image-regression baselines pass unchanged. The wide-FMODE SHRES fetch-to-display phase on these modes still shows a residual (leading fetch gulps of a row are discarded where real silicon buffers them across the window opening); that calibration is separate and under investigation.
fetch_origin_native_shift converts DIW H units (lo-res pixels) into native samples with *2/pixel_repeat, which is right for lo-res (one sample per unit) and hi-res (two) but half the real factor in super-hi-res, where a native sample is half a framebuffer pixel and one H unit spans four samples. The DDF-derived term already used the correct per-resolution scale, so any screen whose DIW sits away from the standard fetch reference had its SHRES picture placed at half the intended offset. Every calibrated case was immune: lo-res and hi-res scale correctly, and standard-DIW SHRES screens zero the term. Linux/m68k amifb's 31 kHz console is the first decoupled case: DIW $45 with DDF $18 came out 194 native px left of the window, hiding the first 24 bytes of every row - the console lost its leading ~22 characters on every line (the BPLCON1 scroll of 19 covers the difference). With the unit fixed the picture sits flush with the window and the console is complete. The clamped-window correction now measures against the frame's active comparator anchor instead of the fixed standard one, so a programmable scan whose window is on-canvas no longer gets a spurious off-canvas clip compensation; standard frames use the same anchor as before. Verified: full workspace suite (probe-golden renders byte-identical), image-regression baselines including DblPAL, and Inside The Machine screenshots byte-identical to main at 45s and 90s.
A programmable (VARBEAMEN) frame was presented by mapping the whole HTOTAL line onto the glass time-linearly from the capture aperture's left edge, which pinned the picture toward the left and left the sync and blanking interval as a black band on the right. A multisync monitor locks its horizontal deflection to the sync pulse: the visible raster runs from the HSYNC trailing edge to the next pulse, and the picture sits wherever the mode's own porches place it. When the mode programs its sync generator (VARHSYEN with a sane HSSTRT..HSSTOP), the presentation now resamples that interval onto the glass through a windowed variant of the row stretch; the source origin may sit left of the captured aperture, clamping to the border columns. Modes that leave sync unprogrammed keep the previous time-linear whole-line map, and standard scans are untouched. The Linux amifb console gains its proper left border and porch-set centring; the DblPAL structural regression and the full suite pass unchanged. Also wraps three overlong lines from the previous two commits that had escaped rustfmt; docs/internals/video.md gains the programmable-scan counter-origin and presentation model.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes multiple issues in Copperline’s programmable-scan (VARBEAMEN / 31 kHz) video path so that unusual ECS/AGA display modes (notably Linux/m68k amifb on A4000/AGA) render with correct horizontal placement and monitor-like presentation, while keeping standard 15 kHz paths byte-identical.
Changes:
- Adjust comparator-anchored horizontal positioning for programmable scans by introducing an “active canvas shift” and applying it to DIW/sprite comparator mappings.
- Correct SHRES DIW-relative placement scaling so DIW/DDF decoupling in super-hi-res modes produces the intended offsets.
- Present programmable scans like a multisync monitor by anchoring the visible interval to the programmed HSYNC pulse (with a fallback to time-linear whole-line mapping when sync isn’t programmed), including a new windowed horizontal resampler.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/video/window/present.rs | Passes per-frame presentation window into the common post-processing pipeline. |
| src/video/window.rs | Updates frontend presentation call sites to supply the programmable-scan presentation window. |
| src/video/present_common.rs | Adds programmable-scan sync-anchored presentation path (windowed horizontal resample). |
| src/video/bitplane/sprite.rs | Applies programmable-scan comparator-origin shift to sprite horizontal placement. |
| src/video/bitplane.rs | Implements active comparator-origin shift for programmable scans and fixes SHRES DIW-relative scaling. |
| src/screenshot.rs | Adds stretch_rows_x_window for sync-anchored, windowed horizontal resampling. |
| src/cpu.rs | Applies the sync-anchored programmable-scan presentation in headless/screenshot output. |
| src/chipset/agnus.rs | Exposes the programmed HSYNC window for VARBEAMEN+VARHSYEN modes. |
| src/bus.rs | Computes a programmable-scan presentation window for consumers (renderer/frontend/headless). |
| src/bin/bench.rs | Updates benchmark presentation pipeline to pass the presentation window. |
| docs/internals/video.md | Documents programmable-scan comparator origin and sync-anchored presentation model. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The sync-anchored presentation window was computed from the live Agnus sync latches at present time while the geometry it pairs with is latched at the frame wrap, so sync registers rewritten after the wrap could present the previous frame through the next frame's window (a one-frame horizontal jump in programmable modes). The window is now computed once at the wrap beside the frame geometry, promoted to the last-frame slot with it, recomputed after a state load, and carried as a serde-skipped presentation cache so the save-state layout is unchanged. Also passes the new presentation-window argument in the browser frontend's present call, which the desktop-focused sweep missed and which broke the wasm32 CI check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Linux/m68k framebuffer console (amifb on an A4000/AGA, following up on #246) was unreadable: every line lost its leading ~22 characters and the picture sat pinned to the left of the display with a black band on the right. amifb programs a display no Amiga software ever exercised before -- a 31 kHz programmable scan (VARBEAMEN, HTOTAL=113) in super-hi-res with FMODE=3, DIW $45 against DDF $18 -- and it flushed out three independent defects in the programmable-scan display path. Three commits:
video: restart the Denise H counter at 0 on programmable scans
Under VARBEAMEN the horizontal comparators were evaluated with the standard 15 kHz counter phase (origin $24 at the line start). The DIW values 31 kHz modes program only make sense against a counter that restarts at 0 with the programmable line: amifb opens its window at $45 against a programmed HSYNC of $0C..$1A -- the documented back porch under a zero origin, but inside the sync pulse under the standard phase -- and the KS3.1 DblPAL screen's DIW $5B agrees (this also accounts for the last ~14 px of its long-standing left-edge offset against the FS-UAE reference). A render-scoped comparator-origin shift now moves every comparator-anchored position (DIW window flip-flop scan, sprite HSTART matches) on programmable frames; beam-anchored positions (fetch slots, register-write landings) are physical line positions and do not move. Standard scans use shift 0 and render byte-identically.
video: scale DIW-relative placement terms for super-hi-res
fetch_origin_native_shiftconverts DIW H units into native samples with*2/pixel_repeat-- right for lo-res and hi-res, but half the real factor in super-hi-res (four native samples per H unit). The DDF-derived term in the same expression already scaled per resolution, so any SHRES screen whose DIW sits away from the standard fetch reference placed its picture at half the intended offset. Every previously calibrated case was immune (lo-res/hi-res scale correctly; standard-DIW SHRES zeroes the term); amifb's decoupled DIW/DDF put the console 194 native px left of the window and hid the first 24 bytes of every row. The clamped-window correction also measures against the frame's active comparator anchor now instead of the fixed standard one.video: anchor the programmable-scan presentation at the sync pulse
Programmable frames were presented by mapping the whole HTOTAL line onto the glass from the capture aperture's left edge. A multisync monitor locks its deflection to the sync pulse: when the mode programs its sync generator (VARHSYEN), the glass now shows the line from the HSYNC trailing edge to the next pulse through a windowed row resample, so the picture sits where the mode's own porches place it. Modes without programmed sync keep the previous time-linear map; standard scans are untouched.
The investigation cross-checked WinUAE's model (drawing.cpp's BPLCON1 composition and delayoffset) for the scroll decode and placement semantics, and verified at each step against guest ground truth (the kernel's vc text buffer and a direct bitplane decode of chip RAM, both holding complete lines while the display truncated them).
Verification
ocs_bpu7_hamhost-flaky perf test, failing identically on clean main).debian-uae login:prompt.cargo clippyclean, touched files rustfmt-clean;docs/internals/video.mdupdated with the counter-origin and presentation model.