mirror of
https://github.com/TomHarte/CLK.git
synced 2024-07-17 13:29:02 +00:00
Merge pull request #1073 from TomHarte/AmigaSprites
Improve Amiga sprite emulation.
This commit is contained in:
commit
b67790df7d
@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
using namespace Amiga;
|
using namespace Amiga;
|
||||||
|
|
||||||
|
// TODO: I don't think the nonsense below, which was intended to allow a typed enum but also
|
||||||
|
// clean combination, really works. Rethink.
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <typename EnumT, EnumT... T> struct Mask {
|
template <typename EnumT, EnumT... T> struct Mask {
|
||||||
@ -34,6 +36,18 @@ template <typename EnumT, EnumT F, EnumT... T> struct Mask<EnumT, F, T...> {
|
|||||||
template <InterruptFlag... Flags> struct InterruptMask: Mask<InterruptFlag, Flags...> {};
|
template <InterruptFlag... Flags> struct InterruptMask: Mask<InterruptFlag, Flags...> {};
|
||||||
template <DMAFlag... Flags> struct DMAMask: Mask<DMAFlag, Flags...> {};
|
template <DMAFlag... Flags> struct DMAMask: Mask<DMAFlag, Flags...> {};
|
||||||
|
|
||||||
|
constexpr uint16_t AudioFlags[] = {
|
||||||
|
DMAMask<DMAFlag::AudioChannel0, DMAFlag::AllBelow>::value,
|
||||||
|
DMAMask<DMAFlag::AudioChannel1, DMAFlag::AllBelow>::value,
|
||||||
|
DMAMask<DMAFlag::AudioChannel2, DMAFlag::AllBelow>::value,
|
||||||
|
DMAMask<DMAFlag::AudioChannel3, DMAFlag::AllBelow>::value,
|
||||||
|
};
|
||||||
|
constexpr auto BlitterFlag = DMAMask<DMAFlag::Blitter, DMAFlag::AllBelow>::value;
|
||||||
|
constexpr auto BitplaneFlag = DMAMask<DMAFlag::Bitplane, DMAFlag::AllBelow>::value;
|
||||||
|
constexpr auto CopperFlag = DMAMask<DMAFlag::Copper, DMAFlag::AllBelow>::value;
|
||||||
|
constexpr auto DiskFlag = DMAMask<DMAFlag::Disk, DMAFlag::AllBelow>::value;
|
||||||
|
constexpr auto SpritesFlag = DMAMask<DMAFlag::Sprites, DMAFlag::AllBelow>::value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DMA_CONSTRUCT *this, reinterpret_cast<uint16_t *>(map.chip_ram.data()), map.chip_ram.size() >> 1
|
#define DMA_CONSTRUCT *this, reinterpret_cast<uint16_t *>(map.chip_ram.data()), map.chip_ram.size() >> 1
|
||||||
@ -488,17 +502,6 @@ void Chipset::flush_output() {
|
|||||||
|
|
||||||
/// @returns @c true if this was a CPU slot; @c false otherwise.
|
/// @returns @c true if this was a CPU slot; @c false otherwise.
|
||||||
template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
|
template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
|
||||||
constexpr uint16_t AudioFlags[] = {
|
|
||||||
DMAMask<DMAFlag::AudioChannel0, DMAFlag::AllBelow>::value,
|
|
||||||
DMAMask<DMAFlag::AudioChannel1, DMAFlag::AllBelow>::value,
|
|
||||||
DMAMask<DMAFlag::AudioChannel2, DMAFlag::AllBelow>::value,
|
|
||||||
DMAMask<DMAFlag::AudioChannel3, DMAFlag::AllBelow>::value,
|
|
||||||
};
|
|
||||||
constexpr auto BlitterFlag = DMAMask<DMAFlag::Blitter, DMAFlag::AllBelow>::value;
|
|
||||||
constexpr auto BitplaneFlag = DMAMask<DMAFlag::Bitplane, DMAFlag::AllBelow>::value;
|
|
||||||
constexpr auto CopperFlag = DMAMask<DMAFlag::Copper, DMAFlag::AllBelow>::value;
|
|
||||||
constexpr auto DiskFlag = DMAMask<DMAFlag::Disk, DMAFlag::AllBelow>::value;
|
|
||||||
constexpr auto SpritesFlag = DMAMask<DMAFlag::Sprites, DMAFlag::AllBelow>::value;
|
|
||||||
|
|
||||||
// Update state as to whether bitplane fetching should happen now.
|
// Update state as to whether bitplane fetching should happen now.
|
||||||
//
|
//
|
||||||
@ -607,11 +610,11 @@ template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (cycle >= 0x16 && cycle < 0x36) {
|
if constexpr (cycle >= 0x16 && cycle < 0x36) {
|
||||||
if((dma_control_ & SpritesFlag) == SpritesFlag && y_ >= vertical_blank_height_) {
|
if((dma_control_ & SpritesFlag) == SpritesFlag) {
|
||||||
constexpr auto sprite_id = (cycle - 0x16) >> 2;
|
constexpr auto sprite_id = (cycle - 0x16) >> 2;
|
||||||
static_assert(sprite_id >= 0 && sprite_id < std::tuple_size<decltype(sprites_)>::value);
|
static_assert(sprite_id >= 0 && sprite_id < std::tuple_size<decltype(sprites_)>::value);
|
||||||
|
|
||||||
if(sprites_[sprite_id].advance_dma(!(cycle&2))) {
|
if(sprites_[sprite_id].advance_dma((~cycle&2) >> 1, y_, y_ == vertical_blank_height_)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -741,10 +744,6 @@ template <bool stop_on_cpu> Chipset::Changes Chipset::run(HalfCycles length) {
|
|||||||
is_long_field_ ^= interlace_;
|
is_long_field_ ^= interlace_;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto &sprite: sprites_) {
|
|
||||||
sprite.advance_line(y_, y_ == vertical_blank_height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch_vertical_ |= y_ == display_window_start_[1];
|
fetch_vertical_ |= y_ == display_window_start_[1];
|
||||||
fetch_vertical_ &= y_ != display_window_stop_[1];
|
fetch_vertical_ &= y_ != display_window_stop_[1];
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "Sprites.hpp"
|
#include "Sprites.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
using namespace Amiga;
|
using namespace Amiga;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -33,61 +35,69 @@ static_assert(expand_sprite_word(0x0000) == 0x00'00'00'00'00'00'00'00);
|
|||||||
// MARK: - Sprites.
|
// MARK: - Sprites.
|
||||||
|
|
||||||
void Sprite::set_start_position(uint16_t value) {
|
void Sprite::set_start_position(uint16_t value) {
|
||||||
|
// b8–b15: low 8 bits of VSTART;
|
||||||
|
// b0–b7: high 8 bits of HSTART.
|
||||||
v_start_ = (v_start_ & 0xff00) | (value >> 8);
|
v_start_ = (v_start_ & 0xff00) | (value >> 8);
|
||||||
h_start = uint16_t((h_start & 0x0001) | ((value & 0xff) << 1));
|
h_start = uint16_t((h_start & 0x0001) | ((value & 0xff) << 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::set_stop_and_control(uint16_t value) {
|
void Sprite::set_stop_and_control(uint16_t value) {
|
||||||
|
// 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.
|
||||||
h_start = uint16_t((h_start & 0x01fe) | (value & 0x01));
|
h_start = uint16_t((h_start & 0x01fe) | (value & 0x01));
|
||||||
v_stop_ = uint16_t((value >> 8) | ((value & 0x02) << 7));
|
v_stop_ = uint16_t((value >> 8) | ((value & 0x02) << 7));
|
||||||
v_start_ = uint16_t((v_start_ & 0x00ff) | ((value & 0x04) << 6));
|
v_start_ = uint16_t((v_start_ & 0x00ff) | ((value & 0x04) << 6));
|
||||||
attached = value & 0x80;
|
attached = value & 0x80;
|
||||||
|
|
||||||
// Disarm the sprite, but expect graphics next from DMA.
|
// Disarm the sprite.
|
||||||
visible = false;
|
visible = false;
|
||||||
dma_state_ = DMAState::FetchImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::set_image_data(int slot, uint16_t value) {
|
void Sprite::set_image_data(int slot, uint16_t value) {
|
||||||
|
// Store data; also mark sprite as visible (i.e. 'arm' it)
|
||||||
|
// if data is being stored to slot 0.
|
||||||
data[slot] = value;
|
data[slot] = value;
|
||||||
visible |= slot == 0;
|
visible |= slot == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::advance_line(int y, bool is_end_of_blank) {
|
bool Sprite::advance_dma(int offset, int y, bool is_first_line) {
|
||||||
if(dma_state_ == DMAState::FetchImage && y == v_start_) {
|
assert(offset == 0 || offset == 1);
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
if(is_end_of_blank || y == v_stop_) {
|
|
||||||
dma_state_ = DMAState::FetchControl;
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Sprite::advance_dma(int offset) {
|
// Determine which word would be fetched, if DMA occurs.
|
||||||
if(!visible) return false;
|
// A bit of a cheat.
|
||||||
|
|
||||||
// Fetch another word.
|
|
||||||
const uint16_t next_word = ram_[pointer_[0] & ram_mask_];
|
const uint16_t next_word = ram_[pointer_[0] & ram_mask_];
|
||||||
++pointer_[0];
|
|
||||||
|
|
||||||
// Put the fetched word somewhere appropriate and update the DMA state.
|
// "When the vertical position of the beam counter is equal to the VSTOP
|
||||||
switch(dma_state_) {
|
// value in the sprite control words, the next two words fetched from the
|
||||||
// i.e. stopped.
|
// sprite data structure are written into the sprite control registers
|
||||||
default: return false;
|
// instead of being sent to the color registers"
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
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_;
|
||||||
|
if(!visible) return false; // Act as if there wasn't a fetch.
|
||||||
|
|
||||||
case DMAState::FetchControl:
|
// Write colour word 1, then colour word 0; 0 is the word that 'arms'
|
||||||
if(offset) {
|
// the sprite (i.e. makes it visible).
|
||||||
set_stop_and_control(next_word);
|
set_image_data(1 - offset, next_word);
|
||||||
} else {
|
|
||||||
set_start_position(next_word);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case DMAState::FetchImage:
|
|
||||||
set_image_data(1 - bool(offset), next_word);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
// Acknowledge the fetch.
|
||||||
|
++pointer_[0];
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int sprite> void TwoSpriteShifter::load(
|
template <int sprite> void TwoSpriteShifter::load(
|
||||||
|
@ -23,8 +23,7 @@ class Sprite: public DMADevice<1> {
|
|||||||
void set_stop_and_control(uint16_t value);
|
void set_stop_and_control(uint16_t value);
|
||||||
void set_image_data(int slot, uint16_t value);
|
void set_image_data(int slot, uint16_t value);
|
||||||
|
|
||||||
void advance_line(int y, bool is_end_of_blank);
|
bool advance_dma(int offset, int y, bool is_first_line);
|
||||||
bool advance_dma(int offset);
|
|
||||||
|
|
||||||
uint16_t data[2]{};
|
uint16_t data[2]{};
|
||||||
bool attached = false;
|
bool attached = false;
|
||||||
@ -33,11 +32,6 @@ class Sprite: public DMADevice<1> {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t v_start_ = 0, v_stop_ = 0;
|
uint16_t v_start_ = 0, v_stop_ = 0;
|
||||||
|
|
||||||
enum class DMAState {
|
|
||||||
FetchControl,
|
|
||||||
FetchImage
|
|
||||||
} dma_state_ = DMAState::FetchControl;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class TwoSpriteShifter {
|
class TwoSpriteShifter {
|
||||||
|
Loading…
Reference in New Issue
Block a user