much better frame pacing
Some checks are pending
Build coop / build-linux (push) Waiting to run
Build coop / build-steamos (push) Waiting to run
Build coop / build-windows-opengl (push) Waiting to run
Build coop / build-windows-directx (push) Waiting to run
Build coop / build-macos-arm (push) Waiting to run
Build coop / build-macos-intel (push) Waiting to run

plus limited frame rates on the loading and crash screens to go easy on the cpu.

during gameplay, this new system is able to stay true to your fps limit without frame drops.
Vsync causes occasional frame drops by just 1 frame. this is an sdl issue and i know of no workaround; luckily it's very minor.

this commit changes frame pacing by moving frame delaying to just before the frame is displayed. a more precise delay function is also used.
This commit is contained in:
Isaac0-dev 2025-07-25 13:57:55 +10:00
parent fe2c31554d
commit 0a84ca725e
6 changed files with 68 additions and 20 deletions

View file

@ -138,7 +138,7 @@ override_disallowed_functions = {
"src/game/first_person_cam.h": [ "first_person_update" ],
"src/pc/lua/utils/smlua_collision_utils.h": [ "collision_find_surface_on_ray" ],
"src/engine/behavior_script.h": [ "stub_behavior_script_2", "cur_obj_update" ],
"src/pc/utils/misc.h": [ "str_.*", "file_get_line", "delta_interpolate_(normal|rgba|mtx)", "detect_and_skip_mtx_interpolation" ],
"src/pc/utils/misc.h": [ "str_.*", "file_get_line", "delta_interpolate_(normal|rgba|mtx)", "detect_and_skip_mtx_interpolation", "precise_delay_f64" ],
"src/engine/lighting_engine.h": [ "le_calculate_vertex_lighting", "le_clear", "le_shutdown" ],
}

View file

