diff --git a/ClockReceiver/DeferredQueue.hpp b/ClockReceiver/DeferredQueue.hpp index d2680f322..be1c7b01e 100644 --- a/ClockReceiver/DeferredQueue.hpp +++ b/ClockReceiver/DeferredQueue.hpp @@ -13,62 +13,68 @@ #include /*! - A DeferredQueue maintains a list of ordered actions and the times at which - they should happen, and divides a total execution period up into the portions - that occur between those actions, triggering each action when it is reached. + Provides the logic to insert into and traverse a list of future scheduled items. */ template class DeferredQueue { public: - /// Constructs a DeferredQueue that will call target(period) in between deferred actions. - DeferredQueue(std::function &&target) : target_(std::move(target)) {} - /*! Schedules @c action to occur in @c delay units of time. - - Actions must be scheduled in the order they will occur. It is undefined behaviour - to schedule them out of order. */ void defer(TimeUnit delay, const std::function &action) { - pending_actions_.emplace_back(delay, action); - } - - /*! - Runs for @c length units of time. - - The constructor-supplied target will be called with one or more periods that add up to @c length; - any scheduled actions will be called between periods. - */ - void run_for(TimeUnit length) { - // If there are no pending actions, just run for the entire length. - // This should be the normal branch. - if(pending_actions_.empty()) { - target_(length); + // Apply immediately if there's no delay (or a negative delay). + if(delay <= TimeUnit(0)) { + action(); return; } - // Divide the time to run according to the pending actions. - while(length > TimeUnit(0)) { - TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay); - target_(next_period); - length -= next_period; + if(!pending_actions_.empty()) { + // Otherwise enqueue, having subtracted the delay for any preceding events, + // and subtracting from the subsequent, if any. + auto insertion_point = pending_actions_.begin(); + while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) { + delay -= insertion_point->delay; + ++insertion_point; + } + if(insertion_point != pending_actions_.end()) { + insertion_point->delay -= delay; + } - off_t performances = 0; - for(auto &action: pending_actions_) { - action.delay -= next_period; - if(!action.delay) { - action.action(); - ++performances; - } - } - if(performances) { - pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances); + pending_actions_.emplace(insertion_point, delay, action); + } else { + pending_actions_.emplace_back(delay, action); + } + } + + /*! + @returns The amount of time until the next enqueued action will occur, + or TimeUnit(-1) if the queue is empty. + */ + TimeUnit time_until_next_action() { + if(pending_actions_.empty()) return TimeUnit(-1); + return pending_actions_.front().delay; + } + + /*! + Advances the queue the specified amount of time, performing any actions it reaches. + */ + void advance(TimeUnit time) { + auto erase_iterator = pending_actions_.begin(); + while(erase_iterator != pending_actions_.end()) { + erase_iterator->delay -= time; + if(erase_iterator->delay <= TimeUnit(0)) { + time = -erase_iterator->delay; + erase_iterator->action(); + ++erase_iterator; + } else { + break; } } + if(erase_iterator != pending_actions_.begin()) { + pending_actions_.erase(pending_actions_.begin(), erase_iterator); + } } private: - std::function target_; - // The list of deferred actions. struct DeferredAction { TimeUnit delay; @@ -79,4 +85,40 @@ template class DeferredQueue { std::vector pending_actions_; }; +/*! + A DeferredQueue maintains a list of ordered actions and the times at which + they should happen, and divides a total execution period up into the portions + that occur between those actions, triggering each action when it is reached. + + This list is efficient only for short queues. +*/ +template class DeferredQueuePerformer: public DeferredQueue { + public: + /// Constructs a DeferredQueue that will call target(period) in between deferred actions. + DeferredQueuePerformer(std::function &&target) : target_(std::move(target)) {} + + /*! + Runs for @c length units of time. + + The constructor-supplied target will be called with one or more periods that add up to @c length; + any scheduled actions will be called between periods. + */ + void run_for(TimeUnit length) { + auto time_to_next = DeferredQueue::time_until_next_action(); + while(time_to_next != TimeUnit(-1) && time_to_next <= length) { + target_(time_to_next); + length -= time_to_next; + DeferredQueue::advance(time_to_next); + } + + DeferredQueue::advance(length); + target_(length); + + // TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe? + } + + private: + std::function target_; +}; + #endif /* DeferredQueue_h */ diff --git a/ClockReceiver/JustInTime.hpp b/ClockReceiver/JustInTime.hpp index ad328dbe4..90150138e 100644 --- a/ClockReceiver/JustInTime.hpp +++ b/ClockReceiver/JustInTime.hpp @@ -76,6 +76,48 @@ template class RealTimeActor { + public: + template RealTimeActor(Args&&... args) : object_(std::forward(args)...) {} + + forceinline void operator += (const LocalTimeScale &rhs) { + if constexpr (multiplier == 1 && divider == 1) { + object_.run_for(TargetTimeScale(rhs)); + return; + } + + if constexpr (multiplier == 1) { + accumulated_time_ += rhs; + } else { + accumulated_time_ += rhs * multiplier; + } + + if constexpr (divider == 1) { + const auto duration = accumulated_time_.template flush(); + object_.run_for(duration); + } else { + const auto duration = accumulated_time_.template divide(LocalTimeScale(divider)); + if(duration > TargetTimeScale(0)) + object_.run_for(duration); + } + } + + forceinline T *operator->() { return &object_; } + forceinline const T *operator->() const { return &object_; } + forceinline T *last_valid() { return &object_; } + forceinline void flush() {} + + private: + T object_; + LocalTimeScale accumulated_time_; +}; + /*! A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue. Any time the amount of accumulated time crosses a threshold provided at construction time, diff --git a/Machines/Apple/AppleII/Video.hpp b/Machines/Apple/AppleII/Video.hpp index ac7085a07..ec61d5da0 100644 --- a/Machines/Apple/AppleII/Video.hpp +++ b/Machines/Apple/AppleII/Video.hpp @@ -255,7 +255,7 @@ class VideoBase { void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; // Maintain a DeferredQueue for delayed mode switches. - DeferredQueue deferrer_; + DeferredQueuePerformer deferrer_; }; template class Video: public VideoBase { diff --git a/Machines/Apple/Macintosh/Video.cpp b/Machines/Apple/Macintosh/Video.cpp index 00276a419..211401e36 100644 --- a/Machines/Apple/Macintosh/Video.cpp +++ b/Machines/Apple/Macintosh/Video.cpp @@ -26,7 +26,7 @@ using namespace Apple::Macintosh; Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : audio_(audio), drive_speed_accumulator_(drive_speed_accumulator), - crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) { + crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) { crt_.set_display_type(Outputs::Display::DisplayType::RGB); crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f)); diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index c7e9a1a05..f1ed580bc 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -101,8 +101,10 @@ class ConcreteMachine: const bool is_early_tos = true; if(is_early_tos) { + rom_start_ = 0xfc0000; for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM; } else { + rom_start_ = 0xe00000; for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM; } @@ -245,7 +247,7 @@ class ConcreteMachine: case BusDevice::ROM: memory = rom_.data(); - address %= rom_.size(); + address -= rom_start_; break; case BusDevice::Floating: @@ -469,6 +471,7 @@ class ConcreteMachine: length -= cycles_until_video_event_; video_ += cycles_until_video_event_; cycles_until_video_event_ = video_->get_next_sequence_point(); + assert(cycles_until_video_event_ > HalfCycles(0)); mfp_->set_timer_event_input(1, video_->display_enabled()); update_interrupt_input(); @@ -504,6 +507,7 @@ class ConcreteMachine: std::vector ram_; std::vector rom_; + uint32_t rom_start_ = 0; enum class BusDevice { /// A mostly RAM page is one that returns ROM for the first 8 bytes, RAM elsewhere. @@ -567,7 +571,7 @@ class ConcreteMachine: GPIP 0: centronics busy */ mfp_->set_port_input( - 0x80 | // b7: Monochrome monitor detect (1 = is monochrome). + 0x80 | // b7: Monochrome monitor detect (0 = is monochrome). 0x40 | // b6: RS-232 ring indicator. (dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested). ((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested). diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 1170928f1..ca84ed3c2 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -13,6 +13,8 @@ #include #include +#define CYCLE(x) ((x) * 2) + using namespace Atari::ST; namespace { @@ -29,7 +31,7 @@ const struct VerticalParams { } vertical_params[3] = { {63, 263, 313}, // 47 rather than 63 on early machines. {34, 234, 263}, - {1, 401, 500} // 72 Hz mode: who knows? + {34, 434, 500} // Guesswork: (i) nobody ever recommends 72Hz mode for opening the top border, so it's likely to be the same as another mode; (ii) being the same as PAL feels too late. }; /// @returns The correct @c VerticalParams for output at @c frequency. @@ -37,8 +39,6 @@ const VerticalParams &vertical_parameters(Video::FieldFrequency frequency) { return vertical_params[int(frequency)]; } - -#define CYCLE(x) ((x) * 2) /*! Defines the horizontal counts at which mode-specific events will occur: horizontal enable being set and being reset, blank being set and reset, and the @@ -57,13 +57,23 @@ const struct HorizontalParams { const int set_blank; const int reset_blank; - const int length; + const int vertical_decision; + + LineLength length; } horizontal_params[3] = { - {CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(512)}, - {CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(508)}, - {CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(224)} // 72Hz mode doesn't set or reset blank. + {CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(502), { CYCLE(512), CYCLE(464), CYCLE(504) }}, + {CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(502), { CYCLE(508), CYCLE(460), CYCLE(500) }}, + {CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(214), { CYCLE(224), CYCLE(194), CYCLE(212) }} + // 72Hz mode doesn't set or reset blank. }; +// Re: 'vertical_decision': +// This is cycle 502 if in 50 or 60 Hz mode; in 70 Hz mode I've put it on cycle 214 +// in order to be analogous to 50 and 60 Hz mode. I have no idea where it should +// actually go. +// +// Ditto the horizontal sync timings for 72Hz are plucked out of thin air. + const HorizontalParams &horizontal_parameters(Video::FieldFrequency frequency) { return horizontal_params[int(frequency)]; } @@ -79,10 +89,10 @@ struct Checker { assert(horizontal.reset_blank < horizontal.set_enable); assert(horizontal.set_enable < horizontal.reset_enable); assert(horizontal.reset_enable < horizontal.set_blank); - assert(horizontal.set_blank+50 < horizontal.length); + assert(horizontal.set_blank+50 < horizontal.length.length); } else { assert(horizontal.set_enable < horizontal.reset_enable); - assert(horizontal.set_enable+50 delay -= duration_remaining; - if(erase_iterator->delay <= 0) { - duration_remaining = -erase_iterator->delay; - erase_iterator->apply(public_state_); - ++erase_iterator; - } else { - break; - } - } - if(erase_iterator != pending_events_.begin()) { - pending_events_.erase(pending_events_.begin(), erase_iterator); - } - } + assert(integer_duration >= 0); while(integer_duration) { + const auto horizontal_timings = horizontal_parameters(field_frequency_); + const auto vertical_timings = vertical_parameters(field_frequency_); + + // Determine time to next event; this'll either be one of the ones informally scheduled in here, + // or something from the deferral queue. + // Seed next event to end of line. - int next_event = line_length_; + int next_event = line_length_.length; + + const int next_deferred_event = deferrer_.time_until_next_action().as(); + if(next_deferred_event >= 0) + next_event = std::min(next_event, next_deferred_event + x_); // 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(next_load_toggle_ > x_) next_event = std::min(next_event, next_load_toggle_); // Check for events that are relative to existing latched state. - if(line_length_ - hsync_start > x_) next_event = std::min(next_event, line_length_ - hsync_start); - if(line_length_ - hsync_end > x_) next_event = std::min(next_event, line_length_ - hsync_end); + if(line_length_.hsync_start > x_) next_event = std::min(next_event, line_length_.hsync_start); + if(line_length_.hsync_end > x_) next_event = std::min(next_event, line_length_.hsync_end); // Also, a vertical sync event might intercede. if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ < vsync_x_position && next_event >= vsync_x_position) { @@ -189,6 +185,8 @@ void Video::advance(HalfCycles duration) { const bool hsync = horizontal_.sync; const bool vsync = vertical_.sync; + assert(run_length > 0); + // Ensure proper fetching irrespective of the output. if(load_) { const int since_load = x_ - load_base_; @@ -214,13 +212,16 @@ void Video::advance(HalfCycles duration) { } else if(!load_) { video_stream_.output(run_length, VideoStream::OutputMode::Pixels); } else { - const int since_load = x_ - load_base_; + const int start = x_ - load_base_; + const int end = start + run_length; // 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 = since_load >> 3; - const int end_column = (since_load + run_length) >> 3; + int start_column = start >> 3; + const int end_column = end >> 3; + const int start_offset = start & 7; + const int end_offset = end & 7; // Rules obeyed below: // @@ -229,35 +230,40 @@ void Video::advance(HalfCycles duration) { // was reloaded by the fetch depends on the FIFO. if(start_column == end_column) { + if(!start_offset) { + push_latched_data(); + } video_stream_.output(run_length, VideoStream::OutputMode::Pixels); } else { // Continue the current column if partway across. - if(since_load&7) { + if(start_offset) { // If at least one column boundary is crossed, complete this column. - video_stream_.output(8 - (since_load & 7), VideoStream::OutputMode::Pixels); + video_stream_.output(8 - start_offset, VideoStream::OutputMode::Pixels); ++start_column; // This starts a new column, so latch a new word. - push_latched_data(); } // Run for all columns that have their starts in this time period. int complete_columns = end_column - start_column; while(complete_columns--) { - video_stream_.output(8, VideoStream::OutputMode::Pixels); push_latched_data(); + video_stream_.output(8, VideoStream::OutputMode::Pixels); } // Output the start of the next column, if necessary. - if((since_load + run_length) & 7) { - video_stream_.output((since_load + run_length) & 7, VideoStream::OutputMode::Pixels); + if(end_offset) { + push_latched_data(); + video_stream_.output(end_offset, VideoStream::OutputMode::Pixels); } } } // Check for whether line length should have been latched during this run. - if(x_ <= CYCLE(54) && (x_ + run_length) > CYCLE(54)) line_length_ = horizontal_timings.length; + if(x_ < line_length_latch_position && (x_ + run_length) >= line_length_latch_position) { + line_length_ = horizontal_timings.length; + } - // Make a decision about vertical state on cycle 502. - if(x_ <= CYCLE(502) && (x_ + run_length) > CYCLE(502)) { + // Make a decision about vertical state on the appropriate cycle. + if(x_ < horizontal_timings.vertical_decision && (x_ + run_length) >= horizontal_timings.vertical_decision) { next_y_ = y_ + 1; next_vertical_ = vertical_; next_vertical_.sync_schedule = VerticalState::SyncSchedule::None; @@ -282,23 +288,17 @@ void Video::advance(HalfCycles duration) { // Apply the next event. x_ += run_length; + assert(integer_duration >= run_length); integer_duration -= run_length; + deferrer_.advance(HalfCycles(run_length)); // Check horizontal events; the first six are guaranteed to occur separately. 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_ - hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; } - else if(line_length_ - hsync_end == x_) horizontal_.sync = false; - - // next_load_toggle_ is less predictable; test separately because it may coincide - // with one of the above tests. - if(next_load_toggle_ == x_) { - next_load_toggle_ = -1; - load_ ^= true; - load_base_ = x_; - } + else if(line_length_.hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; } + else if(line_length_.hsync_end == x_) horizontal_.sync = false; // Check vertical events. if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ == vsync_x_position) { @@ -310,7 +310,7 @@ void Video::advance(HalfCycles duration) { // Check whether the terminating event was end-of-line; if so then advance // the vertical bits of state. - if(x_ == line_length_) { + if(x_ == line_length_.length) { x_ = 0; vertical_ = next_vertical_; y_ = next_y_; @@ -333,21 +333,30 @@ void Video::advance(HalfCycles duration) { // Chuck any deferred output changes into the queue. const bool next_display_enable = vertical_.enable && horizontal_.enable; if(display_enable != next_display_enable) { - // Schedule change in outwardly-visible DE line. - add_event(de_delay_period - integer_duration, next_display_enable ? Event::Type::SetDisplayEnable : Event::Type::ResetDisplayEnable); + // Schedule change in load line. + deferrer_.defer(load_delay_period, [this, next_display_enable] { + this->load_ = next_display_enable; + this->load_base_ = this->x_; + }); - // Schedule change in inwardly-visible effect. - next_load_toggle_ = x_ + 8; // 4 cycles = 8 half-cycles + // Schedule change in outwardly-visible DE line. + deferrer_.defer(de_delay_period, [this, next_display_enable] { + this->public_state_.display_enable = next_display_enable; + }); } if(horizontal_.sync != hsync) { // Schedule change in outwardly-visible hsync line. - add_event(hsync_delay_period - integer_duration, horizontal_.sync ? Event::Type::SetHsync : Event::Type::ResetHsync); + deferrer_.defer(hsync_delay_period, [this, next_horizontal_sync = horizontal_.sync] { + this->public_state_.hsync = next_horizontal_sync; + }); } if(vertical_.sync != vsync) { // Schedule change in outwardly-visible hsync line. - add_event(vsync_delay_period - integer_duration, vertical_.sync ? Event::Type::SetVsync : Event::Type::ResetVsync); + deferrer_.defer(vsync_delay_period, [this, next_vertical_sync = vertical_.sync] { + this->public_state_.vsync = next_vertical_sync; + }); } } } @@ -400,11 +409,12 @@ HalfCycles Video::get_next_sequence_point() { const auto horizontal_timings = horizontal_parameters(field_frequency_); - int event_time = line_length_; // Worst case: report end of line. + int event_time = line_length_.length; // Worst case: report end of line. // If any events are pending, give the first of those the chance to be next. - if(!pending_events_.empty()) { - event_time = std::min(event_time, x_ + pending_events_.front().delay); + const auto next_deferred_item = deferrer_.time_until_next_action(); + if(next_deferred_item != HalfCycles(-1)) { + event_time = std::min(event_time, x_ + next_deferred_item.as()); } // If this is a vertically-enabled line, check for the display enable boundaries, + the standard delay. @@ -423,11 +433,17 @@ HalfCycles Video::get_next_sequence_point() { } // Test for beginning and end of horizontal sync. - if(x_ < line_length_ - hsync_start + hsync_delay_period) { - event_time = std::min(line_length_ - hsync_start + hsync_delay_period, event_time); + if(x_ < line_length_.hsync_start + hsync_delay_period) { + event_time = std::min(line_length_.hsync_start + hsync_delay_period, event_time); + } + if(x_ < line_length_.hsync_end + hsync_delay_period) { + event_time = std::min(line_length_.hsync_end + hsync_delay_period, event_time); + } + + // Also factor in the line length latching time. + if(x_ < line_length_latch_position) { + event_time = std::min(line_length_latch_position, event_time); } - /* Hereby assumed: hsync end will be communicated at end of line: */ - static_assert(hsync_end == hsync_delay_period); // It wasn't any of those, just supply end of line. That's when the static_assert above assumes a visible hsync transition. return HalfCycles(event_time - x_); @@ -559,12 +575,12 @@ void Video::VideoStream::generate(int duration, OutputMode mode, bool is_termina if(mode != OutputMode::Pixels) { switch(mode) { default: - case OutputMode::Sync: crt_.output_sync(duration_); break; - case OutputMode::Blank: crt_.output_blank(duration_); break; - case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_); break; + case OutputMode::Sync: crt_.output_sync(duration_*2); break; + case OutputMode::Blank: crt_.output_blank(duration_*2); break; + case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_*2); break; } - // Reseed duration + // Reseed duration. duration_ = duration; // The shifter should keep running, so throw away the proper amount of content. @@ -614,7 +630,7 @@ void Video::VideoStream::flush_border() { // Output colour 0 for the entirety of duration_ (or black, if this is 1bpp mode). uint16_t *const colour_pointer = reinterpret_cast(crt_.begin_data(1)); if(colour_pointer) *colour_pointer = (bpp_ != OutputBpp::One) ? palette_[0] : 0; - crt_.output_level(duration_); + crt_.output_level(duration_*2); duration_ = 0; } @@ -715,7 +731,7 @@ void Video::VideoStream::output_pixels(int duration) { } // Check whether the limit has been reached. - if(pixel_pointer_ == allocation_size) { + if(pixel_pointer_ >= allocation_size - 32) { flush_pixels(); } } @@ -725,12 +741,12 @@ void Video::VideoStream::output_pixels(int duration) { if(pixels) { int leftover_duration = pixels; switch(bpp_) { - case OutputBpp::One: leftover_duration >>= 1; break; - default: break; - case OutputBpp::Four: leftover_duration <<= 1; break; + default: leftover_duration >>= 1; break; + case OutputBpp::Two: break; + case OutputBpp::Four: leftover_duration <<= 1; break; } shift(leftover_duration); - crt_.output_data(leftover_duration); + crt_.output_data(leftover_duration*2); } } @@ -738,9 +754,9 @@ void Video::VideoStream::flush_pixels() { // Flush only if there's something to flush. if(pixel_pointer_) { switch(bpp_) { - case OutputBpp::One: crt_.output_data(pixel_pointer_ >> 1, size_t(pixel_pointer_)); break; - default: crt_.output_data(pixel_pointer_); break; - case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break; + case OutputBpp::One: crt_.output_data(pixel_pointer_); break; + default: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break; + case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 2, size_t(pixel_pointer_)); break; } } @@ -761,7 +777,13 @@ void Video::VideoStream::set_bpp(OutputBpp bpp) { } void Video::VideoStream::load(uint64_t value) { - output_shifter_ = value; + // In 1bpp mode, a 0 bit is white and a 1 bit is black. + // Invert the input so that the 'just output the border colour + // when the shifter is empty' optimisation works. + if(bpp_ == OutputBpp::One) + output_shifter_ = ~value; + else + output_shifter_ = value; } // MARK: - Range observer. diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index 46196ceed..a13ad8af1 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -21,6 +21,12 @@ class VideoTester; namespace Atari { namespace ST { +struct LineLength { + int length = 1024; + int hsync_start = 1024; + int hsync_end = 1024; +}; + /*! Models a combination of the parts of the GLUE, MMU and Shifter that in net form the video subsystem of the Atari ST. So not accurate to a real chip, but @@ -115,7 +121,6 @@ class Video { Range get_memory_access_range(); private: - void advance(HalfCycles duration); DeferredQueue deferrer_; Outputs::CRT::CRT crt_; @@ -131,7 +136,6 @@ class Video { uint16_t line_buffer_[256]; int x_ = 0, y_ = 0, next_y_ = 0; - int next_load_toggle_ = -1; bool load_ = false; int load_base_ = 0; @@ -163,7 +167,7 @@ class Video { } sync_schedule = SyncSchedule::None; bool sync = false; } vertical_, next_vertical_; - int line_length_ = 1024; + LineLength line_length_; int data_latch_position_ = 0; int data_latch_read_position_ = 0; @@ -237,59 +241,6 @@ class Video { bool vsync = false; } public_state_; - struct Event { - int delay; - enum class Type { - SetDisplayEnable, ResetDisplayEnable, - SetHsync, ResetHsync, - SetVsync, ResetVsync, - } type; - - Event(Type type, int delay) : delay(delay), type(type) {} - - void apply(PublicState &state) { - apply(type, state); - } - - static void apply(Type type, PublicState &state) { - switch(type) { - default: - case Type::SetDisplayEnable: state.display_enable = true; break; - case Type::ResetDisplayEnable: state.display_enable = false; break; - case Type::SetHsync: state.hsync = true; break; - case Type::ResetHsync: state.hsync = false; break; - case Type::SetVsync: state.vsync = true; break; - case Type::ResetVsync: state.vsync = false; break; - } - } - }; - - std::vector pending_events_; - void add_event(int delay, Event::Type type) { - // Apply immediately if there's no delay (or a negative delay). - if(delay <= 0) { - Event::apply(type, public_state_); - return; - } - - if(!pending_events_.empty()) { - // Otherwise enqueue, having subtracted the delay for any preceding events, - // and subtracting from the subsequent, if any. - auto insertion_point = pending_events_.begin(); - while(insertion_point != pending_events_.end() && insertion_point->delay < delay) { - delay -= insertion_point->delay; - ++insertion_point; - } - if(insertion_point != pending_events_.end()) { - insertion_point->delay -= delay; - } - - pending_events_.emplace(insertion_point, type, delay); - } else { - pending_events_.emplace_back(type, delay); - } - } - friend class ::VideoTester; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index feced1631..f4165bef8 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; }; 4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; }; 4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; }; + 4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */; }; 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; 4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; }; @@ -1002,6 +1003,8 @@ 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = ""; }; 4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = ""; }; 4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = ""; }; + 4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSHighPrecisionTimer.h; sourceTree = ""; }; + 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSHighPrecisionTimer.m; sourceTree = ""; }; 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = ""; }; 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = ""; }; 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = ""; }; @@ -2063,6 +2066,15 @@ path = Utility; sourceTree = ""; }; + 4B2BF19323E10F0000C3AD60 /* High Precision Timer */ = { + isa = PBXGroup; + children = ( + 4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */, + 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */, + ); + path = "High Precision Timer"; + sourceTree = ""; + }; 4B2E2D9E1C3A070900138695 /* Electron */ = { isa = PBXGroup; children = ( @@ -3257,6 +3269,7 @@ 4B2A538F1D117D36003C6002 /* Audio */, 4B643F3D1D77B88000D431D6 /* Document Controller */, 4B55CE551C3B7D360093A61B /* Documents */, + 4B2BF19323E10F0000C3AD60 /* High Precision Timer */, 4BBFE83B21015D9C00BF1C40 /* Joystick Manager */, 4B2A53921D117D36003C6002 /* Machine */, 4B55DD7F20DF06680043F2E5 /* MachinePicker */, @@ -3839,17 +3852,19 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1130; ORGANIZATIONNAME = "Thomas Harte"; TargetAttributes = { 4B055A691FAE763F0060FFFF = { CreatedOnToolsVersion = 9.1; + DevelopmentTeam = CP2SKEB3XT; ProvisioningStyle = Automatic; }; 4BB73E9D1B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = CP2SKEB3XT; LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; @@ -3858,11 +3873,14 @@ }; 4BB73EB11B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; + DevelopmentTeam = CP2SKEB3XT; LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; TestTargetID = 4BB73E9D1B587A5100552FC2; }; 4BB73EBC1B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; + DevelopmentTeam = CP2SKEB3XT; LastSwiftMigration = 1020; TestTargetID = 4BB73E9D1B587A5100552FC2; }; @@ -4442,6 +4460,7 @@ 4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */, 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */, 4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */, + 4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */, 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, @@ -4911,6 +4930,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = CP2SKEB3XT; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(USER_LIBRARY_DIR)/Frameworks", @@ -4931,6 +4951,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = CP2SKEB3XT; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(USER_LIBRARY_DIR)/Frameworks", @@ -5059,9 +5080,10 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CP2SKEB3XT; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5086,6 +5108,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -5104,9 +5127,10 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CP2SKEB3XT; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5133,6 +5157,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; @@ -5144,11 +5169,17 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = NO; INFOPLIST_FILE = "Clock SignalTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -5162,12 +5193,18 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = NO; GCC_OPTIMIZATION_LEVEL = 2; INFOPLIST_FILE = "Clock SignalTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clock Signal.app/Contents/MacOS/Clock Signal"; @@ -5178,6 +5215,7 @@ isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = CP2SKEB3XT; INFOPLIST_FILE = "Clock SignalUITests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests"; @@ -5192,6 +5230,7 @@ isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = CP2SKEB3XT; INFOPLIST_FILE = "Clock SignalUITests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests"; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme new file mode 100644 index 000000000..e4fe3267b --- /dev/null +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 1465a4f62..a99eb0a5d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h b/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h index 9ceeded01..5942fa4f1 100644 --- a/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h +++ b/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h @@ -19,6 +19,9 @@ /// Initialises a new instance of the high precision timer; the timer will begin /// ticking immediately. +/// +/// @param task The block to perform each time the timer fires. +/// @param interval The interval at which to fire the timer, in nanoseconds. - (instancetype)initWithTask:(dispatch_block_t)task interval:(uint64_t)interval; /// Stops the timer. diff --git a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm index 7bba7b73e..a99487e9e 100644 --- a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm +++ b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm @@ -95,18 +95,29 @@ struct VideoTester { // MARK: - Sequence Point Prediction Tests /// Tests that no events occur outside of the sequence points the video predicts. -- (void)testSequencePoints { +- (void)testSequencePoints50 { // Set 4bpp, 50Hz. _video->write(0x05, 0x0200); _video->write(0x30, 0x0000); - // Run for [more than] a whole frame making sure that no observeable outputs + [self runSequencePointsTest]; +} + +- (void)testSequencePoints72 { + // Set 1bpp, 72Hz. + _video->write(0x30, 0x0200); + + [self runSequencePointsTest]; +} + +- (void)runSequencePointsTest { + // Run for [more than] two frames making sure that no observeable outputs // change at any time other than a sequence point. HalfCycles next_event; bool display_enable = false; bool vsync = false; bool hsync = false; - for(size_t c = 0; c < 10 * 1000 * 1000; ++c) { + for(size_t c = 0; c < 8000000 / 20; ++c) { const bool is_transition_point = next_event == HalfCycles(0); if(is_transition_point) { @@ -322,6 +333,16 @@ struct RunLength { XCTAssertNotEqual([self currentVideoAddress], 0); } +// MARK: - Tests Relating To Specific Bugs + +- (void)test72LineLength { + // Set 1bpp, 72Hz. + _video->write(0x30, 0x0200); + + [self syncToStartOfLine]; + _video->run_for(HalfCycles(400)); // 392, 399, 406 +} + // MARK: - Tests Correlating To Exact Pieces of Software - (void)testUnionDemoScroller { diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 84f30480d..8d8c4f3fb 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -161,6 +161,18 @@ CRT::CRT( int cycles_per_line, set_new_display_type(cycles_per_line, display_type); } +CRT::CRT(int cycles_per_line, + int clocks_per_pixel_greatest_common_divisor, + int height_of_display, + int vertical_sync_half_lines, + Outputs::Display::InputDataType data_type) { + scan_target_modals_.input_data_type = data_type; + scan_target_modals_.cycles_per_line = cycles_per_line; + scan_target_modals_.clocks_per_pixel_greatest_common_divisor = clocks_per_pixel_greatest_common_divisor; + set_new_timing(cycles_per_line, height_of_display, Outputs::Display::ColourSpace::YIQ, 1, 1, vertical_sync_half_lines, false); +} + + // MARK: - Sync loop Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced) { @@ -294,6 +306,8 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ // MARK: - stream feeding methods void CRT::output_scan(const Scan *const scan) { + assert(scan->number_of_cycles >= 0); + // Simplified colour burst logic: if it's within the back porch we'll take it. if(scan->type == Scan::Type::ColourBurst) { if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) { diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 8fce41666..be07a50ba 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -123,6 +123,15 @@ class CRT { bool should_alternate, Outputs::Display::InputDataType data_type); + /*! Constructs a monitor-style CRT — one that will take only an RGB or monochrome signal, and therefore has + no colour space or colour subcarrier frequency. This monitor will automatically map colour bursts to the black level. + */ + CRT(int cycles_per_line, + int clocks_per_pixel_greatest_common_divisor, + int height_of_display, + int vertical_sync_half_lines, + Outputs::Display::InputDataType data_type); + /*! Exactly identical to calling the designated constructor with colour subcarrier information looked up by display type. */ @@ -227,10 +236,15 @@ class CRT { @returns A pointer to the allocated area if room is available; @c nullptr otherwise. */ inline uint8_t *begin_data(std::size_t required_length, std::size_t required_alignment = 1) { + const auto result = scan_target_->begin_data(required_length, required_alignment); #ifndef NDEBUG - allocated_data_length_ = required_length; + // If data was allocated, make a record of how much so as to be able to hold the caller to that + // contract later. If allocation failed, don't constrain the caller. This allows callers that + // allocate on demand but may allow one failure to hold for a longer period — e.g. until the + // next line. + allocated_data_length_ = result ? required_length : std::numeric_limits::max(); #endif - return scan_target_->begin_data(required_length, required_alignment); + return result; } /*! Sets the gamma exponent for the simulated screen. */ diff --git a/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp b/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp index 487303763..e6123fd23 100644 --- a/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp +++ b/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp @@ -106,7 +106,7 @@ template class DigitalPhaseL // In net: use an unweighted average of the stored offsets to compute current window size, // bucketing them by rounding to the nearest multiple of the base clocks per bit - window_length_ = total_spacing_ / total_divisor_; + window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1)); // Also apply a difference to phase, use a simple spring mechanism as a lowpass filter. const auto error = new_phase - (window_length_ >> 1);