2021-11-26 20:30:31 +00:00
|
|
|
|
//
|
|
|
|
|
// Sprites.cpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 26/11/2021.
|
|
|
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "Sprites.hpp"
|
|
|
|
|
|
2022-07-20 01:40:16 +00:00
|
|
|
|
#include <cassert>
|
|
|
|
|
|
2021-11-26 20:30:31 +00:00
|
|
|
|
using namespace Amiga;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
/// Expands @c source from b15 ... b0 to 000b15 ... 000b0.
|
|
|
|
|
constexpr uint64_t expand_sprite_word(uint16_t source) {
|
|
|
|
|
uint64_t result = source;
|
|
|
|
|
result = (result | (result << 24)) & 0x0000'00ff'0000'00ff;
|
|
|
|
|
result = (result | (result << 12)) & 0x000f'000f'000f'000f;
|
|
|
|
|
result = (result | (result << 6)) & 0x0303'0303'0303'0303;
|
|
|
|
|
result = (result | (result << 3)) & 0x1111'1111'1111'1111;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A very small selection of test cases.
|
|
|
|
|
static_assert(expand_sprite_word(0xffff) == 0x11'11'11'11'11'11'11'11);
|
|
|
|
|
static_assert(expand_sprite_word(0x5555) == 0x01'01'01'01'01'01'01'01);
|
|
|
|
|
static_assert(expand_sprite_word(0xaaaa) == 0x10'10'10'10'10'10'10'10);
|
|
|
|
|
static_assert(expand_sprite_word(0x0000) == 0x00'00'00'00'00'00'00'00);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Sprites.
|
|
|
|
|
|
|
|
|
|
void Sprite::set_start_position(uint16_t value) {
|
2022-07-19 20:19:19 +00:00
|
|
|
|
// b8–b15: low 8 bits of VSTART;
|
|
|
|
|
// b0–b7: high 8 bits of HSTART.
|
2021-11-26 20:30:31 +00:00
|
|
|
|
v_start_ = (v_start_ & 0xff00) | (value >> 8);
|
|
|
|
|
h_start = uint16_t((h_start & 0x0001) | ((value & 0xff) << 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Sprite::set_stop_and_control(uint16_t value) {
|
2022-07-19 20:19:19 +00:00
|
|
|
|
// b8–b15: low 8 bits of VSTOP;
|
|
|
|
|
// b7: attachment flag;
|
|
|
|
|
// b3–b6: unused;
|
|
|
|
|
// b2: VSTART high bit;
|
|
|
|
|
// b1: VSTOP high bit;
|
|
|
|
|
// b0: HSTART low bit.
|
2021-11-26 20:30:31 +00:00
|
|
|
|
h_start = uint16_t((h_start & 0x01fe) | (value & 0x01));
|
|
|
|
|
v_stop_ = uint16_t((value >> 8) | ((value & 0x02) << 7));
|
|
|
|
|
v_start_ = uint16_t((v_start_ & 0x00ff) | ((value & 0x04) << 6));
|
|
|
|
|
attached = value & 0x80;
|
|
|
|
|
|
2022-07-19 20:19:19 +00:00
|
|
|
|
// Disarm the sprite.
|
2021-11-26 20:30:31 +00:00
|
|
|
|
visible = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Sprite::set_image_data(int slot, uint16_t value) {
|
2022-07-19 20:19:19 +00:00
|
|
|
|
// Store data; also mark sprite as visible (i.e. 'arm' it)
|
|
|
|
|
// if data is being stored to slot 0.
|
2021-11-26 20:30:31 +00:00
|
|
|
|
data[slot] = value;
|
|
|
|
|
visible |= slot == 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 01:25:34 +00:00
|
|
|
|
bool Sprite::advance_dma(int offset, int y, bool is_first_line) {
|
2022-07-19 19:39:57 +00:00
|
|
|
|
assert(offset == 0 || offset == 1);
|
2021-11-26 20:30:31 +00:00
|
|
|
|
|
2022-07-19 19:39:57 +00:00
|
|
|
|
// Determine which word would be fetched, if DMA occurs.
|
|
|
|
|
// A bit of a cheat.
|
2021-11-26 20:30:31 +00:00
|
|
|
|
const uint16_t next_word = ram_[pointer_[0] & ram_mask_];
|
|
|
|
|
|
2022-07-19 20:11:29 +00:00
|
|
|
|
// "When the vertical position of the beam counter is equal to the VSTOP
|
|
|
|
|
// value in the sprite control words, the next two words fetched from the
|
|
|
|
|
// sprite data structure are written into the sprite control registers
|
|
|
|
|
// instead of being sent to the color registers"
|
2022-07-20 01:25:34 +00:00
|
|
|
|
//
|
|
|
|
|
// Guesswork, primarily from observing Spindizzy Worlds: the first line after
|
|
|
|
|
// vertical blank also triggers a control reload. Seek to verify.
|
|
|
|
|
if(y == v_stop_ || is_first_line) {
|
2022-07-19 20:11:29 +00:00
|
|
|
|
if(offset) {
|
|
|
|
|
// Second control word: stop position (mostly).
|
|
|
|
|
set_stop_and_control(next_word);
|
|
|
|
|
} else {
|
|
|
|
|
// First control word: start position.
|
|
|
|
|
set_start_position(next_word);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
visible |= y == v_start_;
|
2022-07-19 20:37:13 +00:00
|
|
|
|
if(!visible) return false; // Act as if there wasn't a fetch.
|
2022-07-19 20:11:29 +00:00
|
|
|
|
|
|
|
|
|
// Write colour word 1, then colour word 0; 0 is the word that 'arms'
|
|
|
|
|
// the sprite (i.e. makes it visible).
|
|
|
|
|
set_image_data(1 - offset, next_word);
|
2021-11-26 20:30:31 +00:00
|
|
|
|
}
|
2022-07-19 19:39:57 +00:00
|
|
|
|
|
|
|
|
|
// Acknowledge the fetch.
|
|
|
|
|
++pointer_[0];
|
|
|
|
|
return true;
|
2021-11-26 20:30:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <int sprite> void TwoSpriteShifter::load(
|
|
|
|
|
uint16_t lsb,
|
|
|
|
|
uint16_t msb,
|
|
|
|
|
int delay) {
|
|
|
|
|
constexpr int sprite_shift = sprite << 1;
|
|
|
|
|
const int delay_shift = delay << 2;
|
|
|
|
|
|
|
|
|
|
// Clear out any current sprite pixels; this is a reload.
|
|
|
|
|
data_ &= 0xcccc'cccc'cccc'ccccull >> (sprite_shift + delay_shift);
|
|
|
|
|
|
|
|
|
|
// Map LSB and MSB up to 64-bits and load into the shifter.
|
|
|
|
|
const uint64_t new_data =
|
|
|
|
|
(
|
|
|
|
|
expand_sprite_word(lsb) |
|
|
|
|
|
(expand_sprite_word(msb) << 1)
|
|
|
|
|
) << sprite_shift;
|
|
|
|
|
|
|
|
|
|
data_ |= new_data >> delay_shift;
|
|
|
|
|
overflow_ |= uint8_t((new_data << 8) >> delay_shift);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template void TwoSpriteShifter::load<0>(uint16_t, uint16_t, int);
|
|
|
|
|
template void TwoSpriteShifter::load<1>(uint16_t, uint16_t, int);
|