From 705d14259cc89df97dadac9027422f7c474e9170 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 9 Feb 2020 21:44:55 -0500 Subject: [PATCH 1/6] Experimentally switches to a 'high-resolution' clock for SDL. --- ClockReceiver/TimeTypes.hpp | 7 ++ .../xcschemes/Clock Signal Kiosk.xcscheme | 6 +- OSBindings/SDL/main.cpp | 67 ++++++++++++++----- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/ClockReceiver/TimeTypes.hpp b/ClockReceiver/TimeTypes.hpp index afcda2d3c..c8ef2fe1c 100644 --- a/ClockReceiver/TimeTypes.hpp +++ b/ClockReceiver/TimeTypes.hpp @@ -9,9 +9,16 @@ #ifndef TimeTypes_h #define TimeTypes_h +#include + namespace Time { typedef double Seconds; +typedef int64_t Nanos; + +inline Nanos nanos_now() { + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); +} } 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 index e4fe3267b..1d7e2ee72 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -54,11 +54,15 @@ + + + isEnabled = "NO"> #include +#include #include #include #include @@ -21,11 +22,11 @@ #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../../Machines/Utility/MachineForTarget.hpp" +#include "../../ClockReceiver/TimeTypes.hpp" + #include "../../Machines/MediaTarget.hpp" #include "../../Machines/CRTMachine.hpp" -#include "../../Concurrency/BestEffortUpdater.hpp" - #include "../../Activity/Observer.hpp" #include "../../Outputs/OpenGL/Primitives/Rectangle.hpp" #include "../../Outputs/OpenGL/ScanTarget.hpp" @@ -33,18 +34,52 @@ namespace { -struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { - Time::Seconds update(Concurrency::BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update, int flags) override { - std::lock_guard lock_guard(*machine_mutex); - return machine->crt_machine()->run_until(duration, flags); +struct MachineRunner { + ~MachineRunner() { + stop(); + } + + void start() { + last_time_ = Time::nanos_now(); + timer_ = SDL_AddTimer(timer_period, &sdl_callback, reinterpret_cast(this)); + } + + void stop() { + if(timer_) { + SDL_RemoveTimer(timer_); + timer_ = 0; + } + } + + void signal_vsync() { + vsync_time_.store(Time::nanos_now()); } std::mutex *machine_mutex; Machine::DynamicMachine *machine; + + private: + SDL_TimerID timer_ = 0; + Time::Nanos last_time_ = 0; + std::atomic vsync_time_; + + static constexpr Uint32 timer_period = 4; + static Uint32 sdl_callback(Uint32 interval, void *param) { + reinterpret_cast(param)->update(); + return timer_period; + } + + void update() { + const int64_t time_now = Time::nanos_now(); + + std::lock_guard lock_guard(*machine_mutex); + machine->crt_machine()->run_for(double(time_now - last_time_) / 1e9); + last_time_ = time_now; + } }; struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { - // This is set to a relatively large number for now. + // This is empirically the best that I can seem to do with SDL's timer precision. static constexpr int buffer_size = 1024; void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) final { @@ -56,7 +91,7 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { } void audio_callback(Uint8 *stream, int len) { - updater->update(); +// updater->update(); std::lock_guard lock_guard(audio_buffer_mutex_); std::size_t sample_length = static_cast(len) / sizeof(int16_t); @@ -75,7 +110,7 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { } SDL_AudioDeviceID audio_device; - Concurrency::BestEffortUpdater *updater; +// Concurrency::BestEffortUpdater *updater; std::mutex audio_buffer_mutex_; std::vector audio_buffer_; @@ -391,8 +426,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - Concurrency::BestEffortUpdater updater; - BestEffortUpdaterDelegate best_effort_updater_delegate; + MachineRunner machine_runner; SpeakerDelegate speaker_delegate; // For vanilla SDL purposes, assume system ROMs can be found in one of: @@ -489,10 +523,8 @@ int main(int argc, char *argv[]) { } // Wire up the best-effort updater, its delegate, and the speaker delegate. - best_effort_updater_delegate.machine = machine.get(); - best_effort_updater_delegate.machine_mutex = &machine_mutex; - speaker_delegate.updater = &updater; - updater.set_delegate(&best_effort_updater_delegate); + machine_runner.machine = machine.get(); + machine_runner.machine_mutex = &machine_mutex; // Attempt to set up video and audio. if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { @@ -644,15 +676,16 @@ int main(int argc, char *argv[]) { const bool uses_mouse = !!machine->mouse_machine(); bool should_quit = false; Uint32 fullscreen_mode = 0; + machine_runner.start(); while(!should_quit) { // Wait for vsync, draw a new frame and post a machine update. // NB: machine_mutex is *not* currently locked, therefore it shouldn't // be 'most' of the time. SDL_GL_SwapWindow(window); + machine_runner.signal_vsync(); scan_target.update(int(window_width), int(window_height)); scan_target.draw(int(window_width), int(window_height)); if(activity_observer) activity_observer->draw(); - updater.update(); // Grab the machine lock and process all pending events. std::lock_guard lock_guard(machine_mutex); @@ -877,7 +910,7 @@ int main(int argc, char *argv[]) { } // Clean up. - updater.flush(); // Ensure no further updates will occur. + machine_runner.stop(); // Ensure no further updates will occur. joysticks.clear(); SDL_DestroyWindow( window ); SDL_Quit(); From 0b0a7e241b9f925b3c32ab1cfd2ca5d73036b3d1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 9 Feb 2020 22:11:06 -0500 Subject: [PATCH 2/6] Factors out the stuff of time warping. --- ClockReceiver/ScanSynchroniser.hpp | 79 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + .../xcschemes/Clock Signal.xcscheme | 2 +- .../Mac/Clock Signal/Machine/CSMachine.mm | 56 ++++--------- 4 files changed, 98 insertions(+), 41 deletions(-) create mode 100644 ClockReceiver/ScanSynchroniser.hpp diff --git a/ClockReceiver/ScanSynchroniser.hpp b/ClockReceiver/ScanSynchroniser.hpp new file mode 100644 index 000000000..aed4b67da --- /dev/null +++ b/ClockReceiver/ScanSynchroniser.hpp @@ -0,0 +1,79 @@ +// +// ScanSynchroniser.hpp +// Clock Signal +// +// Created by Thomas Harte on 09/02/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef ScanSynchroniser_h +#define ScanSynchroniser_h + +#include "../Outputs/ScanTarget.hpp" + +#include + +namespace Time { + +/*! + Where an emulated machine is sufficiently close to a host machine's frame rate that a small nudge in + its speed multiplier will bring it into frame synchronisation, the ScanSynchroniser provides a sequence of + speed multipliers designed both to adjust the machine to the proper speed and, in a reasonable amount + of time, to bring it into phase. +*/ +class ScanSynchroniser { + public: + /*! + @returns @c true if the emulated machine can be synchronised with the host frame output based on its + current @c [scan]status and the host machine's @c frame_duration; @c false otherwise. + */ + bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, double frame_duration) { + ratio_ = 1.0; + if(scan_status.field_duration_gradient < 0.00001) { + // Check out the machine's current frame time. + // If it's within 3% of a non-zero integer multiple of the + // display rate, mark this time window to be split over the sync. + ratio_ = frame_duration / scan_status.field_duration; + const double integer_ratio = round(ratio_); + if(integer_ratio > 0.0) { + ratio_ /= integer_ratio; + return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment; + } + } + return false; + } + + /*! + @returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise. + Results are undefined if @c can_synchroise returned @c false. + */ + double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) { + // The host versus emulated ratio is calculated based on the current perceived frame duration of the machine. + // Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's + // no benefit to second guessing it here — just take it to be correct. + // + // ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points. + // So the set speed multiplier may be adjusted slightly to aim for that. + double speed_multiplier = 1.0 / ratio_; + if(scan_status.current_position > 0.0) { + if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio; + else speed_multiplier *= phase_adjustment_ratio; + } + speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05); + return speed_multiplier_; + } + + private: + static constexpr double maximum_rate_adjustment = 1.03; + static constexpr double phase_adjustment_ratio = 1.005; + + // Managed local state. + double speed_multiplier_ = 1.0; + + // Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap. + double ratio_ = 1.0; +}; + +} + +#endif /* ScanSynchroniser_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 751840b21..3763a8c49 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1129,6 +1129,7 @@ 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = ""; }; 4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = ""; }; 4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = ""; }; + 4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanSynchroniser.hpp; sourceTree = ""; }; 4B65085F22F4CF8D009C1100 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; 4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ComparativeTests.mm; sourceTree = ""; }; 4B680CE323A555CA00451D43 /* 68000 Comparative Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Comparative Tests"; sourceTree = ""; }; @@ -3726,6 +3727,7 @@ 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4B80214322EE7C3E00068002 /* JustInTime.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */, + 4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */, ); name = ClockReceiver; path = ../../ClockReceiver; 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 a99eb0a5d..63be2c037 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ missing_roms; @@ -726,13 +729,13 @@ struct ActivityObserver: public Activity::Observer { - (void)openGLViewDisplayLinkDidFire:(CSOpenGLView *)view now:(const CVTimeStamp *)now outputTime:(const CVTimeStamp *)outputTime { // First order of business: grab a timestamp. - const auto timeNow = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + const auto timeNow = Time::nanos_now(); CGSize pixelSize = view.backingSize; BOOL isSyncLocking; @synchronized(self) { - // Store a means to map from CVTimeStamp.hostTime to std::chrono::high_resolution_clock; - // there is an extremely dodgy assumption here that both are in the same units (and, below, that both as in ns). + // Store a means to map from CVTimeStamp.hostTime to Time::Nanos; + // there is an extremely dodgy assumption here that the former is in ns. if(!_timeDiff) { _timeDiff = int64_t(now->hostTime) - int64_t(timeNow); } @@ -761,11 +764,11 @@ struct ActivityObserver: public Activity::Observer { #define TICKS 600 - (void)start { - __block auto lastTime = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + __block auto lastTime = Time::nanos_now(); _timer = [[CSHighPrecisionTimer alloc] initWithTask:^{ // Grab the time now and, therefore, the amount of time since the timer last fired. - const auto timeNow = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + const auto timeNow = Time::nanos_now(); const auto duration = timeNow - lastTime; @@ -774,42 +777,15 @@ struct ActivityObserver: public Activity::Observer { @synchronized(self) { // If this tick includes vsync then inspect the machine. if(timeNow >= self->_syncTime && lastTime < self->_syncTime) { - // Grab the scan status and check out the machine's current frame time. - // If it's stable and within 3% of a non-zero integer multiple of the - // display rate, mark this time window to be split over the sync. - const auto scan_status = self->_machine->crt_machine()->get_scan_status(); - double ratio = 1.0; - if(scan_status.field_duration_gradient < 0.00001) { - ratio = self->_refreshPeriod / scan_status.field_duration; - const double integerRatio = round(ratio); - if(integerRatio > 0.0) { - ratio /= integerRatio; - - constexpr double maximumAdjustment = 1.03; - splitAndSync = ratio <= maximumAdjustment && ratio >= 1 / maximumAdjustment; - } - } - self->_isSyncLocking = splitAndSync; + splitAndSync = self->_isSyncLocking = self->_scanSynchroniser.can_synchronise(self->_machine->crt_machine()->get_scan_status(), self->_refreshPeriod); // If the time window is being split, run up to the split, then check out machine speed, possibly // adjusting multiplier, then run after the split. if(splitAndSync) { self->_machine->crt_machine()->run_for((double)(self->_syncTime - lastTime) / 1e9); - - // The host versus emulated ratio is calculated based on the current perceived frame duration of the machine. - // Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's - // no benefit to second guessing it here — just take it to be correct. - // - // ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points. - // So the set speed multiplier may be adjusted slightly to aim for that. - double speed_multiplier = 1.0 / ratio; - if(scan_status.current_position > 0.0) { - constexpr double adjustmentRatio = 1.005; - if(scan_status.current_position < 0.5) speed_multiplier /= adjustmentRatio; - else speed_multiplier *= adjustmentRatio; - } - self->_speedMultiplier = (self->_speedMultiplier * 0.95) + (speed_multiplier * 0.05); - self->_machine->crt_machine()->set_speed_multiplier(self->_speedMultiplier); + self->_machine->crt_machine()->set_speed_multiplier( + self->_scanSynchroniser.next_speed_multiplier(self->_machine->crt_machine()->get_scan_status()) + ); self->_machine->crt_machine()->run_for((double)(timeNow - self->_syncTime) / 1e9); } } From bf6bc7c684ce412c49df2e5974cae4fb1388cfda Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 9 Feb 2020 22:27:02 -0500 Subject: [PATCH 3/6] Adds speed control into the SDL build. If I can just figure out how to manipulate OpenGL from the timer thread to SDL's satisfaction, this'll be as good as it probably gets via SDL. --- OSBindings/SDL/main.cpp | 44 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 1879c5e84..7ef4e122d 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -23,6 +23,7 @@ #include "../../Machines/Utility/MachineForTarget.hpp" #include "../../ClockReceiver/TimeTypes.hpp" +#include "../../ClockReceiver/ScanSynchroniser.hpp" #include "../../Machines/MediaTarget.hpp" #include "../../Machines/CRTMachine.hpp" @@ -52,7 +53,17 @@ struct MachineRunner { } void signal_vsync() { - vsync_time_.store(Time::nanos_now()); + const auto now = Time::nanos_now(); + const auto previous_vsync_time = vsync_time_.load(); + vsync_time_.store(now); + + // Update estimate of current frame time. + frame_time_average_ -= frame_times_[frame_time_pointer_]; + frame_times_[frame_time_pointer_] = now - previous_vsync_time; + frame_time_average_ += frame_times_[frame_time_pointer_]; + frame_time_pointer_ = (frame_time_pointer_ + 1) & (frame_times_.size() - 1); + + _frame_period.store((1e9 * 32.0) / double(frame_time_average_)); } std::mutex *machine_mutex; @@ -63,6 +74,17 @@ struct MachineRunner { Time::Nanos last_time_ = 0; std::atomic vsync_time_; + Time::ScanSynchroniser scan_synchroniser_; + + // A slightly clumsy means of trying to derive frame rate from calls to + // signal_vsync(); SDL_DisplayMode provides only an integral quantity + // whereas, empirically, it's fairly common for monitors to run at the + // NTSC-esque frame rates of 59.94Hz. + std::array frame_times_; + Time::Nanos frame_time_average_ = 0; + size_t frame_time_pointer_ = 0; + std::atomic _frame_period; + static constexpr Uint32 timer_period = 4; static Uint32 sdl_callback(Uint32 interval, void *param) { reinterpret_cast(param)->update(); @@ -70,10 +92,26 @@ struct MachineRunner { } void update() { - const int64_t time_now = Time::nanos_now(); + const auto time_now = Time::nanos_now(); + const auto vsync_time = vsync_time_.load(); std::lock_guard lock_guard(*machine_mutex); - machine->crt_machine()->run_for(double(time_now - last_time_) / 1e9); + const auto crt_machine = machine->crt_machine(); + + bool split_and_sync = false; + if(last_time_ < vsync_time && time_now >= vsync_time) { + split_and_sync = scan_synchroniser_.can_synchronise(crt_machine->get_scan_status(), _frame_period); + } + + if(split_and_sync) { + crt_machine->run_for(double(vsync_time - last_time_) / 1e9); + crt_machine->set_speed_multiplier( + scan_synchroniser_.next_speed_multiplier(crt_machine->get_scan_status()) + ); + crt_machine->run_for(double(time_now - vsync_time) / 1e9); + } else { + crt_machine->run_for(double(time_now - last_time_) / 1e9); + } last_time_ = time_now; } }; From 6147134423cc41ffc0130f62f8a8936f829b9e3f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 10 Feb 2020 23:07:09 -0500 Subject: [PATCH 4/6] Introduces frame locking for SDL. --- .../xcschemes/Clock Signal.xcscheme | 2 +- OSBindings/SDL/main.cpp | 36 ++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) 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 63be2c037..a99eb0a5d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ vsync_time_; + std::atomic_flag frame_lock_; Time::ScanSynchroniser scan_synchroniser_; @@ -95,7 +104,7 @@ struct MachineRunner { const auto time_now = Time::nanos_now(); const auto vsync_time = vsync_time_.load(); - std::lock_guard lock_guard(*machine_mutex); + std::unique_lock lock_guard(*machine_mutex); const auto crt_machine = machine->crt_machine(); bool split_and_sync = false; @@ -108,6 +117,14 @@ struct MachineRunner { crt_machine->set_speed_multiplier( scan_synchroniser_.next_speed_multiplier(crt_machine->get_scan_status()) ); + + // This is a bit of an SDL ugliness; wait here until the next frame is drawn. + // That is, unless and until I can think of a good way of running background + // updates via a share group — possibly an extra intermediate buffer is needed? + lock_guard.unlock(); + while(frame_lock_.test_and_set()); + lock_guard.lock(); + crt_machine->run_for(double(time_now - vsync_time) / 1e9); } else { crt_machine->run_for(double(time_now - last_time_) / 1e9); @@ -129,7 +146,6 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { } void audio_callback(Uint8 *stream, int len) { -// updater->update(); std::lock_guard lock_guard(audio_buffer_mutex_); std::size_t sample_length = static_cast(len) / sizeof(int16_t); @@ -148,7 +164,6 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { } SDL_AudioDeviceID audio_device; -// Concurrency::BestEffortUpdater *updater; std::mutex audio_buffer_mutex_; std::vector audio_buffer_; @@ -716,14 +731,19 @@ int main(int argc, char *argv[]) { Uint32 fullscreen_mode = 0; machine_runner.start(); while(!should_quit) { - // Wait for vsync, draw a new frame and post a machine update. - // NB: machine_mutex is *not* currently locked, therefore it shouldn't - // be 'most' of the time. - SDL_GL_SwapWindow(window); - machine_runner.signal_vsync(); + // Draw a new frame, indicating completion of the draw to the machine runner. scan_target.update(int(window_width), int(window_height)); scan_target.draw(int(window_width), int(window_height)); if(activity_observer) activity_observer->draw(); + machine_runner.signal_did_draw(); + + // Wait for presentation of that frame, posting a vsync. + SDL_GL_SwapWindow(window); + machine_runner.signal_vsync(); + + // NB: machine_mutex is *not* currently locked, therefore it shouldn't + // be 'most' of the time — assuming most of the time is spent waiting + // on vsync, anyway. // Grab the machine lock and process all pending events. std::lock_guard lock_guard(machine_mutex); From 6624cb7a78fec64c933ecd9bf43db5de5c1ba26f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 10 Feb 2020 23:19:47 -0500 Subject: [PATCH 5/6] Corrects set_ram macro to act more like a function. --- Machines/Commodore/Vic-20/Vic20.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 71a989a10..2440c1fdb 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -397,9 +397,10 @@ class ConcreteMachine: memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); memset(mos6560_bus_handler_.video_memory_map, 0, sizeof(mos6560_bus_handler_.video_memory_map)); -#define set_ram(baseaddr, length) \ - write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length); \ - write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); +#define set_ram(baseaddr, length) { \ + write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length); \ + write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); \ + } // Add 6502-visible RAM as requested. set_ram(0x0000, 0x0400); From 886d923e30525e6bde1633a2261c9f21e5c5daba Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 10 Feb 2020 23:30:32 -0500 Subject: [PATCH 6/6] Attempts to permit fixed speed multiplication. --- ClockReceiver/ScanSynchroniser.hpp | 15 ++++++++++++--- OSBindings/SDL/main.cpp | 10 +++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ClockReceiver/ScanSynchroniser.hpp b/ClockReceiver/ScanSynchroniser.hpp index aed4b67da..be2682c44 100644 --- a/ClockReceiver/ScanSynchroniser.hpp +++ b/ClockReceiver/ScanSynchroniser.hpp @@ -33,7 +33,7 @@ class ScanSynchroniser { // Check out the machine's current frame time. // If it's within 3% of a non-zero integer multiple of the // display rate, mark this time window to be split over the sync. - ratio_ = frame_duration / scan_status.field_duration; + ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration; const double integer_ratio = round(ratio_); if(integer_ratio > 0.0) { ratio_ /= integer_ratio; @@ -54,13 +54,21 @@ class ScanSynchroniser { // // ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points. // So the set speed multiplier may be adjusted slightly to aim for that. - double speed_multiplier = 1.0 / ratio_; + double speed_multiplier = 1.0 / (ratio_ / base_multiplier_); if(scan_status.current_position > 0.0) { if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio; else speed_multiplier *= phase_adjustment_ratio; } speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05); - return speed_multiplier_; + return speed_multiplier_ * base_multiplier_; + } + + void set_base_speed_multiplier(double multiplier) { + base_multiplier_ = multiplier; + } + + double get_base_speed_multiplier() { + return base_multiplier_; } private: @@ -69,6 +77,7 @@ class ScanSynchroniser { // Managed local state. double speed_multiplier_ = 1.0; + double base_multiplier_ = 1.0; // Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap. double ratio_ = 1.0; diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 79757175e..3b683fa21 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -74,6 +74,10 @@ struct MachineRunner { frame_lock_.clear(); } + void set_speed_multiplier(double multiplier) { + scan_synchroniser_.set_base_speed_multiplier(multiplier); + } + std::mutex *machine_mutex; Machine::DynamicMachine *machine; @@ -127,6 +131,7 @@ struct MachineRunner { crt_machine->run_for(double(time_now - vsync_time) / 1e9); } else { + crt_machine->set_speed_multiplier(scan_synchroniser_.get_base_speed_multiplier()); crt_machine->run_for(double(time_now - last_time_) / 1e9); } last_time_ = time_now; @@ -565,13 +570,12 @@ int main(int argc, char *argv[]) { char *end; double speed = strtod(speed_string, &end); - if(end-speed_string != strlen(speed_string)) { + if(size_t(end - speed_string) != strlen(speed_string)) { std::cerr << "Unable to parse speed: " << speed_string << std::endl; } else if(speed <= 0.0) { std::cerr << "Cannot run at speed " << speed_string << "; speeds must be positive." << std::endl; } else { - machine->crt_machine()->set_speed_multiplier(speed); - // TODO: what if not a 'CRT' machine? Likely rests on resolving this project's machine naming policy. + machine_runner.set_speed_multiplier(speed); } }