diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py index 3e089974d..1b36400c7 100644 --- a/autogen/convert_functions.py +++ b/autogen/convert_functions.py @@ -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" ], } diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index 31703ae09..f498eb5ef 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -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(); diff --git a/src/pc/gfx/gfx_pc.h b/src/pc/gfx/gfx_pc.h index 8469776f3..97fdca0cc 100644 --- a/src/pc/gfx/gfx_pc.h +++ b/src/pc/gfx/gfx_pc.h @@ -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); diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index 97de6bde0..5490dcdac 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -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(); } diff --git a/src/pc/utils/misc.c b/src/pc/utils/misc.c index df98e8f17..26320867d 100644 --- a/src/pc/utils/misc.c +++ b/src/pc/utils/misc.c @@ -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; diff --git a/src/pc/utils/misc.h b/src/pc/utils/misc.h index 98dff7be7..a5b9d69af 100644 --- a/src/pc/utils/misc.h +++ b/src/pc/utils/misc.h @@ -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| */