1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-01 22:41:32 +00:00

Merge pull request #1370 from TomHarte/Zarch

Add automatic runtime frame-rate limiter.
This commit is contained in:
Thomas Harte 2024-04-30 22:02:06 -04:00 committed by GitHub
commit cb70967971
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 62 additions and 27 deletions

View File

@ -336,7 +336,7 @@ class ConcreteMachine:
// //
// The implementation of this is coupled to the ClockRate above, hence its // The implementation of this is coupled to the ClockRate above, hence its
// appearance here. // appearance here.
template <int video_divider> template <int video_divider, bool original_speed>
void macro_tick() { void macro_tick() {
macro_counter_ -= 24; macro_counter_ -= 24;
@ -349,39 +349,37 @@ class ConcreteMachine:
// * timers: 2; // * timers: 2;
// * sound: 1. // * sound: 1.
tick_cpu_video<0, video_divider>(); tick_cpu_video<1, video_divider>(); tick_cpu_video<0, video_divider, original_speed>(); tick_cpu_video<1, video_divider, original_speed>();
tick_cpu_video<2, video_divider>(); tick_floppy(); tick_cpu_video<2, video_divider, original_speed>(); tick_floppy();
tick_cpu_video<3, video_divider>(); tick_cpu_video<4, video_divider>(); tick_cpu_video<3, video_divider, original_speed>(); tick_cpu_video<4, video_divider, original_speed>();
tick_cpu_video<5, video_divider>(); tick_floppy(); tick_cpu_video<5, video_divider, original_speed>(); tick_floppy();
tick_cpu_video<6, video_divider>(); tick_cpu_video<7, video_divider>(); tick_cpu_video<6, video_divider, original_speed>(); tick_cpu_video<7, video_divider, original_speed>();
tick_cpu_video<8, video_divider>(); tick_floppy(); tick_cpu_video<8, video_divider, original_speed>(); tick_floppy();
tick_cpu_video<9, video_divider>(); tick_cpu_video<10, video_divider>(); tick_cpu_video<9, video_divider, original_speed>(); tick_cpu_video<10, video_divider, original_speed>();
tick_cpu_video<11, video_divider>(); tick_floppy(); tick_cpu_video<11, video_divider, original_speed>(); tick_floppy();
tick_timers(); tick_timers();
tick_cpu_video<12, video_divider>(); tick_cpu_video<13, video_divider>(); tick_cpu_video<12, video_divider, original_speed>(); tick_cpu_video<13, video_divider, original_speed>();
tick_cpu_video<14, video_divider>(); tick_floppy(); tick_cpu_video<14, video_divider, original_speed>(); tick_floppy();
tick_cpu_video<15, video_divider>(); tick_cpu_video<16, video_divider>(); tick_cpu_video<15, video_divider, original_speed>(); tick_cpu_video<16, video_divider, original_speed>();
tick_cpu_video<17, video_divider>(); tick_floppy(); tick_cpu_video<17, video_divider, original_speed>(); tick_floppy();
tick_cpu_video<18, video_divider>(); tick_cpu_video<19, video_divider>(); tick_cpu_video<18, video_divider, original_speed>(); tick_cpu_video<19, video_divider, original_speed>();
tick_cpu_video<20, video_divider>(); tick_floppy(); tick_cpu_video<20, video_divider, original_speed>(); tick_floppy();
tick_cpu_video<21, video_divider>(); tick_cpu_video<22, video_divider>(); tick_cpu_video<21, video_divider, original_speed>(); tick_cpu_video<22, video_divider, original_speed>();
tick_cpu_video<23, video_divider>(); tick_floppy(); tick_cpu_video<23, video_divider, original_speed>(); tick_floppy();
tick_timers(); tick_timers();
tick_sound(); tick_sound();
} }
int macro_counter_ = 0; int macro_counter_ = 0;
template <int offset, int video_divider> template <int offset, int video_divider, bool original_speed>
void tick_cpu_video() { void tick_cpu_video() {
if constexpr (!(offset % video_divider)) { if constexpr (!(offset % video_divider)) {
tick_video(); tick_video();
} }
#ifndef NDEBUG
// Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed. // Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed.
if constexpr (offset & 7) return; if constexpr (original_speed && (offset & 7)) return;
#endif
if constexpr (offset & 1) return; if constexpr (offset & 1) return;
tick_cpu(); tick_cpu();
} }
@ -473,20 +471,36 @@ class ConcreteMachine:
} }
// MARK: - TimedMachine. // MARK: - TimedMachine.
int video_divider_ = 1;
void run_for(Cycles cycles) override { 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<true>(cycles);
else run_for<false>(cycles);
}
template <bool original_speed>
void run_for(Cycles cycles) {
macro_counter_ += cycles.as<int>(); macro_counter_ += cycles.as<int>();
while(macro_counter_ > 0) { while(macro_counter_ > 0) {
switch(video_divider_) { switch(video_divider_) {
default: macro_tick<2>(); break; default: macro_tick<2, original_speed>(); break;
case 3: macro_tick<3>(); break; case 3: macro_tick<3, original_speed>(); break;
case 4: macro_tick<4>(); break; case 4: macro_tick<4, original_speed>(); break;
case 6: macro_tick<6>(); break; case 6: macro_tick<6, original_speed>(); break;
} }
} }
} }
int video_divider_ = 1;
void tick_cpu() { void tick_cpu() {
const uint32_t instruction = advance_pipeline(executor_.pc() + 8); const uint32_t instruction = advance_pipeline(executor_.pc() + 8);

View File

@ -142,6 +142,16 @@ struct Video {
if(phase == Phase::Display) { if(phase == Phase::Display) {
address_ = frame_start_; address_ = frame_start_;
cursor_address_ = cursor_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) { if(old_phase == Phase::Display) {
entered_flyback_ = true; entered_flyback_ = true;
@ -211,7 +221,10 @@ struct Video {
return vertical_state_.phase() != Phase::Display; 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_start(uint32_t address) { buffer_start_ = address; }
void set_buffer_end(uint32_t address) { buffer_end_ = address; } void set_buffer_end(uint32_t address) { buffer_end_ = address; }
void set_cursor_start(uint32_t address) { cursor_start_ = address; } void set_cursor_start(uint32_t address) { cursor_start_ = address; }
@ -223,6 +236,10 @@ struct Video {
return static_cast<int>(clock_divider_); return static_cast<int>(clock_divider_);
} }
int frame_rate_overages() const {
return overages_;
}
private: private:
Log::Logger<Log::Source::ARMIOC> logger; Log::Logger<Log::Source::ARMIOC> logger;
InterruptObserverT &interrupt_observer_; InterruptObserverT &interrupt_observer_;
@ -361,6 +378,10 @@ private:
uint32_t frame_start_ = 0; uint32_t frame_start_ = 0;
uint32_t cursor_start_ = 0; uint32_t cursor_start_ = 0;
int frame_start_sets_ = 0;
int frame_starts_ = 0;
int overages_ = 0;
// Ephemeral address state. // Ephemeral address state.
uint32_t address_ = 0; uint32_t address_ = 0;