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
5 changes: 4 additions & 1 deletion src/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,10 @@ const CLXDAT_SPRITE_PLAYFIELD_MASK: u16 = 0x01FE;
const CLXDAT_SPRITE_SPRITE_MASK: u16 = 0x7E00;
const BITPLANE_DDF_HARD_START: u16 = 0x0018;
const BITPLANE_DDF_HARD_STOP: u16 = 0x00D8;
const SPRITE_DMA_PAIR_CAPTURE_HPOS: [u32; 4] = [0x018, 0x020, 0x028, 0x030];
/// First DMA slot colour clock of each sprite channel (the POS/DATA word;
/// the CTL/DATB word follows two clocks later). Hardware slot chart (and
/// vAmiga's DAS table): sprite N fetches at $15+4N / $17+4N.
const SPRITE_DMA_SLOT1_HPOS: [u32; 8] = [0x15, 0x19, 0x1D, 0x21, 0x25, 0x29, 0x2D, 0x31];
const NANOS_PER_SECOND: u128 = 1_000_000_000;
const VIDEO_FETCH_TIMING_SAMPLE_RATE: u128 = 128;
const VIDEO_COLLISION_TIMING_SAMPLE_RATE: u128 = 16;
Expand Down
5 changes: 4 additions & 1 deletion src/bus/custom_regs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,10 @@ impl Bus {
self.agnus.set_ersy(val & 0x0002 != 0);
if self.denise.bplcon0 != previous {
self.record_bitplane_bplcon0_write(previous);
self.ddf_seq_record_bplcon0_write(self.denise.bplcon0, previous, 3);
// Agnus's sequencer copy of BPLCON0 updates four colour
// clocks after the write slot (vAmiga DMA_CYCLES(4);
// Denise's own interpretation switches earlier).
self.ddf_seq_record_bplcon0_write(self.denise.bplcon0, previous, 4);
}
false
}
Expand Down
39 changes: 17 additions & 22 deletions src/bus/dma_slots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@ impl Bus {
self.check_ui_beam_traps((old_vpos, old_hpos), old_frame_lines, tick.new_frames);
}
if tick.new_frames == 0 && tick.new_lines == 0 {
self.capture_sprite_dma_words_if_due(old_vpos, old_hpos, self.agnus.hpos);
self.capture_sprite_dma_words_if_due(
old_vpos,
old_hpos,
self.agnus.hpos,
old_emulated_cck,
);
self.capture_bitplane_dma_words_if_due(
old_vpos,
old_hpos,
Expand Down Expand Up @@ -572,36 +577,26 @@ impl Bus {

pub(super) fn sprite_slot_active_at(&self, hpos: u32) -> bool {
// Real OCS sprite DMA fetches only on lines where a sprite is actually
// active (within its vstart..vstop), not on every line. The reserved
// slots map to sprite pairs by `SPRITE_DMA_PAIR_CAPTURE_HPOS`
// (0x18->sprites 0/1, 0x20->2/3, 0x28->4/5, 0x30->6/7), so reserve a
// pair's slot only when one of its sprites is fetching data this line --
// gating on the same `data_dma_active` the renderer uses, so the bus
// model and the captured image agree. Parked/off-screen sprites free
// their slot for the CPU/blitter; previously they were reserved
// unconditionally whenever SPREN was on (~2504 cck/frame of phantom DMA
// stolen from the blitter).
// active (within its vstart..vstop), not on every line. Sprite N owns
// the two odd slots $15+4N and $17+4N (the hardware slot chart /
// vAmiga's DAS table), so reserve them only when that sprite is
// fetching data this line -- gating on the same `data_dma_active` the
// renderer uses, so the bus model and the captured image agree.
// Parked/off-screen sprites free their slots for the CPU/blitter.
if self.agnus.dmacon & DMACON_SPREN == 0 {
return false;
}
// Sprite DMA slots sit on ODD color clocks (same parity as refresh/
// disk/audio -- the HRM chart's fixed-DMA band), so they never block
// the Copper's even-clock fetches. Each active sprite pair reserves
// the two odd slots of its 8-cck band (0x19/0x1B, 0x21/0x23, ...).
if !(0x019..=0x037).contains(&hpos) || hpos & 1 == 0 {
return false;
}
let rel = hpos - 0x019;
if rel % 8 >= 4 {
// the Copper's even-clock fetches.
if !(0x015..=0x033).contains(&hpos) || hpos & 1 == 0 {
return false;
}
let pair = (rel / 8) as usize;
if pair >= 4 {
let sprite = ((hpos - 0x015) / 4) as usize;
if sprite >= 8 {
return false;
}
let first = pair * 2;
self.display_dma_sprite_state[first].data_dma_active
|| self.display_dma_sprite_state[first + 1].data_dma_active
self.display_dma_sprite_state[sprite].data_dma_active
}

pub(super) fn record_bitplane_dmacon_write(&mut self, previous: u16) {
Expand Down
44 changes: 25 additions & 19 deletions src/bus/frame_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ impl Bus {
vpos >= self.current_frame_visible_start_vpos
&& beam_y >= vstart
&& beam_y < vstop
&& hpos >= SPRITE_DMA_PAIR_CAPTURE_HPOS[sprite / 2]
&& hpos >= SPRITE_DMA_SLOT1_HPOS[sprite]
}

pub(super) fn latch_display_sprite_register_data_stream_at(
Expand All @@ -334,7 +334,7 @@ impl Bus {
if beam_y >= frame_lines {
return;
}
let fetch_slot = SPRITE_DMA_PAIR_CAPTURE_HPOS[sprite / 2];
let fetch_slot = SPRITE_DMA_SLOT1_HPOS[sprite];
let stream_start = if hpos >= fetch_slot {
beam_y.saturating_add(1)
} else {
Expand Down Expand Up @@ -470,9 +470,8 @@ impl Bus {
let in_window = beam_y >= control.vstart && beam_y < control.vstop;
let sprite_dma_enabled =
dmacon & (DMACON_DMAEN | DMACON_SPREN) == (DMACON_DMAEN | DMACON_SPREN);
let reaches_current_fetch_slot = beam_y == control.vstart
&& hpos <= SPRITE_DMA_PAIR_CAPTURE_HPOS[sprite / 2]
&& sprite_dma_enabled;
let reaches_current_fetch_slot =
beam_y == control.vstart && hpos <= SPRITE_DMA_SLOT1_HPOS[sprite] && sprite_dma_enabled;
let keep_held_line =
!sprite_dma_enabled && in_window && state.data_dma_active && state.last_line.is_some();
let keep_active_dma_line =
Expand Down Expand Up @@ -545,7 +544,7 @@ impl Bus {
} else {
(beam_y - control.vstart) as u32
};
if beam_y >= control.vstart && hpos > SPRITE_DMA_PAIR_CAPTURE_HPOS[sprite / 2] {
if beam_y >= control.vstart && hpos > SPRITE_DMA_SLOT1_HPOS[sprite] {
line = line.saturating_add(1);
}
let line = if sprite_scan_doubled(self.agnus.fmode()) {
Expand Down Expand Up @@ -748,7 +747,7 @@ impl Bus {
.collect();
let mut idx = 0;
for vpos in 0..display_start {
for (pair, &capture_hpos) in SPRITE_DMA_PAIR_CAPTURE_HPOS.iter().enumerate() {
for (sprite, &capture_hpos) in SPRITE_DMA_SLOT1_HPOS.iter().enumerate() {
while idx < writes.len()
&& (writes[idx].0 < vpos
|| (writes[idx].0 == vpos && writes[idx].1 < capture_hpos))
Expand All @@ -770,7 +769,7 @@ impl Bus {
if self.sprite_dma_inhibited_by_vertical_blank_at(vpos) {
continue;
}
for sprite in pair * 2..pair * 2 + 2 {
{
if sprite_dma_disabled_by_bitplane_ddf(
sprite,
self.agnus.revision(),
Expand Down Expand Up @@ -840,26 +839,21 @@ impl Bus {
vpos: u32,
old_hpos: u32,
new_hpos: u32,
old_emulated_cck: u64,
) {
// No sprite DMA pair slot lies in [old_hpos, new_hpos): nothing below
// can run (the per-pair loop checks the same window), so skip the
// No sprite DMA slot lies in [old_hpos, new_hpos): nothing below can
// run (the per-sprite loop checks the same window), so skip the
// sprite-state scan on the vast majority of beam advances.
if old_hpos > SPRITE_DMA_PAIR_CAPTURE_HPOS[3] || new_hpos <= SPRITE_DMA_PAIR_CAPTURE_HPOS[0]
{
if old_hpos > SPRITE_DMA_SLOT1_HPOS[7] || new_hpos <= SPRITE_DMA_SLOT1_HPOS[0] {
return;
}
if self.sprite_dma_inhibited_by_vertical_blank_at(vpos) {
return;
}
let sprite_dma_enabled =
self.agnus.dmacon & (DMACON_DMAEN | DMACON_SPREN) == (DMACON_DMAEN | DMACON_SPREN);
let sprite_vertical_bar_active = self
.display_dma_sprite_state
.iter()
.any(|state| state.data_dma_active && state.last_line.is_some());
if !sprite_dma_enabled && !sprite_vertical_bar_active {
return;
}
let Some(fb_y) = visible_framebuffer_y(
vpos,
self.current_frame_visible_start_vpos,
Expand All @@ -875,15 +869,27 @@ impl Bus {
let mut fetched_lines = 0usize;
let bitplane_bplcon0 = self.effective_bitplane_bplcon0();
let bitplane_dmacon = self.effective_bitplane_dmacon();
for (pair, &capture_hpos) in SPRITE_DMA_PAIR_CAPTURE_HPOS.iter().enumerate() {
for (sprite, &capture_hpos) in SPRITE_DMA_SLOT1_HPOS.iter().enumerate() {
if old_hpos > capture_hpos || new_hpos <= capture_hpos {
continue;
}
// SPREN is sampled by each sprite's own DMA slot (the sprena/
// sprdis vAmigaTS sweeps step DMACON writes in two-colour-clock
// increments around individual slots), honouring the DMACON
// write commit delay.
let slot_cck =
old_emulated_cck.saturating_add(u64::from(capture_hpos.saturating_sub(old_hpos)));
let sprite_dma_enabled = self.effective_bitplane_dmacon_at(slot_cck)
& (DMACON_DMAEN | DMACON_SPREN)
== (DMACON_DMAEN | DMACON_SPREN);
if !sprite_dma_enabled && !sprite_vertical_bar_active {
continue;
}
if sprite_dma_enabled {
pair_slots += 1;
}
let mut captured_line = false;
for sprite in pair * 2..pair * 2 + 2 {
{
if sprite_dma_disabled_by_bitplane_ddf(
sprite,
self.agnus.revision(),
Expand Down
Loading
Loading