1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-30 22:29:56 +00:00

Avoid hand-writing all the various conversions.

This commit is contained in:
Thomas Harte 2023-01-09 22:34:56 -05:00
parent c0fe88a5bb
commit fd14829992
3 changed files with 60 additions and 114 deletions

View File

@ -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_] :

View File

@ -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.

View File

@ -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);