@ -1970,20 +1970,26 @@ void gfx_run(Gfx *commands) {
//double t0 = gfx_wapi->get_time();
gfx_rapi->start_frame();
gfx_run_dl(commands);
gfx_flush();
//double t1 = gfx_wapi->get_time();
//printf("Process %f %f\n", t1, t1 - t0);
gfx_rapi->end_frame();
gfx_wapi->swap_buffers_begin();
}
void gfx_end_frame(void) {
void gfx_end_frame_render(void) {
gfx_flush();
gfx_rapi->end_frame();
}
void gfx_display_frame(void) {
gfx_wapi->swap_buffers_begin();
if (!dropped_frame) {
gfx_rapi->finish_render();
gfx_wapi->swap_buffers_end();
}
}
void gfx_end_frame(void) {
gfx_end_frame_render();
gfx_display_frame();
}
void gfx_shutdown(void) {
if (gfx_rapi) {
if (gfx_rapi->shutdown) gfx_rapi->shutdown();

View file

@ -21,6 +21,8 @@ void gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, co
struct GfxRenderingAPI *gfx_get_current_rendering_api(void);
void gfx_start_frame(void);
void gfx_run(Gfx *commands);
void gfx_end_frame_render(void);
void gfx_display_frame(void);
void gfx_end_frame(void);
void gfx_shutdown(void);
void gfx_pc_precomp_shader(uint32_t rgb1, uint32_t alpha1, uint32_t rgb2, uint32_t alpha2, uint32_t flags);

View file

@ -219,24 +219,29 @@ void produce_interpolation_frames_and_delay(void) {
);
gRenderingDelta = delta;
// prepare interpolated frame
gfx_start_frame();
if (!gSkipInterpolationTitleScreen) { patch_interpolations(delta); }
send_display_list(gGfxSPTask);
gfx_end_frame();
sDrawnFrames++;
if (!is30Fps && configUncappedFramerate) { continue; }
gfx_end_frame_render();
// delay if our framerate is capped
f64 now = clock_elapsed_f64();
f64 elapsedTime = now - loopStartTime;
expectedTime += (targetTime - curTime) / (f64) numFramesToDraw;
f64 delay = (expectedTime - elapsedTime) * 1000.0;
if (delay > 0.0) {
WAPI.delay((u32)delay);
if (!configUncappedFramerate && !configWindow.vsync) {
f64 now = clock_elapsed_f64();
f64 elapsedTime = now - loopStartTime;
expectedTime += (targetTime - curTime) / (f64) numFramesToDraw;
f64 delay = (expectedTime - elapsedTime);
numFramesToDraw--;
if (delay > 0.0) {
precise_delay_f64(delay);
}
}
numFramesToDraw--;
// send the frame to the screen (should be directly after the delay for good frame pacing)
gfx_display_frame();
sDrawnFrames++;
} while ((curTime = clock_elapsed_f64()) < targetTime && numFramesToDraw > 0);
// compute and update the frame rate every second
@ -271,7 +276,7 @@ inline static void buffer_audio(void) {
for (s32 i = 0; i < 2; i++) {
create_next_audio_buffer(sAudioBuffer + i * (numAudioSamples * 2), numAudioSamples);
}
if (!shouldMute) {
for (u16 i=0; i < ARRAY_COUNT(sAudioBuffer); i++) {
sAudioBuffer[i] *= gMasterVolume;
@ -326,6 +331,10 @@ void produce_one_frame(void) {
// used for rendering 2D scenes fullscreen like the loading or crash screens
void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG, u8 clearColorB) {
// measure frame start time
f64 frameStart = clock_elapsed_f64();
f64 targetFrameTime = 1.0 / 60.0; // update at 60fps
// start frame
gfx_start_frame();
config_gfx_pool();
@ -353,6 +362,15 @@ void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG,
alloc_display_list(0);
gfx_run((Gfx*) gGfxSPTask->task.t.data_ptr); // send_display_list
display_and_vsync();
// delay to go easy on the cpu
f64 frameEnd = clock_elapsed_f64();
f64 elapsed = frameEnd - frameStart;
f64 remaining = targetFrameTime - elapsed;
if (remaining > 0) {
WAPI.delay((u32)(remaining * 1000.0));
}
gfx_end_frame();
}

View file

@ -13,6 +13,7 @@
#include "game/save_file.h"
#include "engine/math_util.h"
#include "pc/configfile.h"
#include "pc/pc_main.h"
float smooth_step(float edge0, float edge1, float x) {
float t = (x - edge0) / (edge1 - edge0);
@ -90,6 +91,25 @@ bool clock_is_date(u8 month, u8 day) {
return tm_info->tm_mon == month - 1 && tm_info->tm_mday == day;
}
// delay functions lack accuracy sometimes due to os scheduling
// busy-waiting is bad practice but it's very accurate so we use a hybrid
void precise_delay_f64(f64 delaySec) {
const f64 sleepMargin = 0.002; // 2 ms margin before we switch to busy-waiting
f64 start = clock_elapsed_f64();
f64 end = start + delaySec;
// sleep until we're ~2ms away from the target
for (f64 remaining = end - clock_elapsed_f64(); remaining > sleepMargin; remaining = end - clock_elapsed_f64()) {
u32 sleepMs = (u32) ((remaining - sleepMargin) * 1000.0);
if (sleepMs < 1) { break; } // not enough time to sleep
WAPI.delay(sleepMs);
}
// busy-wait until the target time is hit
while (clock_elapsed_f64() < end);
}
void file_get_line(char* buffer, size_t maxLength, FILE* fp) {
char* initial = buffer;

View file

@ -19,6 +19,8 @@ u32 clock_elapsed_ticks(void);
/* |description|Checks whether it is the day given|descriptionEnd| */
bool clock_is_date(u8 month, u8 day);
void precise_delay_f64(f64 delaySec);
void file_get_line(char* buffer, size_t maxLength, FILE* fp);
/* |description|Linearly interpolates between `a` and `b` with `delta`|descriptionEnd| */