From 72e0bfecc1f5f28523384312f033252045b7d5fe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 7 Jan 2023 14:57:32 -0500 Subject: [PATCH] Edge towards clock-independent line composition. --- Components/9918/Implementation/9918.cpp | 21 ++++--- .../9918/Implementation/ClockConverter.hpp | 57 +++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp index 39c78b843..9a36f478e 100644 --- a/Components/9918/Implementation/9918.cpp +++ b/Components/9918/Implementation/9918.cpp @@ -319,9 +319,11 @@ void TMS9918::run_for(const HalfCycles cycles) { int read_cycles = target_read_cycles - read_cycles_performed; if(!read_cycles) continue; + // Grab the next CRAM dot value and schedule a break in output if applicable. const uint32_t cram_value = next_cram_value; - next_cram_value = 0; if constexpr (is_sega_vdp(personality)) { + next_cram_value = 0; + if(!this->upcoming_cram_dots_.empty() && this->upcoming_cram_dots_.front().location.row == this->read_pointer_.row) { int time_until_dot = this->upcoming_cram_dots_.front().location.column - this->read_pointer_.column; @@ -353,16 +355,21 @@ void TMS9918::run_for(const HalfCycles cycles) { #define border(left, right) intersect(left, right, this->output_border(end - start, cram_value)) - // TODO: CRT clock might need to change? if(line_buffer.line_mode == LineMode::Refresh || this->read_pointer_.row > this->mode_timing_.pixel_lines) { - if(this->read_pointer_.row >= this->mode_timing_.first_vsync_line && this->read_pointer_.row < this->mode_timing_.first_vsync_line+4) { + if( + this->read_pointer_.row >= this->mode_timing_.first_vsync_line && + this->read_pointer_.row < this->mode_timing_.first_vsync_line + 4 + ) { // Vertical sync. - if(end_column == 342) { - this->crt_.output_sync(342 * 4); + // TODO: the Mega Drive supports interlaced video, I think? + if(end_column == Timing::CyclesPerLine) { + this->crt_.output_sync( + this->clock_converter_.to_crt_clock(Timing::CyclesPerLine) + ); } } else { // Right border. - border(0, 15); + border(0, Timing::EndOfRightBorder); // Blanking region; total length is 58 cycles, // and 58+15 = 73. So output the lot when the @@ -409,7 +416,7 @@ void TMS9918::run_for(const HalfCycles cycles) { const int relative_start = start - line_buffer.first_pixel_output_column; const int relative_end = end - line_buffer.first_pixel_output_column; switch(line_buffer.line_mode) { - case LineMode::SMS: this->draw_sms(relative_start, relative_end, cram_value); break; + case LineMode::SMS: this->draw_sms(relative_start, relative_end, cram_value); break; case LineMode::Character: this->draw_tms_character(relative_start, relative_end); break; case LineMode::Text: this->draw_tms_text(relative_start, relative_end); break; diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp index a9d562aca..3c85da37c 100644 --- a/Components/9918/Implementation/ClockConverter.hpp +++ b/Components/9918/Implementation/ClockConverter.hpp @@ -40,24 +40,61 @@ struct Timing> { /// The final internal cycle at which pixels will be output text mode. constexpr static int LastTextCycle = 334; + + // For the below, the fixed portion of line layout is: + // + // [0, EndOfRightBorder): right border colour + // [EndOfRightBorder, StartOfSync): blank + // [StartOfSync, EndOfSync): sync + // [EndOfSync, StartOfColourBurst): blank + // [StartOfColourBurst, EndOfColourBurst): the colour burst + // [EndOfColourBurst, StartOfLeftBorder): blank + // + // The region from StartOfLeftBorder until the end is then filled with + // some combination of pixels and more border, depending on the vertical + // position of this line and the current screen mode. + constexpr static int EndOfRightBorder = 15; + constexpr static int StartOfSync = 23; + constexpr static int EndOfSync = 49; + constexpr static int StartOfColourBurst = 51; + constexpr static int EndOfColourBurst = 65; + constexpr static int StartOfLeftBorder = 73; }; template struct Timing> { constexpr static int CyclesPerLine = 1368; + constexpr static int VRAMAccessDelay = 6; constexpr static int FirstPixelCycle = 344; constexpr static bool SupportsTextMode = true; constexpr static int FirstTextCycle = 376; constexpr static int LastTextCycle = 1336; + + constexpr static int EndOfRightBorder = 15 * 4; + constexpr static int StartOfSync = 23 * 4; + constexpr static int EndOfSync = 49 * 4; + constexpr static int StartOfColourBurst = 51 * 4; + constexpr static int EndOfColourBurst = 65 * 4; + constexpr static int StartOfLeftBorder = 73 * 4; }; template <> struct Timing { constexpr static int CyclesPerLine = 3420; + constexpr static int VRAMAccessDelay = 6; constexpr static int FirstPixelCycle = 860; constexpr static bool SupportsTextMode = false; + + // Implementation note: these currently need to be multiples of 2.5 + // per the stateless Mega Drive -> CRT clock conversion. + constexpr static int EndOfRightBorder = 15 * 10; + constexpr static int StartOfSync = 23 * 10; + constexpr static int EndOfSync = 49 * 10; + constexpr static int StartOfColourBurst = 51 * 10; + constexpr static int EndOfColourBurst = 65 * 10; + constexpr static int StartOfLeftBorder = 73 * 10; }; constexpr int TMSAccessWindowsPerLine = 171; @@ -170,6 +207,26 @@ template class ClockConverter { } } + /*! + 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 * 4; + + 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.