mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +00:00
Avoid hand-writing all the various conversions.
This commit is contained in:
parent
c0fe88a5bb
commit
fd14829992
@ -200,21 +200,21 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
// ------------------------
|
||||
// Perform memory accesses.
|
||||
// ------------------------
|
||||
#define fetch(function, converter) \
|
||||
const int first_window = this->clock_converter_.converter(this->write_pointer_.column); \
|
||||
const int final_window = this->clock_converter_.converter(end_column); \
|
||||
#define fetch(function, clock) \
|
||||
const int first_window = from_internal<personality, clock>(this->write_pointer_.column);\
|
||||
const int final_window = from_internal<personality, clock>(end_column); \
|
||||
if(first_window == final_window) break; \
|
||||
if(final_window != TMSAccessWindowsPerLine) { \
|
||||
if(final_window != clock_rate<personality, clock>()) { \
|
||||
function<true>(first_window, final_window); \
|
||||
} else { \
|
||||
function<false>(first_window, final_window); \
|
||||
}
|
||||
|
||||
switch(line_buffer.line_mode) {
|
||||
case LineMode::Text: { fetch(this->template fetch_tms_text, to_tms_access_clock); } break;
|
||||
case LineMode::Character: { fetch(this->template fetch_tms_character, to_tms_access_clock); } break;
|
||||
case LineMode::SMS: { fetch(this->template fetch_sms, to_tms_access_clock); } break;
|
||||
case LineMode::Refresh: { fetch(this->template fetch_tms_refresh, to_tms_access_clock); } break;
|
||||
case LineMode::Text: { fetch(this->template fetch_tms_text, Clock::TMSMemoryWindow); } break;
|
||||
case LineMode::Character: { fetch(this->template fetch_tms_character, Clock::TMSMemoryWindow); } break;
|
||||
case LineMode::SMS: { fetch(this->template fetch_sms, Clock::TMSMemoryWindow); } break;
|
||||
case LineMode::Refresh: { fetch(this->template fetch_tms_refresh, Clock::TMSMemoryWindow); } break;
|
||||
}
|
||||
|
||||
#undef fetch
|
||||
@ -343,7 +343,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
// Output video stream.
|
||||
// --------------------
|
||||
|
||||
#define crt_convert(action, time) this->crt_.action(this->clock_converter_.to_crt_clock(time))
|
||||
#define crt_convert(action, time) this->crt_.action(from_internal<personality, Clock::CRT>(time))
|
||||
#define output_sync(x) crt_convert(output_sync, x)
|
||||
#define output_blank(x) crt_convert(output_blank, x)
|
||||
#define output_default_colour_burst(x) crt_convert(output_default_colour_burst, x)
|
||||
@ -407,9 +407,9 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
// Left border.
|
||||
border(Timing<personality>::StartOfLeftBorder, line_buffer.first_pixel_output_column);
|
||||
|
||||
#define draw(function, converter) { \
|
||||
const int relative_start = this->clock_converter_.converter(start - line_buffer.first_pixel_output_column); \
|
||||
const int relative_end = this->clock_converter_.converter(end - line_buffer.first_pixel_output_column); \
|
||||
#define draw(function, clock) { \
|
||||
const int relative_start = from_internal<personality, clock>(start - line_buffer.first_pixel_output_column); \
|
||||
const int relative_end = from_internal<personality, clock>(end - line_buffer.first_pixel_output_column); \
|
||||
if(relative_start == relative_end) break; \
|
||||
this->function; }
|
||||
|
||||
@ -427,9 +427,9 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
|
||||
if(this->pixel_target_) {
|
||||
switch(line_buffer.line_mode) {
|
||||
case LineMode::SMS: draw(draw_sms(relative_start, relative_end, cram_value), to_tms_pixel_clock); break;
|
||||
case LineMode::Character: draw(draw_tms_character(relative_start, relative_end), to_tms_pixel_clock); break;
|
||||
case LineMode::Text: draw(draw_tms_text(relative_start, relative_end), to_tms_pixel_clock); break;
|
||||
case LineMode::SMS: draw(draw_sms(relative_start, relative_end, cram_value), Clock::TMSPixel); break;
|
||||
case LineMode::Character: draw(draw_tms_character(relative_start, relative_end), Clock::TMSPixel); break;
|
||||
case LineMode::Text: draw(draw_tms_text(relative_start, relative_end), Clock::TMSPixel); break;
|
||||
|
||||
case LineMode::Refresh: break; /* Dealt with elsewhere. */
|
||||
}
|
||||
@ -437,7 +437,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
|
||||
if(end == line_buffer.next_border_column) {
|
||||
const int length = line_buffer.next_border_column - line_buffer.first_pixel_output_column;
|
||||
this->crt_.output_data(this->clock_converter_.to_crt_clock(length), line_buffer.pixel_count);
|
||||
this->crt_.output_data(from_internal<personality, Clock::CRT>(length), line_buffer.pixel_count);
|
||||
this->pixel_origin_ = this->pixel_target_ = nullptr;
|
||||
this->asked_for_write_area_ = false;
|
||||
}
|
||||
@ -480,7 +480,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::output_border(int cycles, [[maybe_unused]] uint32_t cram_dot) {
|
||||
cycles = this->clock_converter_.to_crt_clock(cycles);
|
||||
cycles = from_internal<personality, Clock::CRT>(cycles);
|
||||
const uint32_t border_colour =
|
||||
is_sega_vdp(personality) ?
|
||||
master_system_.colour_ram[16 + background_colour_] :
|
||||
|
@ -15,14 +15,48 @@
|
||||
namespace TI {
|
||||
namespace TMS {
|
||||
|
||||
// Timing constants.
|
||||
template <Personality, typename Enable = void> struct Timing {};
|
||||
enum class Clock {
|
||||
Internal,
|
||||
TMSPixel,
|
||||
TMSMemoryWindow,
|
||||
CRT
|
||||
};
|
||||
|
||||
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
||||
static_assert(
|
||||
is_classic_vdp(personality) ||
|
||||
is_yamaha_vdp(personality) ||
|
||||
(personality == Personality::MDVDP)
|
||||
);
|
||||
|
||||
switch(clk) {
|
||||
case Clock::TMSPixel: return 342;
|
||||
case Clock::TMSMemoryWindow: return 171;
|
||||
case Clock::CRT: return 1368;
|
||||
case Clock::Internal:
|
||||
if constexpr (is_classic_vdp(personality)) {
|
||||
return 342;
|
||||
} else if constexpr (is_yamaha_vdp(personality)) {
|
||||
return 1368;
|
||||
} else if constexpr (personality == Personality::MDVDP) {
|
||||
return 3420;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
|
||||
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
|
||||
}
|
||||
|
||||
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
|
||||
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 <int _CyclesPerLine> struct StandardTiming {
|
||||
template <Personality personality> struct StandardTiming {
|
||||
/// The total number of internal cycles per line of output.
|
||||
constexpr static int CyclesPerLine = _CyclesPerLine;
|
||||
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.
|
||||
@ -58,22 +92,8 @@ template <int _CyclesPerLine> struct StandardTiming {
|
||||
constexpr static int StartOfLeftBorder = 73 * CyclesPerLine / 342;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct Timing<personality, std::enable_if_t<is_classic_vdp(personality)>>: public StandardTiming<342> {
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct Timing<personality, std::enable_if_t<is_yamaha_vdp(personality)>>: public StandardTiming<1368> {
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Timing<Personality::MDVDP>: public StandardTiming<3420> {
|
||||
// Implementation note: descending from StandardTiming works as long as the numbers computed
|
||||
// end up being a multiple of 2.5. In practice they're all multiples of 10, so that's guaranteed.
|
||||
// Coupled logic is as per to_crt_clock below.
|
||||
};
|
||||
|
||||
constexpr int TMSAccessWindowsPerLine = 171;
|
||||
/// Provides concrete, specific timing for the nominated personality.
|
||||
template <Personality personality> struct Timing: public StandardTiming<personality> {};
|
||||
|
||||
/*!
|
||||
This implementation of the TMS, etc mediates between three clocks:
|
||||
@ -164,80 +184,6 @@ template <Personality personality> class ClockConverter {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts a position in internal cycles to its corresponding position
|
||||
on the TMS memory-access clock, i.e. scales to 171 clocks per line.
|
||||
*/
|
||||
static constexpr int to_tms_access_clock(int source) {
|
||||
switch(personality) {
|
||||
default:
|
||||
return source >> 1;
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source >> 3;
|
||||
|
||||
case Personality::MDVDP:
|
||||
return source / 20;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts a position in TMS access cycles back to one at the native
|
||||
clock rate.
|
||||
*/
|
||||
static constexpr int from_tms_access_clock(int source) {
|
||||
switch(personality) {
|
||||
default:
|
||||
return source << 1;
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source << 3;
|
||||
|
||||
case Personality::MDVDP:
|
||||
return source * 20;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts a position in internal cycles to its corresponding position
|
||||
on the TMS pixel clock, i.e. scales to 342 clocks per line.
|
||||
*/
|
||||
static constexpr int to_tms_pixel_clock(int source) {
|
||||
switch(personality) {
|
||||
default:
|
||||
return source;
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source >> 2;
|
||||
|
||||
case Personality::MDVDP:
|
||||
return source / 10;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Convers a position in internal cycles to its corresponding position
|
||||
on the CRT's output clock, which [TODO] is clocked so that
|
||||
1368 cycles is 228 NTSC colour cycles.
|
||||
*/
|
||||
static constexpr int to_crt_clock(int source) {
|
||||
switch(personality) {
|
||||
default:
|
||||
return source << 2;
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source;
|
||||
|
||||
case Personality::MDVDP:
|
||||
return (source * 2) / 5;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
// Holds current residue in conversion from the external to
|
||||
// internal clock.
|
||||
|
@ -47,7 +47,7 @@
|
||||
case n
|
||||
|
||||
#define external_slot(n) \
|
||||
slot(n): do_external_slot(clock_converter_.from_tms_access_clock(n));
|
||||
slot(n): do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(n));
|
||||
|
||||
#define external_slots_2(n) \
|
||||
external_slot(n); \
|
||||
@ -280,7 +280,7 @@ template<bool use_end> void Base<personality>::fetch_tms_character(int start, in
|
||||
|
||||
slot(31):
|
||||
sprite_selection_buffer.reset_sprite_collection();
|
||||
do_external_slot(clock_converter_.from_tms_access_clock(31));
|
||||
do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(31));
|
||||
external_slots_2(32);
|
||||
external_slot(34);
|
||||
|
||||
@ -431,7 +431,7 @@ template<bool use_end> void Base<personality>::fetch_sms(int start, int end) {
|
||||
|
||||
slot(29):
|
||||
sprite_selection_buffer.reset_sprite_collection();
|
||||
do_external_slot(clock_converter_.from_tms_access_clock(29));
|
||||
do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(29));
|
||||
external_slot(30);
|
||||
|
||||
sprite_y_read(31, 0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user