mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-09 20:25:19 +00:00
Merge pull request #1135 from TomHarte/9918Cleanup
Adds yet more clenliness
This commit is contained in:
@@ -18,6 +18,8 @@ namespace TI::TMS {
|
||||
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
|
||||
// Yamaha extensions.
|
||||
V9938,
|
||||
V9958,
|
||||
|
||||
|
@@ -35,7 +35,7 @@ Base<personality>::Base() :
|
||||
|
||||
// "For a line interrupt, /INT is pulled low 608 mclks into the appropriate scanline relative to pixel 0.
|
||||
// This is 3 mclks before the rising edge of /HSYNC which starts the next scanline."
|
||||
mode_timing_.line_interrupt_position = (LineLayout<personality>::EndOfLeftBorder + 304) % Timing<personality>::CyclesPerLine;
|
||||
mode_timing_.line_interrupt_position = (LineLayout<personality>::EndOfLeftBorder + 304) % LineLayout<personality>::CyclesPerLine;
|
||||
|
||||
// For a frame interrupt, /INT is pulled low 607 mclks into scanline 192 (of scanlines 0 through 261) relative to pixel 0.
|
||||
// This is 4 mclks before the rising edge of /HSYNC which starts the next scanline.
|
||||
@@ -197,7 +197,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
if(fetch_cycles_pool) {
|
||||
// Determine how much writing to do; at the absolute most go to the end of this line.
|
||||
const int fetch_cycles = std::min(
|
||||
Timing<personality>::CyclesPerLine - this->fetch_pointer_.column,
|
||||
LineLayout<personality>::CyclesPerLine - this->fetch_pointer_.column,
|
||||
fetch_cycles_pool
|
||||
);
|
||||
const int end_column = this->fetch_pointer_.column + fetch_cycles;
|
||||
@@ -319,7 +319,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
fetch_cycles_pool -= fetch_cycles;
|
||||
|
||||
// Check for end of line.
|
||||
if(this->fetch_pointer_.column == Timing<personality>::CyclesPerLine) {
|
||||
if(this->fetch_pointer_.column == LineLayout<personality>::CyclesPerLine) {
|
||||
this->fetch_pointer_.column = 0;
|
||||
this->fetch_pointer_.row = (this->fetch_pointer_.row + 1) % this->mode_timing_.total_lines;
|
||||
|
||||
@@ -341,13 +341,13 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
this->minimum_access_column_ =
|
||||
std::max(
|
||||
0,
|
||||
this->minimum_access_column_ - Timing<personality>::CyclesPerLine
|
||||
this->minimum_access_column_ - LineLayout<personality>::CyclesPerLine
|
||||
);
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
Storage<personality>::minimum_command_column_ =
|
||||
std::max(
|
||||
0,
|
||||
Storage<personality>::minimum_command_column_ - Timing<personality>::CyclesPerLine
|
||||
Storage<personality>::minimum_command_column_ - LineLayout<personality>::CyclesPerLine
|
||||
);
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
if(output_cycles_pool) {
|
||||
// Determine how much time has passed in the remainder of this line, and proceed.
|
||||
const int target_output_cycles = std::min(
|
||||
Timing<personality>::CyclesPerLine - this->output_pointer_.column,
|
||||
LineLayout<personality>::CyclesPerLine - this->output_pointer_.column,
|
||||
output_cycles_pool
|
||||
);
|
||||
int output_cycles_performed = 0;
|
||||
@@ -522,8 +522,8 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
};
|
||||
|
||||
const auto right_blank = [&]() {
|
||||
if(end_column == Timing<personality>::CyclesPerLine) {
|
||||
output_blank(Timing<personality>::CyclesPerLine - LineLayout<personality>::EndOfRightBorder);
|
||||
if(end_column == LineLayout<personality>::CyclesPerLine) {
|
||||
output_blank(LineLayout<personality>::CyclesPerLine - LineLayout<personality>::EndOfRightBorder);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -534,8 +534,8 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
) {
|
||||
// Vertical sync.
|
||||
// TODO: the Yamaha and Mega Drive both support interlaced video.
|
||||
if(end_column == Timing<personality>::CyclesPerLine) {
|
||||
output_sync(Timing<personality>::CyclesPerLine);
|
||||
if(end_column == LineLayout<personality>::CyclesPerLine) {
|
||||
output_sync(LineLayout<personality>::CyclesPerLine);
|
||||
}
|
||||
} else {
|
||||
left_blank();
|
||||
@@ -610,14 +610,14 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
// Advance time.
|
||||
// -------------
|
||||
this->output_pointer_.column = end_column;
|
||||
if(end_column == Timing<personality>::CyclesPerLine) {
|
||||
if(end_column == LineLayout<personality>::CyclesPerLine) {
|
||||
// Advance line buffer.
|
||||
this->advance(this->draw_line_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
output_cycles_pool -= target_output_cycles;
|
||||
if(this->output_pointer_.column == Timing<personality>::CyclesPerLine) {
|
||||
if(this->output_pointer_.column == LineLayout<personality>::CyclesPerLine) {
|
||||
this->output_pointer_.column = 0;
|
||||
this->output_pointer_.row = (this->output_pointer_.row + 1) % this->mode_timing_.total_lines;
|
||||
}
|
||||
@@ -685,7 +685,7 @@ void Base<personality>::write_vram(uint8_t value) {
|
||||
// Enqueue the write to occur at the next available slot.
|
||||
read_ahead_buffer_ = value;
|
||||
queued_access_ = MemoryAccess::Write;
|
||||
minimum_access_column_ = fetch_pointer_.column + Timing<personality>::VRAMAccessDelay;
|
||||
minimum_access_column_ = fetch_pointer_.column + LineLayout<personality>::VRAMAccessDelay;
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
@@ -1034,7 +1034,7 @@ void Base<personality>::write_register(uint8_t value) {
|
||||
// A read request is enqueued upon setting the address; conversely a write
|
||||
// won't be enqueued unless and until some actual data is supplied.
|
||||
queued_access_ = MemoryAccess::Read;
|
||||
minimum_access_column_ = fetch_pointer_.column + Timing<personality>::VRAMAccessDelay;
|
||||
minimum_access_column_ = fetch_pointer_.column + LineLayout<personality>::VRAMAccessDelay;
|
||||
}
|
||||
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
@@ -1242,11 +1242,11 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const {
|
||||
if(get_interrupt_line()) return HalfCycles::max();
|
||||
|
||||
// Calculate the amount of time until the next end-of-frame interrupt.
|
||||
const int frame_length = Timing<personality>::CyclesPerLine * this->mode_timing_.total_lines;
|
||||
const int frame_length = LineLayout<personality>::CyclesPerLine * this->mode_timing_.total_lines;
|
||||
int time_until_frame_interrupt =
|
||||
(
|
||||
((this->mode_timing_.end_of_frame_interrupt_position.row * Timing<personality>::CyclesPerLine) + this->mode_timing_.end_of_frame_interrupt_position.column + frame_length) -
|
||||
((this->fetch_pointer_.row * Timing<personality>::CyclesPerLine) + this->fetch_pointer_.column)
|
||||
((this->mode_timing_.end_of_frame_interrupt_position.row * LineLayout<personality>::CyclesPerLine) + this->mode_timing_.end_of_frame_interrupt_position.column + frame_length) -
|
||||
((this->fetch_pointer_.row * LineLayout<personality>::CyclesPerLine) + this->fetch_pointer_.column)
|
||||
) % frame_length;
|
||||
if(!time_until_frame_interrupt) time_until_frame_interrupt = frame_length;
|
||||
|
||||
@@ -1260,7 +1260,7 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const {
|
||||
int cycles_to_next_interrupt_threshold = this->mode_timing_.line_interrupt_position - this->fetch_pointer_.column;
|
||||
int line_of_next_interrupt_threshold = this->fetch_pointer_.row;
|
||||
if(cycles_to_next_interrupt_threshold <= 0) {
|
||||
cycles_to_next_interrupt_threshold += Timing<personality>::CyclesPerLine;
|
||||
cycles_to_next_interrupt_threshold += LineLayout<personality>::CyclesPerLine;
|
||||
++line_of_next_interrupt_threshold;
|
||||
}
|
||||
|
||||
@@ -1291,7 +1291,7 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const {
|
||||
// Figure out the number of internal cycles until the next line interrupt, which is the amount
|
||||
// of time to the next tick over and then next_line_interrupt_row - row_ lines further.
|
||||
const int lines_until_interrupt = (next_line_interrupt_row - line_of_next_interrupt_threshold + this->mode_timing_.total_lines) % this->mode_timing_.total_lines;
|
||||
const int local_cycles_until_line_interrupt = cycles_to_next_interrupt_threshold + lines_until_interrupt * Timing<personality>::CyclesPerLine;
|
||||
const int local_cycles_until_line_interrupt = cycles_to_next_interrupt_threshold + lines_until_interrupt * LineLayout<personality>::CyclesPerLine;
|
||||
if(!this->generate_interrupts_) return this->clock_converter_.half_cycles_before_internal_cycles(local_cycles_until_line_interrupt);
|
||||
|
||||
// Return whichever interrupt is closer.
|
||||
@@ -1305,7 +1305,7 @@ HalfCycles TMS9918<personality>::get_time_until_line(int line) {
|
||||
int cycles_to_next_interrupt_threshold = this->mode_timing_.line_interrupt_position - this->fetch_pointer_.column;
|
||||
int line_of_next_interrupt_threshold = this->fetch_pointer_.row;
|
||||
if(cycles_to_next_interrupt_threshold <= 0) {
|
||||
cycles_to_next_interrupt_threshold += Timing<personality>::CyclesPerLine;
|
||||
cycles_to_next_interrupt_threshold += LineLayout<personality>::CyclesPerLine;
|
||||
++line_of_next_interrupt_threshold;
|
||||
}
|
||||
|
||||
@@ -1313,7 +1313,7 @@ HalfCycles TMS9918<personality>::get_time_until_line(int line) {
|
||||
line += this->mode_timing_.total_lines;
|
||||
}
|
||||
|
||||
return this->clock_converter_.half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*Timing<personality>::CyclesPerLine);
|
||||
return this->clock_converter_.half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*LineLayout<personality>::CyclesPerLine);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
@@ -1329,7 +1329,7 @@ template <Personality personality>uint8_t TMS9918<personality>::get_latched_hori
|
||||
// which counts the 256 pixels as items 0–255, starts
|
||||
// counting at -48, and returns only the top 8 bits of the number.
|
||||
int public_counter = this->latched_column_ - LineLayout<personality>::EndOfLeftBorder;
|
||||
if(public_counter < -46) public_counter += Timing<personality>::CyclesPerLine;
|
||||
if(public_counter < -46) public_counter += LineLayout<personality>::CyclesPerLine;
|
||||
return uint8_t(public_counter >> 1);
|
||||
}
|
||||
|
||||
|
@@ -620,9 +620,9 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
template <SpriteMode mode, bool double_width> void draw_sprites(uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer = nullptr);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#include "Fetch.hpp"
|
||||
#include "Draw.hpp"
|
||||
|
||||
}
|
||||
|
||||
#endif /* TMS9918Base_hpp */
|
||||
|
@@ -78,7 +78,6 @@ constexpr bool interleaves_banks(ScreenMode mode) {
|
||||
return mode == ScreenMode::YamahaGraphics6 || mode == ScreenMode::YamahaGraphics7;
|
||||
}
|
||||
|
||||
|
||||
enum class FetchMode {
|
||||
Text,
|
||||
Character,
|
||||
|
@@ -51,20 +51,6 @@ template <Personality personality, Clock clock> constexpr int from_internal(int
|
||||
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
|
||||
}
|
||||
|
||||
/// Provides default timing measurements that duplicate the layout of a TMS9928's line,
|
||||
/// scaled to the clock rate specified.
|
||||
template <Personality personality> struct StandardTiming {
|
||||
/// The total number of internal cycles per line of output.
|
||||
constexpr static int CyclesPerLine = clock_rate<personality, Clock::Internal>();
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 6;
|
||||
};
|
||||
|
||||
/// Provides concrete, specific timing for the nominated personality.
|
||||
template <Personality personality> struct Timing: public StandardTiming<personality> {};
|
||||
|
||||
/*!
|
||||
Provides a [potentially-]stateful conversion between the external and internal clocks.
|
||||
Unlike the other clock conversions, this one may be non-integral, requiring that
|
||||
@@ -175,8 +161,14 @@ template <Personality personality> struct LineLayout<personality, std::enable_if
|
||||
constexpr static int EndOfPixels = 319;
|
||||
constexpr static int EndOfRightBorder = 334;
|
||||
|
||||
constexpr static int CyclesPerLine = 342;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 69;
|
||||
constexpr static int TextModeEndOfPixels = 309;
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 6;
|
||||
};
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||
@@ -188,8 +180,14 @@ template <Personality personality> struct LineLayout<personality, std::enable_if
|
||||
constexpr static int EndOfPixels = 1282;
|
||||
constexpr static int EndOfRightBorder = 1341;
|
||||
|
||||
constexpr static int CyclesPerLine = 1368;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 294;
|
||||
constexpr static int TextModeEndOfPixels = 1254;
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 16;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef Draw_hpp
|
||||
#define Draw_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// MARK: - Sprites, as generalised.
|
||||
|
||||
template <Personality personality>
|
||||
@@ -565,4 +567,6 @@ void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
|
||||
// TODO.
|
||||
|
||||
}
|
||||
|
||||
#endif /* Draw_hpp */
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef Fetch_hpp
|
||||
#define Fetch_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/*
|
||||
Fetching routines follow below; they obey the following rules:
|
||||
|
||||
@@ -802,4 +804,6 @@ template<bool use_end> void Base<personality>::fetch_yamaha(uint8_t y, int, int
|
||||
|
||||
// TODO.
|
||||
|
||||
}
|
||||
|
||||
#endif /* Fetch_hpp */
|
||||
|
@@ -28,32 +28,8 @@ template <> struct Storage<Personality::TMS9918A> {
|
||||
void begin_line(ScreenMode, bool) {}
|
||||
};
|
||||
|
||||
// Yamaha-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||
using AddressT = uint32_t;
|
||||
|
||||
std::array<uint8_t, 65536> expansion_ram_;
|
||||
|
||||
int selected_status_ = 0;
|
||||
|
||||
int indirect_register_ = 0;
|
||||
bool increment_indirect_register_ = false;
|
||||
|
||||
int adjustment_[2]{};
|
||||
|
||||
std::array<uint32_t, 16> palette_{};
|
||||
std::array<uint32_t, 16> background_palette_{};
|
||||
bool solid_background_ = true;
|
||||
|
||||
uint8_t new_colour_ = 0;
|
||||
uint8_t palette_entry_ = 0;
|
||||
bool palette_write_phase_ = false;
|
||||
|
||||
uint8_t mode_ = 0;
|
||||
|
||||
uint8_t vertical_offset_ = 0;
|
||||
uint8_t sprite_cache_[8][32]{};
|
||||
|
||||
struct YamahaFetcher {
|
||||
public:
|
||||
/// Describes an _observable_ memory access event. i.e. anything that it is safe
|
||||
/// (and convenient) to treat as atomic in between external slots.
|
||||
struct Event {
|
||||
@@ -90,135 +66,11 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
|
||||
// State that tracks fetching position within a line.
|
||||
const Event *next_event_ = nullptr;
|
||||
|
||||
// Text blink colours.
|
||||
uint8_t blink_text_colour_ = 0;
|
||||
uint8_t blink_background_colour_ = 0;
|
||||
|
||||
// Blink state (which is also affects even/odd page display in applicable modes).
|
||||
int in_blink_ = 1;
|
||||
uint8_t blink_periods_ = 0;
|
||||
uint8_t blink_counter_ = 0;
|
||||
|
||||
// Sprite collection state.
|
||||
bool sprites_enabled_ = true;
|
||||
|
||||
// Additional status.
|
||||
uint8_t colour_status_ = 0;
|
||||
uint16_t colour_location_ = 0;
|
||||
uint16_t collision_location_[2]{};
|
||||
|
||||
/// Resets line-ephemeral state for a new line.
|
||||
void begin_line(ScreenMode mode, bool is_refresh) {
|
||||
if(is_refresh) {
|
||||
next_event_ = refresh_events.data();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80:
|
||||
case ScreenMode::Text:
|
||||
next_event_ = text_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::YamahaGraphics1:
|
||||
case ScreenMode::YamahaGraphics2:
|
||||
next_event_ = character_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3: // TODO: verify; my guess is that G3 is timed like a bitmap mode
|
||||
// in order to fit the pattern for sprite mode 2. Just a guess.
|
||||
default:
|
||||
next_event_ = sprites_enabled_ ? sprites_events.data() : no_sprites_events.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Command engine state.
|
||||
CommandContext command_context_;
|
||||
ModeDescription mode_description_;
|
||||
std::unique_ptr<Command> command_ = nullptr;
|
||||
|
||||
enum class CommandStep {
|
||||
None,
|
||||
|
||||
CopySourcePixelToStatus,
|
||||
|
||||
ReadSourcePixel,
|
||||
ReadDestinationPixel,
|
||||
WritePixel,
|
||||
|
||||
ReadSourceByte,
|
||||
WriteByte,
|
||||
};
|
||||
CommandStep next_command_step_ = CommandStep::None;
|
||||
int minimum_command_column_ = 0;
|
||||
uint8_t command_latch_ = 0;
|
||||
|
||||
void update_command_step(int current_column) {
|
||||
if(!command_) {
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
if(command_->done()) {
|
||||
command_ = nullptr;
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
|
||||
minimum_command_column_ = current_column + command_->cycles;
|
||||
switch(command_->access) {
|
||||
case Command::AccessType::ReadPoint:
|
||||
next_command_step_ = CommandStep::CopySourcePixelToStatus;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyPoint:
|
||||
next_command_step_ = CommandStep::ReadSourcePixel;
|
||||
break;
|
||||
case Command::AccessType::PlotPoint:
|
||||
next_command_step_ = CommandStep::ReadDestinationPixel;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
// i.e. nothing to do until a colour is received.
|
||||
next_command_step_ = CommandStep::None;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyByte:
|
||||
next_command_step_ = CommandStep::ReadSourceByte;
|
||||
break;
|
||||
case Command::AccessType::WriteByte:
|
||||
next_command_step_ = CommandStep::WriteByte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Storage() noexcept {
|
||||
// Perform sanity checks on the event lists.
|
||||
#ifndef NDEBUG
|
||||
const Event *lists[] = { no_sprites_events.data(), sprites_events.data(), text_events.data(), character_events.data(), refresh_events.data(), nullptr };
|
||||
const Event **list = lists;
|
||||
while(*list) {
|
||||
const Event *cursor = *list;
|
||||
++list;
|
||||
|
||||
while(cursor[1].offset != 1368) {
|
||||
assert(cursor[1].offset > cursor[0].offset);
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Seed to _something_ meaningful.
|
||||
//
|
||||
// TODO: this is a workaround [/hack], in effect, for the main TMS' habit of starting
|
||||
// in a randomised position, which means that start-of-line isn't announced.
|
||||
//
|
||||
// Do I really want that behaviour?
|
||||
next_event_ = refresh_events.data();
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
/// @return 1 + the number of times within a line that @c GeneratorT produces an event.
|
||||
template <typename GeneratorT> static constexpr size_t events_size() {
|
||||
size_t size = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
@@ -228,6 +80,7 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
/// @return An array of all events generated by @c GeneratorT in line order.
|
||||
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
|
||||
static constexpr std::array<Event, size> events() {
|
||||
std::array<Event, size> result{};
|
||||
@@ -287,7 +140,6 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
static constexpr auto refresh_events = events<RefreshGenerator>();
|
||||
|
||||
template <bool include_sprites> struct BitmapGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
@@ -366,8 +218,6 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
|
||||
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
|
||||
|
||||
struct TextGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
@@ -404,7 +254,6 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
static constexpr auto text_events = events<TextGenerator>();
|
||||
|
||||
struct CharacterGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
@@ -442,6 +291,155 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct YamahaCommandState {
|
||||
CommandContext command_context_;
|
||||
ModeDescription mode_description_;
|
||||
std::unique_ptr<Command> command_ = nullptr;
|
||||
|
||||
enum class CommandStep {
|
||||
None,
|
||||
|
||||
CopySourcePixelToStatus,
|
||||
|
||||
ReadSourcePixel,
|
||||
ReadDestinationPixel,
|
||||
WritePixel,
|
||||
|
||||
ReadSourceByte,
|
||||
WriteByte,
|
||||
};
|
||||
CommandStep next_command_step_ = CommandStep::None;
|
||||
int minimum_command_column_ = 0;
|
||||
uint8_t command_latch_ = 0;
|
||||
|
||||
void update_command_step(int current_column) {
|
||||
if(!command_) {
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
if(command_->done()) {
|
||||
command_ = nullptr;
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
|
||||
minimum_command_column_ = current_column + command_->cycles;
|
||||
switch(command_->access) {
|
||||
case Command::AccessType::ReadPoint:
|
||||
next_command_step_ = CommandStep::CopySourcePixelToStatus;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyPoint:
|
||||
next_command_step_ = CommandStep::ReadSourcePixel;
|
||||
break;
|
||||
case Command::AccessType::PlotPoint:
|
||||
next_command_step_ = CommandStep::ReadDestinationPixel;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
// i.e. nothing to do until a colour is received.
|
||||
next_command_step_ = CommandStep::None;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyByte:
|
||||
next_command_step_ = CommandStep::ReadSourceByte;
|
||||
break;
|
||||
case Command::AccessType::WriteByte:
|
||||
next_command_step_ = CommandStep::WriteByte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Yamaha-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>: public YamahaFetcher, public YamahaCommandState {
|
||||
using AddressT = uint32_t;
|
||||
|
||||
// The Yamaha's (optional in real hardware) additional 64kb of expansion RAM.
|
||||
// This is a valid target and source for the command engine, but can't be used as a source for current video data.
|
||||
std::array<uint8_t, 65536> expansion_ram_;
|
||||
|
||||
// Register indirections.
|
||||
int selected_status_ = 0;
|
||||
int indirect_register_ = 0;
|
||||
bool increment_indirect_register_ = false;
|
||||
|
||||
// Output horizontal and vertical adjustment, plus the selected vertical offset (i.e. hardware scroll).
|
||||
int adjustment_[2]{};
|
||||
uint8_t vertical_offset_ = 0;
|
||||
|
||||
// The palette, plus a shadow copy in which colour 0 is not the current palette colour 0,
|
||||
// but is rather the current global background colour. This simplifies flow when colour 0
|
||||
// is set as transparent.
|
||||
std::array<uint32_t, 16> palette_{};
|
||||
std::array<uint32_t, 16> background_palette_{};
|
||||
bool solid_background_ = true;
|
||||
|
||||
// Transient state for palette setting.
|
||||
uint8_t new_colour_ = 0;
|
||||
uint8_t palette_entry_ = 0;
|
||||
bool palette_write_phase_ = false;
|
||||
|
||||
// Recepticle for all five bits of the current screen mode.
|
||||
uint8_t mode_ = 0;
|
||||
|
||||
// Used ephemerally during drawing to compound sprites with the 'CC'
|
||||
// (compound colour?) bit set.
|
||||
uint8_t sprite_cache_[8][32]{};
|
||||
|
||||
// Text blink colours.
|
||||
uint8_t blink_text_colour_ = 0;
|
||||
uint8_t blink_background_colour_ = 0;
|
||||
|
||||
// Blink state (which is also affects even/odd page display in applicable modes).
|
||||
int in_blink_ = 1;
|
||||
uint8_t blink_periods_ = 0;
|
||||
uint8_t blink_counter_ = 0;
|
||||
|
||||
// Additional things exposed by status registers.
|
||||
uint8_t colour_status_ = 0;
|
||||
uint16_t colour_location_ = 0;
|
||||
uint16_t collision_location_[2]{};
|
||||
|
||||
Storage() noexcept {
|
||||
// Seed to something valid.
|
||||
next_event_ = refresh_events.data();
|
||||
}
|
||||
|
||||
/// Resets line-ephemeral state for a new line.
|
||||
void begin_line(ScreenMode mode, bool is_refresh) {
|
||||
if(is_refresh) {
|
||||
next_event_ = refresh_events.data();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80:
|
||||
case ScreenMode::Text:
|
||||
next_event_ = text_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::YamahaGraphics1:
|
||||
case ScreenMode::YamahaGraphics2:
|
||||
next_event_ = character_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3: // TODO: verify; my guess is that G3 is timed like a bitmap mode
|
||||
// in order to fit the pattern for sprite mode 2. Just a guess.
|
||||
default:
|
||||
next_event_ = sprites_enabled_ ? sprites_events.data() : no_sprites_events.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto refresh_events = events<RefreshGenerator>();
|
||||
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
|
||||
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
|
||||
static constexpr auto text_events = events<TextGenerator>();
|
||||
static constexpr auto character_events = events<CharacterGenerator>();
|
||||
};
|
||||
|
||||
|
@@ -28,8 +28,6 @@ class AllRAMProcessor:
|
||||
virtual uint16_t value_of(Register r) = 0;
|
||||
virtual void set_value_of(Register r, uint16_t value) = 0;
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
AllRAMProcessor(size_t memory_size) : ::CPU::AllRAMProcessor(memory_size) {}
|
||||
};
|
||||
|
Reference in New Issue
Block a user