From 42aea2663c136ed63b6bec179f46be5fa6c82024 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 30 Apr 2024 21:38:37 -0400 Subject: [PATCH] Add automatic runtime frame-rate limiter. --- Machines/Acorn/Archimedes/Archimedes.cpp | 66 ++++++++++++++---------- Machines/Acorn/Archimedes/Video.hpp | 23 ++++++++- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 54f2538ca..5c1300779 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -336,7 +336,7 @@ class ConcreteMachine: // // The implementation of this is coupled to the ClockRate above, hence its // appearance here. - template + template void macro_tick() { macro_counter_ -= 24; @@ -349,39 +349,37 @@ class ConcreteMachine: // * timers: 2; // * sound: 1. - tick_cpu_video<0, video_divider>(); tick_cpu_video<1, video_divider>(); - tick_cpu_video<2, video_divider>(); tick_floppy(); - tick_cpu_video<3, video_divider>(); tick_cpu_video<4, video_divider>(); - tick_cpu_video<5, video_divider>(); tick_floppy(); - tick_cpu_video<6, video_divider>(); tick_cpu_video<7, video_divider>(); - tick_cpu_video<8, video_divider>(); tick_floppy(); - tick_cpu_video<9, video_divider>(); tick_cpu_video<10, video_divider>(); - tick_cpu_video<11, video_divider>(); tick_floppy(); + tick_cpu_video<0, video_divider, original_speed>(); tick_cpu_video<1, video_divider, original_speed>(); + tick_cpu_video<2, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<3, video_divider, original_speed>(); tick_cpu_video<4, video_divider, original_speed>(); + tick_cpu_video<5, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<6, video_divider, original_speed>(); tick_cpu_video<7, video_divider, original_speed>(); + tick_cpu_video<8, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<9, video_divider, original_speed>(); tick_cpu_video<10, video_divider, original_speed>(); + tick_cpu_video<11, video_divider, original_speed>(); tick_floppy(); tick_timers(); - tick_cpu_video<12, video_divider>(); tick_cpu_video<13, video_divider>(); - tick_cpu_video<14, video_divider>(); tick_floppy(); - tick_cpu_video<15, video_divider>(); tick_cpu_video<16, video_divider>(); - tick_cpu_video<17, video_divider>(); tick_floppy(); - tick_cpu_video<18, video_divider>(); tick_cpu_video<19, video_divider>(); - tick_cpu_video<20, video_divider>(); tick_floppy(); - tick_cpu_video<21, video_divider>(); tick_cpu_video<22, video_divider>(); - tick_cpu_video<23, video_divider>(); tick_floppy(); + tick_cpu_video<12, video_divider, original_speed>(); tick_cpu_video<13, video_divider, original_speed>(); + tick_cpu_video<14, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<15, video_divider, original_speed>(); tick_cpu_video<16, video_divider, original_speed>(); + tick_cpu_video<17, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<18, video_divider, original_speed>(); tick_cpu_video<19, video_divider, original_speed>(); + tick_cpu_video<20, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<21, video_divider, original_speed>(); tick_cpu_video<22, video_divider, original_speed>(); + tick_cpu_video<23, video_divider, original_speed>(); tick_floppy(); tick_timers(); tick_sound(); } int macro_counter_ = 0; - template + template void tick_cpu_video() { if constexpr (!(offset % video_divider)) { tick_video(); } -#ifndef NDEBUG // Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed. - if constexpr (offset & 7) return; -#endif + if constexpr (original_speed && (offset & 7)) return; if constexpr (offset & 1) return; tick_cpu(); } @@ -473,20 +471,36 @@ class ConcreteMachine: } // MARK: - TimedMachine. + int video_divider_ = 1; void run_for(Cycles cycles) override { +#ifndef NDEBUG + // Debug mode: always run 'slowly' because that's less of a burden, and + // because it allows me to peer at problems with greater leisure. + const bool use_original_speed = true; +#else + // As a first, blunt implementation: try to model something close + // to original speed if there have been 10 frame rate overages in total. + const bool use_original_speed = executor_.bus.video().frame_rate_overages() > 10; +#endif + + if(use_original_speed) run_for(cycles); + else run_for(cycles); + } + + template + void run_for(Cycles cycles) { macro_counter_ += cycles.as(); while(macro_counter_ > 0) { switch(video_divider_) { - default: macro_tick<2>(); break; - case 3: macro_tick<3>(); break; - case 4: macro_tick<4>(); break; - case 6: macro_tick<6>(); break; + default: macro_tick<2, original_speed>(); break; + case 3: macro_tick<3, original_speed>(); break; + case 4: macro_tick<4, original_speed>(); break; + case 6: macro_tick<6, original_speed>(); break; } } } - int video_divider_ = 1; void tick_cpu() { const uint32_t instruction = advance_pipeline(executor_.pc() + 8); diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp index cca4420df..6f76f701e 100644 --- a/Machines/Acorn/Archimedes/Video.hpp +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -142,6 +142,16 @@ struct Video { if(phase == Phase::Display) { address_ = frame_start_; cursor_address_ = cursor_start_; + + // Accumulate a count of how many times the processor has tried + // to update the visible buffer more than once in a frame; this + // will usually indicate that the software being run isn't properly + // synchronised to actual machine speed. + ++frame_starts_; + if(frame_start_sets_ > 10) { + overages_ += frame_start_sets_ > frame_starts_; + frame_start_sets_ = frame_starts_ = 0; + } } if(old_phase == Phase::Display) { entered_flyback_ = true; @@ -211,7 +221,10 @@ struct Video { return vertical_state_.phase() != Phase::Display; } - void set_frame_start(uint32_t address) { frame_start_ = address; } + void set_frame_start(uint32_t address) { + frame_start_ = address; + ++frame_start_sets_; + } void set_buffer_start(uint32_t address) { buffer_start_ = address; } void set_buffer_end(uint32_t address) { buffer_end_ = address; } void set_cursor_start(uint32_t address) { cursor_start_ = address; } @@ -223,6 +236,10 @@ struct Video { return static_cast(clock_divider_); } + int frame_rate_overages() const { + return overages_; + } + private: Log::Logger logger; InterruptObserverT &interrupt_observer_; @@ -361,6 +378,10 @@ private: uint32_t frame_start_ = 0; uint32_t cursor_start_ = 0; + int frame_start_sets_ = 0; + int frame_starts_ = 0; + int overages_ = 0; + // Ephemeral address state. uint32_t address_ = 0;