From 8b0d550b8169ebdd27f7fa59af72971d9302ef1f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 8 Nov 2019 22:18:47 -0500 Subject: [PATCH] Attempts to move vertical sync out to cycle 30. --- Machines/AtariST/Video.cpp | 108 ++++++++++++++++++++++--------------- Machines/AtariST/Video.hpp | 28 +++++++--- 2 files changed, 86 insertions(+), 50 deletions(-) diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index 2eb6b15c9..a3cff91be 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -112,17 +112,22 @@ void Video::run_for(HalfCycles duration) { int next_event = line_length_; // Check the explicitly-placed events. - if(horizontal_timings.reset_blank > x) next_event = std::min(next_event, horizontal_timings.reset_blank); - if(horizontal_timings.set_blank > x) next_event = std::min(next_event, horizontal_timings.set_blank); - if(horizontal_timings.reset_enable > x) next_event = std::min(next_event, horizontal_timings.reset_enable); - if(horizontal_timings.set_enable > x) next_event = std::min(next_event, horizontal_timings.set_enable); + if(horizontal_timings.reset_blank > x_) next_event = std::min(next_event, horizontal_timings.reset_blank); + if(horizontal_timings.set_blank > x_) next_event = std::min(next_event, horizontal_timings.set_blank); + if(horizontal_timings.reset_enable > x_) next_event = std::min(next_event, horizontal_timings.reset_enable); + if(horizontal_timings.set_enable > x_) next_event = std::min(next_event, horizontal_timings.set_enable); // Check for events that are relative to existing latched state. - if(line_length_ - 50*2 > x) next_event = std::min(next_event, line_length_ - 50*2); - if(line_length_ - 10*2 > x) next_event = std::min(next_event, line_length_ - 10*2); + if(line_length_ - 50*2 > x_) next_event = std::min(next_event, line_length_ - 50*2); + if(line_length_ - 10*2 > x_) next_event = std::min(next_event, line_length_ - 10*2); + + // Also, a vertical sync event might intercede. + if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ < 30*2 && next_event >= 30*2) { + next_event = 30*2; + } // Determine current output mode and number of cycles to output for. - const int run_length = std::min(integer_duration, next_event - x); + const int run_length = std::min(integer_duration, next_event - x_); enum class OutputMode { Sync, Blank, Border, Pixels @@ -173,8 +178,8 @@ void Video::run_for(HalfCycles duration) { // There will be pixels this line, subject to the shifter pipeline. // Divide into 8-[half-]cycle windows; at the start of each window fetch a word, // and during the rest of the window, shift out. - int start_column = x >> 3; - const int end_column = (x + run_length) >> 3; + int start_column = x_ >> 3; + const int end_column = (x_ + run_length) >> 3; // Rules obeyed below: // @@ -186,11 +191,11 @@ void Video::run_for(HalfCycles duration) { shift_out(run_length); } else { // Continue the current column if partway across. - if(x&7) { + if(x_&7) { // If at least one column boundary is crossed, complete this column. // Otherwise the run_length is clearly less than 8 and within this column, // so go for the entirety of it. - shift_out(8 - (x & 7)); + shift_out(8 - (x_ & 7)); ++start_column; latch_word(); } @@ -203,52 +208,60 @@ void Video::run_for(HalfCycles duration) { } // Output the start of the next column, if necessary. - if(start_column != end_column && (x + run_length) & 7) { - shift_out((x + run_length) & 7); + if(start_column != end_column && (x_ + run_length) & 7) { + shift_out((x_ + run_length) & 7); } } break; } // Check for whether line length should have been latched during this run. - if(x <= 54*2 && (x + run_length) > 54*2) line_length_ = horizontal_timings.length; + if(x_ <= 54*2 && (x_ + run_length) > 54*2) line_length_ = horizontal_timings.length; // Make a decision about vertical state on cycle 502. - if(x <= 502*2 && (x + run_length) > 502*2) { - next_y = y + 1; + if(x_ <= 502*2 && (x_ + run_length) > 502*2) { + next_y_ = y_ + 1; next_vertical_ = vertical_; + next_vertical_.sync_schedule = VerticalState::SyncSchedule::None; // Use vertical_parameters to get parameters for the current output frequency. - if(y == vertical_timings.set_enable) { + if(next_y_ == vertical_timings.set_enable) { next_vertical_.enable = true; - } else if(y == vertical_timings.reset_enable) { + } else if(next_y_ == vertical_timings.reset_enable) { next_vertical_.enable = false; - } else if(y == vertical_timings.height) { - next_y = 0; - next_vertical_.sync = true; + } else if(next_y_ == vertical_timings.height) { + next_y_ = 0; + next_vertical_.sync_schedule = VerticalState::SyncSchedule::Begin; current_address_ = base_address_ >> 1; - } else if(y == 3) { - next_vertical_.sync = false; + } else if(next_y_ == 3) { + next_vertical_.sync_schedule = VerticalState::SyncSchedule::End; } } // Apply the next event. - x += run_length; + x_ += run_length; integer_duration -= run_length; - if(horizontal_timings.reset_blank == x) horizontal_.blank = false; - else if(horizontal_timings.set_blank == x) horizontal_.blank = true; - else if(horizontal_timings.reset_enable == x) horizontal_.enable = false; - else if(horizontal_timings.set_enable == x) horizontal_.enable = true; - else if(line_length_ - 50*2 == x) horizontal_.sync = true; - else if(line_length_ - 10*2 == x) horizontal_.sync = false; + // Check horizontal events. + if(horizontal_timings.reset_blank == x_) horizontal_.blank = false; + else if(horizontal_timings.set_blank == x_) horizontal_.blank = true; + else if(horizontal_timings.reset_enable == x_) horizontal_.enable = false; + else if(horizontal_timings.set_enable == x_) horizontal_.enable = true; + else if(line_length_ - 50*2 == x_) horizontal_.sync = true; + else if(line_length_ - 10*2 == x_) horizontal_.sync = false; + + // Check vertical events. + if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ == 30*2) { + vertical_.sync = vertical_.sync_schedule == VerticalState::SyncSchedule::Begin; + vertical_.enable &= !vertical_.sync; + } // Check whether the terminating event was end-of-line; if so then advance // the vertical bits of state. - if(x == line_length_) { - x = 0; + if(x_ == line_length_) { + x_ = 0; vertical_ = next_vertical_; - y = next_y; + y_ = next_y_; } } } @@ -360,7 +373,7 @@ bool Video::display_enabled() { HalfCycles Video::get_next_sequence_point() { // The next sequence point will be whenever display_enabled, vsync or hsync next changes. - // Sequence of events within a line: + // Sequence of events within a standard line: // // 1) blank disabled; // 2) display enabled; @@ -369,32 +382,40 @@ HalfCycles Video::get_next_sequence_point() { // 5) sync enabled; // 6) sync disabled; // 7) end-of-line, potential vertical event. + // + // If this line has a vertical sync event on it, there will also be an event at cycle 30, + // which will always falls somewhere between (1) and (4) but might or might not be in the + // visible area. const auto horizontal_timings = horizontal_parameters(field_frequency_); +// const auto vertical_timings = vertical_parameters(field_frequency_); // If this is a vertically-enabled line, check for the display enable boundaries. if(vertical_.enable) { - if(x < horizontal_timings.set_enable) return HalfCycles(horizontal_timings.set_enable - x); - if(x < horizontal_timings.reset_enable) return HalfCycles(horizontal_timings.reset_enable - x); + // TODO: what if there's a sync event scheduled for this line? + if(x_ < horizontal_timings.set_enable) return HalfCycles(horizontal_timings.set_enable - x_); + if(x_ < horizontal_timings.reset_enable) return HalfCycles(horizontal_timings.reset_enable - x_); + } else { + if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && (x_ < 30*2)) { + return HalfCycles(30*2 - x_); + } } // Test for beginning and end of sync. - if(x < line_length_ - 50) return HalfCycles(line_length_ - 50 - x); - if(x < line_length_ - 10) return HalfCycles(line_length_ - 10 - x); + if(x_ < line_length_ - 50) return HalfCycles(line_length_ - 50 - x_); + if(x_ < line_length_ - 10) return HalfCycles(line_length_ - 10 - x_); // Okay, then, it depends on the next line. If the next line is the start or end of vertical sync, // it's that. - const auto vertical_timings = horizontal_parameters(field_frequency_); - if(y+1 == vertical_timings.length || y+1 == 3) return HalfCycles(line_length_ - x); +// if(y_+1 == vertical_timings.height || y_+1 == 3) return HalfCycles(line_length_ - x_); - // It wasn't any of those, so it's display enabled on the next line. - return HalfCycles(line_length_ + horizontal_timings.set_enable - x); + // It wasn't any of those, so as a temporary expedient, just supply end of line. + return HalfCycles(line_length_ - x_); } // MARK: - IO dispatch uint16_t Video::read(int address) { -// LOG("[Video] read " << PADHEX(2) << (address & 0x3f)); address &= 0x3f; switch(address) { default: @@ -417,7 +438,6 @@ uint16_t Video::read(int address) { } void Video::write(int address, uint16_t value) { -// LOG("[Video] write " << PADHEX(2) << int(value) << " to " << PADHEX(2) << (address & 0x3f)); address &= 0x3f; switch(address) { default: break; diff --git a/Machines/AtariST/Video.hpp b/Machines/AtariST/Video.hpp index f2b996bab..77bdce5e6 100644 --- a/Machines/AtariST/Video.hpp +++ b/Machines/AtariST/Video.hpp @@ -59,7 +59,7 @@ class Video { uint16_t *ram_; uint16_t line_buffer_[256]; - int x = 0, y = 0, next_y = 0; + int x_ = 0, y_ = 0, next_y_ = 0; void output_border(int duration); uint16_t video_mode_ = 0; @@ -71,11 +71,25 @@ class Video { } output_bpp_; void update_output_mode(); - struct State { + struct HorizontalState { bool enable = false; bool blank = false; bool sync = false; - } horizontal_, vertical_, next_vertical_; + } horizontal_; + struct VerticalState { + bool enable = false; + bool blank = false; + + enum class SyncSchedule { + /// No sync events this line. + None, + /// Sync should begin during this horizontal line. + Begin, + /// Sync should end during this horizontal line. + End, + } sync_schedule = SyncSchedule::None; + bool sync = false; + } vertical_, next_vertical_; int line_length_ = 1024; int data_latch_position_ = 0; @@ -93,9 +107,11 @@ class Video { int cycles_output = 0; OutputBpp output_bpp; void flush(Outputs::CRT::CRT &crt) { - if(cycles_output) crt.output_data(cycles_output, size_t(pixels_output)); - pixels_output = cycles_output = 0; - pixel_pointer = nullptr; + if(cycles_output) { + crt.output_data(cycles_output, size_t(pixels_output)); + pixels_output = cycles_output = 0; + pixel_pointer = nullptr; + } } void allocate(Outputs::CRT::CRT &crt) { flush(crt);