mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-04-24 11:02:14 +00:00
revise better frame pacing (#901)
improved frame pacing, where frame delaying is done just before displaying the frame. so, the frame is fully rendered, and then the delay is done to wait the extra time before displaying the frame. This is a far more reliable and consistent way to manage frame pacing, and it will help to keep the framerate from choking too badly when the game lags a little. The changes to `gfx_end_frame` is to minimize the steps necessary between delaying and actually displaying the frame. This is an optimization just to increase the accuracy of frame pacing even further. Also, delta calculation is improved when the number of frames to draw is known. Djui cursor movement is also updated every frame for smoother movement.
This commit is contained in:
parent
ac1cf57cf0
commit
e74ea05fde
6 changed files with 104 additions and 38 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue