diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index d2af0dd6f..47df984ea 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -60,7 +60,7 @@ Machine::Machine() : void Machine::setup_output(float aspect_ratio) { speaker_.reset(new Speaker); - crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, 1)); + crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, false, 1)); crt_->set_output_device(Outputs::CRT::Television); // this is the NTSC phase offset function; see below for PAL @@ -93,7 +93,7 @@ void Machine::switch_region() "return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" "}"); - crt_->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1); + crt_->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1, true); is_pal_region_ = true; speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index aee65f36d..c8e156915 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -43,8 +43,7 @@ Machine::Machine() : display_output_position_(0), audio_output_position_(0), current_pixel_line_(-1), - use_fast_tape_hack_(false), - phase_(0) + use_fast_tape_hack_(false) { memset(key_states_, 0, sizeof(key_states_)); memset(palette_, 0xf, sizeof(palette_)); @@ -452,8 +451,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin frame_cycles_ += cycles; - if(!(frame_cycles_&127)) phase_ += 64; - // deal with frame wraparound by updating the two dependent subsystems // as though the exact end of frame had been hit, then reset those // and allow the frame cycle counter to assume its real value @@ -874,7 +871,7 @@ inline void Machine::update_display() if(this_cycle < 24) { if(final_cycle < 24) return; - crt_->output_colour_burst((24-9) * crt_cycles_multiplier, phase_, 12); + crt_->output_default_colour_burst((24-9) * crt_cycles_multiplier); display_output_position_ += 24-9; this_cycle = 24; // TODO: phase shouldn't be zero on every line diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 38ccaa8ee..980de1e7d 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -135,7 +135,6 @@ class Machine: // Counters related to simultaneous subsystems unsigned int frame_cycles_, display_output_position_; unsigned int audio_output_position_, audio_output_position_error_; - uint8_t phase_; struct { uint16_t forty1bpp[256]; diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 3326bde69..b8838e53b 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -24,7 +24,6 @@ VideoOutput::VideoOutput(uint8_t *memory) : frame_counter_(0), counter_(0), is_graphics_mode_(false), character_set_base_address_(0xb400), - phase_(0), v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition), counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false), crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) @@ -95,12 +94,10 @@ void VideoOutput::run_for_cycles(int number_of_cycles) paper_ = 0x00; use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false; set_character_set_base_address(); - phase_ += 64; pixel_target_ = (uint16_t *)crt_->allocate_write_area(240); if(!counter_) { - phase_ += 3; // TODO: incorporate all the lines that were missed frame_counter_++; v_sync_start_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncStartPosition : PAL50VSyncStartPosition; @@ -228,7 +225,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) else if(h_counter < 56) { cycles_run_for = 56 - h_counter; - clamp(crt_->output_colour_burst(2 * 6, phase_, 128)); + clamp(crt_->output_default_colour_burst(2 * 6)); } else { diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index b8126558e..fe7b91d53 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -43,8 +43,6 @@ class VideoOutput { bool use_alternative_character_set_; bool use_double_height_characters_; bool blink_text_; - - uint8_t phase_; }; } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 0096fc0c9..8e7694761 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -14,7 +14,7 @@ using namespace Outputs::CRT; -void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) +void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) { openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); @@ -29,6 +29,11 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV." time_multiplier_ = IntermediateBufferWidth / cycles_per_line; + phase_denominator_ = cycles_per_line * colour_cycle_denominator; + phase_numerator_ = 0; + colour_cycle_numerator_ = colour_cycle_numerator * time_multiplier_; + phase_alternates_ = should_alternate; + is_alernate_line_ &= phase_alternates_; unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; // generate timing values implied by the given arbuments @@ -50,11 +55,11 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display switch(displayType) { case DisplayType::PAL50: - set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500); // i.e. 283.7516 + set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516 break; case DisplayType::NTSC60: - set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 545, 2); + set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 545, 2, false); break; } } @@ -67,12 +72,13 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : is_writing_composite_run_(false), delegate_(nullptr), frames_since_last_delegate_call_(0), - openGL_output_builder_(buffer_depth) {} + openGL_output_builder_(buffer_depth), + is_alernate_line_(false) {} -CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth) : +CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth) : CRT(common_output_divisor, buffer_depth) { - set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); + set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, should_alternate); } CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : @@ -124,6 +130,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo // get the next sync event and its timing; hsync request is instantaneous (being edge triggered) so // set it to false for the next run through this loop (if any) unsigned int next_run_length = std::min(time_until_vertical_sync_event, time_until_horizontal_sync_event); + phase_numerator_ += next_run_length * colour_cycle_numerator_; + phase_numerator_ %= phase_denominator_; hsync_requested = false; vsync_requested = false; @@ -171,6 +179,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo (honoured_event == Flywheel::SyncEvent::StartRetrace && is_writing_composite_run_) || (honoured_event == Flywheel::SyncEvent::EndRetrace && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace()); + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) is_alernate_line_ ^= phase_alternates_; + if(needs_endpoint) { if( @@ -329,6 +339,17 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint output_scan(&scan); } +void CRT::output_default_colour_burst(unsigned int number_of_cycles) +{ + Scan scan { + .type = Scan::Type::ColourBurst, + .number_of_cycles = number_of_cycles, + .phase = (uint8_t)((phase_numerator_ * 255) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)), + .amplitude = 32 + }; + output_scan(&scan); +} + void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index d2482c25b..0b07910f9 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -46,7 +46,6 @@ class CRT { int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync unsigned int sync_period_; - // each call to output_* generates a scan. A two-slot queue for scans allows edge extensions. struct Scan { enum Type { Sync, Level, Data, Blank, ColourBurst @@ -64,6 +63,9 @@ class CRT { uint16_t colour_burst_time_; bool is_writing_composite_run_; + unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_; + bool is_alernate_line_, phase_alternates_; + // the outer entry point for dispatching output_sync, output_blank, output_level and output_data void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type); @@ -91,7 +93,7 @@ class CRT { @param cycles_per_line The clock rate at which this CRT will be driven, specified as the number of cycles expected to take up one whole scanline of the display. - + @param common_output_divisor The greatest a priori common divisor of all cycle counts that will be supplied to @c output_sync, @c output_data, etc; supply 1 if no greater divisor is known. For many machines output will run at a fixed multiple of the clock rate; knowing this divisor can improve @@ -113,7 +115,7 @@ class CRT { @see @c set_rgb_sampling_function , @c set_composite_sampling_function */ - CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth); + CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth); /*! Constructs the CRT with the specified clock rate, with the display height and colour subcarrier frequency dictated by a standard display type and with the requested number of @@ -126,7 +128,7 @@ class CRT { /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had been provided at construction. */ - void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator); + void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate); /*! Resets the CRT with new timing information derived from a new display type. The CRT then continues as though the new timing had been provided at construction. */ @@ -175,6 +177,12 @@ class CRT { */ void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude); + /*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude. + + @param number_of_cycles The length of the colour burst; + */ + void output_default_colour_burst(unsigned int number_of_cycles); + /*! Attempts to allocate the given number of output samples for writing. The beginning of the most recently allocated area is used as the start