Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/internals/video.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ against the vAmigaTS `Agnus/DIW/OLDDIW/diw1` A500 photos, OCS and ECS).
Early and late single-word lo-res DDF keep the picture beam-anchored;
the renderer must not add or subtract a sample just to
align the picture to a fetch-unit boundary.
Hi-res early DDF is beam-anchored the same way: content fetched ahead of the
window edge is hidden by the window comparator alone (XSysInfo's DDFSTRT `$38`
panel clips exactly its one pre-fetch word), so when an extreme-overscan
screen opens the window early as well (KS 3.2 Overscan editor on ECS:
DDFSTRT `$28` with DIWSTRT h `$5D`), the early words are visible inside the
window rather than being snapped away (issue #186).
When DDFSTRT is late enough that DIW opens before DMA has delivered the
first BPL1DAT word for the row, playfield output remains border-colour until
that plane-0 fetch reaches Denise instead of sampling stale shifter contents.
Expand Down
32 changes: 11 additions & 21 deletions src/video/bitplane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,27 +1342,17 @@ impl ControlState {
// window edge sitting one lo-res pixel too far right, which made a
// standard DIW overrun the fetched row at the right edge on
// early-DDF screens; the picture phase itself was never non-linear.
let mut origin_shift = display_native_shift - ddf_native_shift + clamped_window_native;
// A hi-res/SHRES FMODE=0 screen that starts DDFSTRT earlier than the
// standard $3C slot pre-fetches whole word(s) before the display window
// opens. On real hardware those words are shifted into the left border
// ahead of DIWSTRT and never appear inside the window; the negative
// `ddf_native_shift` is exactly that early pre-fetch width in pixels.
// Snap the picture origin up to the first fully-fetched word so the
// pre-fetch word is clipped on the left AND the real right edge (the
// last visible word) stays inside the window instead of being cropped.
//
// Confirmed against vAmiga for XSysInfo's hardware panel (hi-res,
// DDFSTRT=$38, DIWSTRT=$81, BPL1MOD/BPL2MOD=-4): a clean left edge with
// the >OVERVIEW box keeping its right border. With the negative modulo
// that pre-fetch word equals the previous row's right-edge word, so
// without this the right edge both bled into the left column one
// scanline down and was cropped off the right. Late DDFSTRT (e.g. the
// Kickstart insert-disk screen's $40, `ddf_native_shift >= 0`), lo-res,
// and wide-FMODE fetches are untouched.
if (self.hires() || self.shres()) && self.fetch_quantum() == 1 && ddf_native_shift < 0 {
origin_shift = origin_shift.max(-ddf_native_shift);
}
let origin_shift = display_native_shift - ddf_native_shift + clamped_window_native;
// No extra clipping of early-DDF hi-res pre-fetch words happens here:
// content fetched ahead of the window edge is exactly the positive
// part of `origin_shift`, so the window comparator already hides it
// (XSysInfo's hardware panel: DDFSTRT=$38, DIWSTRT=$81 -> the one
// pre-fetch word is the 16 skipped samples). When the display window
// itself opens left of the standard edge (ECS/AGA extreme-overscan
// screens: DDFSTRT=$28, DIWSTRT h=$5D), those early words ARE inside
// the window and must be shown - an unconditional snap to the early
// fetch width shifted the whole picture left and left a blank
// right-edge band (issue #186).
// A playfield whose BPLCON1 scroll covers an off-grid fetch phase
// catches the floor reload slot instead of the rounded-up one (see
// `reload_advance_for_scroll`): extend the fetch span left by the
Expand Down
38 changes: 30 additions & 8 deletions src/video/bitplane/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,18 +246,16 @@ fn content_window_h_matches_standard_diw_for_stock_ddf() {
}

#[test]
fn early_ddf_hires_origin_snaps_to_word_boundary() {
fn early_ddf_hires_origin_clips_prefetch_against_the_window_edge() {
// XSysInfo's hardware-information panel: hi-res, FMODE=0, DDFSTRT=$38
// (the lo-res "Normal" slot) - one 4-cck fetch word earlier than the
// hi-res standard $3C - with DIWSTRT=$81 and BPL1MOD/BPL2MOD=-4. The
// early pre-fetch word is clocked into the left border before the
// window opens, so the picture origin must snap up to the first
// fully-fetched word: native x-offset 16 (one 16-pixel word) instead of
// the unsnapped 12. That clips the pre-fetch word on the left AND keeps
// the real right edge inside the window. Without it the negative modulo
// made that word both bleed into the left column one scanline down and
// crop off the right edge. Confirmed against vAmiga (clean left edge,
// the >OVERVIEW box keeps its right border).
// window opens: native x-offset 16 (one 16-pixel word) skips it. With
// the negative modulo that word equals the previous row's right-edge
// word; showing it bled that edge into the left column one scanline
// down and cropped it off the right. Confirmed against vAmiga (clean
// left edge, the >OVERVIEW box keeps its right border).
let xsysinfo = ControlState {
agnus_revision: AgnusRevision::AgaAlice,
bplcon0: 0x8200,
Expand Down Expand Up @@ -292,6 +290,30 @@ fn early_ddf_hires_origin_snaps_to_word_boundary() {
..xsysinfo
};
assert_eq!(ks_boot.native_x_offset(ks_boot.diw_h_start(), repeat), 24);

// ECS extreme-overscan hi-res screen (KS 3.2 Overscan editor's edit
// display, issue #186): DDFSTRT=$28 fetches 80 hi-res px ahead of the
// standard slot, but DIWSTRT h=$5D opens the window 72 px early too, so
// nearly all of that early content is INSIDE the window. Only the part
// left of the framebuffer origin is skipped (10 px window clamp plus the
// 8 px between the window edge and the reference): origin 18, not the
// early-fetch width 80. Snapping to 80 shifted the whole picture left
// and left a blank band at the right window edge.
let overscan = ControlState {
agnus_revision: AgnusRevision::Ecs8375,
bplcon0: 0x9200,
diwstrt: 0x1D5D,
diwstop: 0x38C7,
diwhigh: DiwHigh::ecs_explicit(0x2100),
ddfstrt: 0x0028,
ddfstop: 0x00D8,
bplcon1: 0x0044,
..xsysinfo
};
assert_eq!(overscan.diw_h_start(), 0x5D);
// (0x5D - 0x81)*2 display shift, -(0x3C - 0x28)*4 DDF shift, +(0x62 -
// 0x5D)*2 framebuffer-origin clamp = 18.
assert_eq!(overscan.native_x_offset(overscan.diw_h_start(), repeat), 18);
}

fn ocs_snapshot(diwstrt: u16, diwstop: u16, ddfstrt: u16, ddfstop: u16) -> RenderRegisterSnapshot {
Expand Down
Loading