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
14 changes: 7 additions & 7 deletions docs/internals/video.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ visible output one native sample into the fetched stream, so replay
pre-advances that hidden sample before painting the DIW edge. Extra fetch
groups from an earlier DDFSTRT are not decoded into the HAM hold colour before
DIW opens; they are fetched by Agnus, but the first visible HAM history is
bounded to the display-phase samples. Single-word lo-res fetches that start
before the standard `$38` DDF slot expose complete 16-pixel groups; the
standard one-sample phase bias is trimmed when it would push a standard-width
DIW past the completed early-DDF row at the right edge.
Late single-word lo-res DDF keeps the standard DIW `$81` one-sample phase;
the renderer must not subtract an extra sample just to align the clipped
start to a fetch-unit boundary.
bounded to the display-phase samples. Single-word lo-res fetch placement is linear in DDFSTRT: each 8-cck fetch
period before the standard `$38` slot moves the picture exactly 16 lo-res
pixels left, keeping the standard one-sample phase bias (hardware-verified
against the vAmigaTS `Agnus/DIW/OLDDIW/diw1` A500 photos, OCS and ECS).
Early and late single-word lo-res DDF both keep the standard DIW `$81`
one-sample phase; the renderer must not add or subtract a sample just to
align the picture to a fetch-unit boundary.
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
25 changes: 10 additions & 15 deletions src/video/bitplane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,22 +1200,17 @@ impl ControlState {
// and lost its right edge off the framebuffer.
let clamped_window_native =
((DIW_HSTART_FB0 - diw_h_start as i32).max(0) * 2) / pixel_repeat as i32;
// Lo-res FMODE=0 placement is linear in DDFSTRT: moving DDFSTRT one
// 8-cck fetch period earlier moves the picture exactly 16 lo-res
// pixels left. Real hardware confirms this (vAmigaTS
// Agnus/DIW/OLDDIW/diw1 A500 photos: the DDF-$30 stripe grid sits
// exactly 16 lo-res pixels left of the standard $38 grid, and the
// window-edge staircase steps pair on that grid). An earlier
// "-1 sample" phase correction here compensated for the display
// 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;
let ddf_start = effective_ddf_start_hpos(
self.agnus_revision,
self.hires() || self.shres(),
self.ddfstrt,
);
if !self.hires() && !self.shres() && self.fetch_quantum() == 1 {
let ddf = i32::from(ddf_start);
if ddf < standard_ddf && origin_shift > 0 {
// Single-word lo-res fetches that start before the standard $38
// slot expose whole 16-pixel groups. The standard one-sample
// lo-res phase bias must not push a standard-width DIW one sample
// past that completed early-DDF row at the right edge.
origin_shift -= 1;
}
}
// 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
Expand Down
40 changes: 36 additions & 4 deletions src/video/bitplane/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1133,9 +1133,38 @@ fn native_x_offset_accounts_for_diw_and_ddf_alignment() {
lores_early_fetch_standard_window.words_per_row(false, 0),
21
);
// Linear DDFSTRT placement: $30 fetches one 8-cck period (16 lo-res
// pixels) earlier than the standard $38, on top of the standard
// one-sample lo-res phase bias. Hardware-verified via the vAmigaTS
// Agnus/DIW/OLDDIW/diw1 A500 photos (OCS and ECS).
assert_eq!(
lores_early_fetch_standard_window.native_x_offset(false, 2),
16
17
);
}

#[test]
fn lores_early_ddf_picture_sits_linearly_left_of_standard_ddf() {
// A lo-res FMODE=0 picture fetched at DDFSTRT $30 sits exactly 16 lo-res
// pixels (one 8-cck fetch period) left of the same picture fetched at
// $38; the DDF->position mapping is linear. Real hardware confirms
// (vAmigaTS Agnus/DIW/OLDDIW/diw1 photos: DDF-$30 stripe grid exactly
// one fetch period left of the standard grid).
let standard = RenderState {
bplcon0: 0,
diwstrt: ((PAL_VISIBLE_LINE0 as u16) << 8) | STANDARD_DIW_HSTART as u16,
ddfstrt: 0x0038,
ddfstop: 0x00D0,
..blank_state()
};
let early = RenderState {
ddfstrt: 0x0030,
..standard
};
assert_eq!(
early.native_x_offset(false, 2) - standard.native_x_offset(false, 2),
16,
"DDFSTRT $30 must sit exactly 16 lo-res px left of $38"
);
}

Expand Down Expand Up @@ -5041,8 +5070,8 @@ fn planned_ham_dma_advances_hold_through_edge_fetch_phase() {
fn planned_ham_dma_ignores_extra_early_ddf_history_before_diw() {
let mut row_words = vec![vec![0; 2]; 6];
row_words[0][0] |= 0x8000; // native x 0: direct palette entry 1
row_words[4][0] |= 0x0001; // native x 15: HAM blue := 0
row_words[4][1] |= 0x8000; // native x 16: HAM blue := 0
row_words[4][1] |= 0x8000; // native x 16: HAM blue := 0 (hidden, pre-DIW)
row_words[4][1] |= 0x4000; // native x 17: HAM blue := 0 (first visible)
let line_plan = DenisePlannedPlayfieldLine::new(0, 64, 66, &row_words, 32);
let mut control = visible_lowres_control(0x6800);
control.diwstrt = ((PAL_VISIBLE_LINE0 as u16) << 8) | STANDARD_DIW_HSTART as u16;
Expand Down Expand Up @@ -5077,7 +5106,10 @@ fn planned_ham_dma_ignores_extra_early_ddf_history_before_diw() {
0,
);

assert_eq!(control.native_x_offset(control.diw_h_start(), 2), 16);
// 17 = one 8-cck fetch period (16 lo-res px) before the standard $38
// origin plus the standard one-sample phase bias: DDFSTRT placement is
// linear (hardware-verified, vAmigaTS Agnus/DIW/OLDDIW/diw1 photos).
assert_eq!(control.native_x_offset(control.diw_h_start(), 2), 17);
assert_eq!(fb[64], rgb12_to_rgba8(0x0000));
assert_eq!(fb[65], rgb12_to_rgba8(0x0000));
assert_eq!(&playfield_mask[64..66], &[0x02, 0x02]);
Expand Down
Loading