diff --git a/src/pc/djui/djui.c b/src/pc/djui/djui.c index a01bd0a1b..96cbfc981 100644 --- a/src/pc/djui/djui.c +++ b/src/pc/djui/djui.c @@ -68,17 +68,28 @@ void djui_shutdown(void) { void patch_djui_before(void) { sDjuiRendered60fps = false; + sSavedDisplayListHead = NULL; + djui_cursor_interp_before(); } void patch_djui_interpolated(UNUSED f32 delta) { - // reset the head and re-render DJUI - if (delta >= 0.5f && !sDjuiRendered60fps && (gDjuiInMainMenu || gDjuiPanelPauseCreated)) { - sDjuiRendered60fps = true; - if (sSavedDisplayListHead == NULL) { return; } - gDisplayListHead = sSavedDisplayListHead; - djui_render(); - gDPFullSync(gDisplayListHead++); - gSPEndDisplayList(gDisplayListHead++); + extern f32 gFramePercentage; + if (gDjuiInMainMenu || gDjuiPanelPauseCreated) { + if (gFramePercentage >= 0.5f && !sDjuiRendered60fps) { + // reset the head and re-render DJUI + sDjuiRendered60fps = true; + if (sSavedDisplayListHead == NULL) { return; } + gDisplayListHead = sSavedDisplayListHead; + djui_render(); + gDPFullSync(gDisplayListHead++); + gSPEndDisplayList(gDisplayListHead++); + } else { + // patch the display list instead of a full re-render + // to make some elements on screen be smooth, while keeping things cheap. + Gfx* displayListHead = gDisplayListHead; + djui_cursor_interp(); + gDisplayListHead = displayListHead; + } } } diff --git a/src/pc/djui/djui_cursor.c b/src/pc/djui/djui_cursor.c index afb1e252d..09ee38a88 100644 --- a/src/pc/djui/djui_cursor.c +++ b/src/pc/djui/djui_cursor.c @@ -17,6 +17,11 @@ static f32 sSavedMouseY = 0; f32 gCursorX = 0; f32 gCursorY = 0; +static f32 sPrevCursorX = 0; +static f32 sPrevCursorY = 0; +static Gfx* sSavedDisplayListHead = NULL; +static bool sInterpCursor = false; + void djui_cursor_set_visible(bool visible) { if (sMouseCursor) { djui_base_set_visible(&sMouseCursor->base, visible); @@ -111,7 +116,9 @@ void djui_cursor_move(s8 xDir, s8 yDir) { } } -void djui_cursor_update(void) { +static void djui_cursor_update_position(void) { + sPrevCursorX = gCursorX; + sPrevCursorY = gCursorY; #if defined(CAPI_SDL2) || defined(CAPI_SDL1) if (djui_interactable_is_binding()) { return; } if (sMouseCursor == NULL) { return; } @@ -152,7 +159,27 @@ void djui_cursor_update(void) { djui_image_set_image(sMouseCursor, gd_texture_hand_open, 32, 32, 16); } #endif +} + +void djui_cursor_interp_before(void) { + sSavedDisplayListHead = NULL; + sInterpCursor = false; +} + +void djui_cursor_interp(void) { + djui_cursor_update_position(); + if (sInterpCursor && (sPrevCursorX != gCursorX || sPrevCursorY != gCursorY)) { + if (sSavedDisplayListHead == NULL) { return; } + gDisplayListHead = sSavedDisplayListHead; + djui_base_render(&sMouseCursor->base); + } +} + +void djui_cursor_update(void) { + djui_cursor_update_position(); + sSavedDisplayListHead = gDisplayListHead; djui_base_render(&sMouseCursor->base); + sInterpCursor = gDisplayListHead != sSavedDisplayListHead; // Check that we actually rendered something } void djui_cursor_create(void) { diff --git a/src/pc/djui/djui_cursor.h b/src/pc/djui/djui_cursor.h index 570ac2267..19be80527 100644 --- a/src/pc/djui/djui_cursor.h +++ b/src/pc/djui/djui_cursor.h @@ -9,5 +9,7 @@ void djui_cursor_set_visible(bool visible); bool djui_cursor_inside_base(struct DjuiBase* base); void djui_cursor_input_controlled_center(struct DjuiBase* base); void djui_cursor_move(s8 xDir, s8 yDir); +void djui_cursor_interp_before(void); +void djui_cursor_interp(void); void djui_cursor_update(void); void djui_cursor_create(void); diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index 0d898b5c5..f498eb5ef 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -1970,18 +1970,26 @@ void gfx_run(Gfx *commands) { //double t0 = gfx_wapi->get_time(); gfx_rapi->start_frame(); gfx_run_dl(commands); - gfx_flush(); - 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 4a3313e00..91f689b6d 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -92,6 +92,7 @@ u32 gNumVblanks = 0; u8 gRenderingInterpolated = 0; f32 gRenderingDelta = 0; +f32 gFramePercentage = 0.f; #define FRAMERATE 30 static const f64 sFrameTime = (1.0 / ((double)FRAMERATE)); @@ -201,15 +202,13 @@ static s32 get_num_frames_to_draw(f64 t, u32 frameLimit) { return (s32) MAX(1, numFramesNext - numFramesCurr); } -static u32 get_refresh_rate() { - if (configFramerateMode == RRM_MANUAL) { return configFrameLimit; } - if (configFramerateMode == RRM_UNLIMITED) { return 3000; } // Has no effect - static u32 refreshRate = 0; +static u32 get_display_refresh_rate() { #ifdef HAVE_SDL2 + static u32 refreshRate = 0; if (!refreshRate) { SDL_DisplayMode mode; if (SDL_GetCurrentDisplayMode(0, &mode) == 0) { - refreshRate = (u32) mode.refresh_rate; + if (mode.refresh_rate > 0) { refreshRate = (u32) mode.refresh_rate; } } else { refreshRate = 60; } @@ -220,48 +219,65 @@ static u32 get_refresh_rate() { #endif } +static u32 get_target_refresh_rate() { + if (configFramerateMode == RRM_MANUAL) { return configFrameLimit; } + if (configFramerateMode == RRM_UNLIMITED) { return 3000; } // Has no effect + return get_display_refresh_rate(); +} + void produce_interpolation_frames_and_delay(void) { - u32 refreshRate = get_refresh_rate(); - bool is30Fps = (refreshRate == FRAMERATE); + u32 refreshRate = get_target_refresh_rate(); gRenderingInterpolated = true; - f64 curTime = clock_elapsed_f64(); + u32 displayRefreshRate = get_display_refresh_rate(); + bool shouldDelay = configFramerateMode != RRM_UNLIMITED; + if (configWindow.vsync && displayRefreshRate <= refreshRate) { + shouldDelay = false; + refreshRate = displayRefreshRate; + } + f64 targetTime = sFrameTimeStart + sFrameTime; s32 numFramesToDraw = get_num_frames_to_draw(sFrameTimeStart, refreshRate); + f64 curTime = clock_elapsed_f64(); f64 loopStartTime = curTime; f64 expectedTime = 0; + u16 framesDrawn = 0; + const f64 interpFrameTime = sFrameTime / (f64) numFramesToDraw; // interpolate and render // make sure to draw at least one frame to prevent the game from freezing completely // (including inputs and window events) if the game update duration is greater than 33ms do { - f32 delta = ( - is30Fps ? - 1.0f : - clamp((curTime - sFrameTimeStart) / sFrameTime, 0.f, 1.f) - ); + ++framesDrawn; + + // when we know how many frames to draw, use a precise delta + f64 idealTime = shouldDelay ? (sFrameTimeStart + interpFrameTime * framesDrawn) : curTime; + f32 delta = clamp((idealTime - sFrameTimeStart) / sFrameTime, 0.f, 1.f); + gFramePercentage = clamp((curTime - sFrameTimeStart) / sFrameTime, 0.f, 1.f); gRenderingDelta = delta; gfx_start_frame(); if (!gSkipInterpolationTitleScreen) { patch_interpolations(delta); } send_display_list(gGfxSPTask); - gfx_end_frame(); - - sDrawnFrames++; - - if (!is30Fps && configFramerateMode == RRM_UNLIMITED) { 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); - if (delay > 0.0) { - precise_delay_f64(delay); + if (shouldDelay) { + expectedTime += (targetTime - curTime) / (f64) numFramesToDraw; + f64 now = clock_elapsed_f64(); + f64 elapsedTime = now - loopStartTime; + f64 delay = (expectedTime - elapsedTime); + 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++; + if (shouldDelay) { numFramesToDraw--; } } while ((curTime = clock_elapsed_f64()) < targetTime && numFramesToDraw > 0); // compute and update the frame rate every second