Add automatic runtime frame-rate limiter.

This commit is contained in:
Thomas Harte 2024-04-30 21:38:37 -04:00
parent a882faa7f6
commit 42aea2663c
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
// appearance here.
template <int video_divider>
template <int video_divider, bool original_speed>
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 <int offset, int video_divider>
template <int offset, int video_divider, bool original_speed>
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<true>(cycles);
else run_for<false>(cycles);
}
template <bool original_speed>
void run_for(Cycles cycles) {
macro_counter_ += cycles.as<int>();
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);

View File

@ -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<int>(clock_divider_);
}
int frame_rate_overages() const {
return overages_;
}
private:
Log::Logger<Log::Source::ARMIOC> 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;