mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Dynres: hold through a savestate load; opt-in DXGI flip presentation (experimental)
- A savestate load is not a heavy scene: hold the dynamic-resolution level for the whole 2 s request and do not arm fast descent, which dropped 18x -> 11x within a second of every load and climbed back slowly. - gl_dxgi_present.c: present the present thread's frames through a DXGI FLIP_DISCARD swapchain via WGL_NV_DX_interop2 (Present(1,0), one frame per refresh). Opt-in (PSX_GL_DXGI_PRESENT=1): sharing the HWND with the GL window surface froze the screen in the first test; needs a child window before it can be default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/* gl_dxgi_present.h - present GL frames through a DXGI flip-model swapchain
|
||||
* (Windows; gl_dxgi_present.cpp). All calls run on the present thread with
|
||||
* its GL context current. */
|
||||
#ifndef PSX_GL_DXGI_PRESENT_H
|
||||
#define PSX_GL_DXGI_PRESENT_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Set up for the window (an HWND); 1 when active. */
|
||||
int dxp_init(void *hwnd, int w, int h);
|
||||
int dxp_active(void);
|
||||
/* Wait for the swapchain's frame latency, resize to w x h if needed, lock the
|
||||
* back buffer: the GL framebuffer to draw this frame into, 0 on failure. Its
|
||||
* origin is the top-left (D3D): draw Y-flipped. */
|
||||
unsigned dxp_begin(int w, int h);
|
||||
/* Unlock and present with vsync (one frame per refresh). 1 on success. */
|
||||
int dxp_present(void);
|
||||
void dxp_shutdown(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -389,6 +389,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/present_thread.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/frame_gen.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu_gl_renderer.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gl_dxgi_present.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gl_batch_policy_lle.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gl_batch_policy_hle.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gl_batch_policy_select.c
|
||||
@@ -2320,7 +2321,7 @@ function(psxrecomp_add_runtime_target target)
|
||||
# by opengl32; Phase 2b will load modern GL via SDL_GL_GetProcAddress.
|
||||
# iphlpapi: GetAdaptersAddresses in the launcher's netplay
|
||||
# local-address discovery (MSVC does not link it implicitly).
|
||||
target_link_libraries(${target} PRIVATE ws2_32 iphlpapi dbghelp comdlg32 opengl32 shell32)
|
||||
target_link_libraries(${target} PRIVATE ws2_32 iphlpapi dbghelp comdlg32 opengl32 shell32 d3d11 dxgi)
|
||||
# Newer mingw-w64 maps clock_gettime → clock_gettime64 in libwinpthread.
|
||||
# Link it even when netplay code prefers Win32 clocks, so any residual
|
||||
# POSIX time refs (third-party / debug tools) resolve under -static.
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/* gl_dxgi_present.c - present the GL renderer's frames through a DXGI
|
||||
* flip-model swapchain (Windows).
|
||||
*
|
||||
* Windowed and borderless GL swaps go through DWM composition. With swap
|
||||
* interval 0 (NVIDIA's vsync'd SwapBuffers stalls the render queue, #723) the
|
||||
* present thread's wall-clock pacing drifts against the refresh and DWM merges
|
||||
* two presents into one refresh: a 60 fps stream showed 35-60 distinct frames
|
||||
* on screen. A FLIP_DISCARD swapchain presented with Present(1, 0) puts every
|
||||
* frame in its own refresh, and only the present thread waits for it.
|
||||
*
|
||||
* GL draws into the swapchain's back buffer through WGL_NV_DX_interop2: the
|
||||
* present thread blits its finished slot into a renderbuffer registered on
|
||||
* the D3D11 back buffer, unlocks it and presents. Anything unavailable (the
|
||||
* extension, the device, the swapchain) leaves the SDL_GL_SwapWindow path in
|
||||
* place. Experimental: PSX_GL_DXGI_PRESENT=1 turns it on. */
|
||||
#ifdef _WIN32
|
||||
#define COBJMACROS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <initguid.h> /* this TU defines the D3D11/DXGI IIDs it uses */
|
||||
#include <d3d11.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include "psx_sdl.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gl_dxgi_present.h"
|
||||
|
||||
typedef unsigned int GLuint_;
|
||||
typedef unsigned int GLenum_;
|
||||
#define GL_RENDERBUFFER_ 0x8D41
|
||||
#define GL_FRAMEBUFFER_ 0x8D40
|
||||
#define GL_COLOR_ATTACHMENT0_ 0x8CE0
|
||||
#define WGL_ACCESS_WRITE_DISCARD_NV_ 0x0002
|
||||
|
||||
typedef HANDLE (WINAPI *PFN_wglDXOpenDeviceNV)(void *);
|
||||
typedef BOOL (WINAPI *PFN_wglDXCloseDeviceNV)(HANDLE);
|
||||
typedef HANDLE (WINAPI *PFN_wglDXRegisterObjectNV)(HANDLE, void *, GLuint_, GLenum_, GLenum_);
|
||||
typedef BOOL (WINAPI *PFN_wglDXUnregisterObjectNV)(HANDLE, HANDLE);
|
||||
typedef BOOL (WINAPI *PFN_wglDXLockObjectsNV)(HANDLE, int, HANDLE *);
|
||||
typedef const char *(WINAPI *PFN_wglGetExtensionsStringARB)(HDC);
|
||||
typedef void (APIENTRY *PFN_glGenX)(int, GLuint_ *);
|
||||
typedef void (APIENTRY *PFN_glDelX)(int, const GLuint_ *);
|
||||
typedef void (APIENTRY *PFN_glBindX)(GLenum_, GLuint_);
|
||||
typedef void (APIENTRY *PFN_glFbRb)(GLenum_, GLenum_, GLenum_, GLuint_);
|
||||
|
||||
typedef struct Dxp {
|
||||
int active;
|
||||
HWND hwnd;
|
||||
ID3D11Device *dev;
|
||||
ID3D11DeviceContext *ctx;
|
||||
IDXGISwapChain2 *sc;
|
||||
HANDLE wait, hdev, hbuf;
|
||||
ID3D11Texture2D *bb;
|
||||
GLuint_ rb, fbo;
|
||||
UINT w, h;
|
||||
PFN_wglDXOpenDeviceNV open;
|
||||
PFN_wglDXCloseDeviceNV close;
|
||||
PFN_wglDXRegisterObjectNV reg;
|
||||
PFN_wglDXUnregisterObjectNV unreg;
|
||||
PFN_wglDXLockObjectsNV lock, unlock;
|
||||
PFN_glGenX gen_rb, gen_fb;
|
||||
PFN_glDelX del_rb, del_fb;
|
||||
PFN_glBindX bind_rb, bind_fb;
|
||||
PFN_glFbRb fb_rb;
|
||||
} Dxp;
|
||||
static Dxp D;
|
||||
|
||||
static void fail(const char *why) {
|
||||
fprintf(stdout, "psxrecomp: DXGI present unavailable (%s); swapping through SDL\n", why);
|
||||
}
|
||||
|
||||
static void unregister_buffer() {
|
||||
if (D.hbuf) { D.unreg(D.hdev, D.hbuf); D.hbuf = NULL; }
|
||||
if (D.fbo) { D.del_fb(1, &D.fbo); D.fbo = 0; }
|
||||
if (D.rb) { D.del_rb(1, &D.rb); D.rb = 0; }
|
||||
if (D.bb) { ID3D11Texture2D_Release(D.bb); D.bb = NULL; }
|
||||
}
|
||||
|
||||
static int register_buffer() {
|
||||
if (FAILED(IDXGISwapChain2_GetBuffer(D.sc, 0, &IID_ID3D11Texture2D, (void **)&D.bb))) return 0;
|
||||
D.gen_rb(1, &D.rb);
|
||||
D.hbuf = D.reg(D.hdev, D.bb, D.rb, GL_RENDERBUFFER_, WGL_ACCESS_WRITE_DISCARD_NV_);
|
||||
if (!D.hbuf) return 0;
|
||||
D.gen_fb(1, &D.fbo);
|
||||
D.bind_fb(GL_FRAMEBUFFER_, D.fbo);
|
||||
D.fb_rb(GL_FRAMEBUFFER_, GL_COLOR_ATTACHMENT0_, GL_RENDERBUFFER_, D.rb);
|
||||
D.bind_fb(GL_FRAMEBUFFER_, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dxp_init(void *hwnd, int w, int h) {
|
||||
/* Opt-in while experimental: the GL window surface and the swapchain on
|
||||
* the same HWND left the screen frozen in the first test. */
|
||||
const char *env = getenv("PSX_GL_DXGI_PRESENT");
|
||||
if (!env || env[0] != '1') return 0;
|
||||
if (D.active) return 1;
|
||||
PFN_wglGetExtensionsStringARB ext = (PFN_wglGetExtensionsStringARB)(void *)wglGetProcAddress("wglGetExtensionsStringARB");
|
||||
const char *exts = ext ? ext(wglGetCurrentDC()) : NULL;
|
||||
if (!exts || !strstr(exts, "WGL_NV_DX_interop2")) { fail("no WGL_NV_DX_interop2"); return 0; }
|
||||
D.open = (PFN_wglDXOpenDeviceNV)(void *)wglGetProcAddress("wglDXOpenDeviceNV");
|
||||
D.close = (PFN_wglDXCloseDeviceNV)(void *)wglGetProcAddress("wglDXCloseDeviceNV");
|
||||
D.reg = (PFN_wglDXRegisterObjectNV)(void *)wglGetProcAddress("wglDXRegisterObjectNV");
|
||||
D.unreg = (PFN_wglDXUnregisterObjectNV)(void *)wglGetProcAddress("wglDXUnregisterObjectNV");
|
||||
D.lock = (PFN_wglDXLockObjectsNV)(void *)wglGetProcAddress("wglDXLockObjectsNV");
|
||||
D.unlock = (PFN_wglDXLockObjectsNV)(void *)wglGetProcAddress("wglDXUnlockObjectsNV");
|
||||
D.gen_rb = (PFN_glGenX)SDL_GL_GetProcAddress("glGenRenderbuffers");
|
||||
D.gen_fb = (PFN_glGenX)SDL_GL_GetProcAddress("glGenFramebuffers");
|
||||
D.del_rb = (PFN_glDelX)SDL_GL_GetProcAddress("glDeleteRenderbuffers");
|
||||
D.del_fb = (PFN_glDelX)SDL_GL_GetProcAddress("glDeleteFramebuffers");
|
||||
D.bind_rb = (PFN_glBindX)SDL_GL_GetProcAddress("glBindRenderbuffer");
|
||||
D.bind_fb = (PFN_glBindX)SDL_GL_GetProcAddress("glBindFramebuffer");
|
||||
D.fb_rb = (PFN_glFbRb)SDL_GL_GetProcAddress("glFramebufferRenderbuffer");
|
||||
if (!D.open || !D.close || !D.reg || !D.unreg || !D.lock || !D.unlock || !D.gen_rb ||
|
||||
!D.gen_fb || !D.del_rb || !D.del_fb || !D.bind_rb || !D.bind_fb || !D.fb_rb) {
|
||||
fail("interop entry points"); return 0;
|
||||
}
|
||||
/* The GL context's adapter: the high-performance GPU (what SDL's GL
|
||||
* context runs on with a single discrete GPU). */
|
||||
IDXGIFactory2 *factory = NULL;
|
||||
if (FAILED(CreateDXGIFactory1(&IID_IDXGIFactory2, (void **)&factory))) { fail("factory"); return 0; }
|
||||
IDXGIAdapter1 *adapter = NULL;
|
||||
if (FAILED(IDXGIFactory2_EnumAdapters1(factory, 0, &adapter))) adapter = NULL;
|
||||
const D3D_FEATURE_LEVEL levels[1] = {D3D_FEATURE_LEVEL_11_0};
|
||||
HRESULT hr = D3D11CreateDevice((IDXGIAdapter *)adapter, adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE,
|
||||
NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, levels, 1,
|
||||
D3D11_SDK_VERSION, &D.dev, NULL, &D.ctx);
|
||||
if (adapter) IDXGIAdapter1_Release(adapter);
|
||||
if (FAILED(hr)) { IDXGIFactory2_Release(factory); fail("D3D11 device"); return 0; }
|
||||
DXGI_SWAP_CHAIN_DESC1 d; memset(&d, 0, sizeof d);
|
||||
d.Width = (UINT)(w > 0 ? w : 1); d.Height = (UINT)(h > 0 ? h : 1);
|
||||
d.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
d.SampleDesc.Count = 1;
|
||||
d.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
d.BufferCount = 2;
|
||||
d.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||
d.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
|
||||
d.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
|
||||
IDXGISwapChain1 *sc1 = NULL;
|
||||
hr = IDXGIFactory2_CreateSwapChainForHwnd(factory, (IUnknown *)D.dev, (HWND)hwnd, &d, NULL, NULL, &sc1);
|
||||
if (SUCCEEDED(hr)) IDXGIFactory2_MakeWindowAssociation(factory, (HWND)hwnd, DXGI_MWA_NO_ALT_ENTER);
|
||||
IDXGIFactory2_Release(factory);
|
||||
if (FAILED(hr)) { char b[64]; snprintf(b, sizeof b, "swapchain 0x%08lx", hr); fail(b); dxp_shutdown(); return 0; }
|
||||
hr = IDXGISwapChain1_QueryInterface(sc1, &IID_IDXGISwapChain2, (void **)&D.sc);
|
||||
IDXGISwapChain1_Release(sc1);
|
||||
if (FAILED(hr)) { fail("IDXGISwapChain2"); dxp_shutdown(); return 0; }
|
||||
IDXGISwapChain2_SetMaximumFrameLatency(D.sc, 1);
|
||||
D.wait = IDXGISwapChain2_GetFrameLatencyWaitableObject(D.sc);
|
||||
D.hdev = D.open(D.dev);
|
||||
if (!D.hdev) { fail("wglDXOpenDeviceNV"); dxp_shutdown(); return 0; }
|
||||
if (!register_buffer()) { fail("wglDXRegisterObjectNV"); dxp_shutdown(); return 0; }
|
||||
D.hwnd = (HWND)hwnd; D.w = d.Width; D.h = d.Height; D.active = 1;
|
||||
fprintf(stdout, "psxrecomp: DXGI flip presentation on (%ux%u, vblank-aligned)\n", D.w, D.h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dxp_active(void) { return D.active; }
|
||||
|
||||
unsigned dxp_begin(int w, int h) {
|
||||
if (!D.active || w <= 0 || h <= 0) return 0;
|
||||
if (D.wait) WaitForSingleObjectEx(D.wait, 100, TRUE);
|
||||
if ((UINT)w != D.w || (UINT)h != D.h) {
|
||||
unregister_buffer();
|
||||
ID3D11DeviceContext_ClearState(D.ctx);
|
||||
ID3D11DeviceContext_Flush(D.ctx);
|
||||
if (FAILED(IDXGISwapChain2_ResizeBuffers(D.sc, 0, (UINT)w, (UINT)h, DXGI_FORMAT_UNKNOWN,
|
||||
DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)) ||
|
||||
!register_buffer()) {
|
||||
fail("resize"); dxp_shutdown(); return 0;
|
||||
}
|
||||
D.w = (UINT)w; D.h = (UINT)h;
|
||||
}
|
||||
if (!D.lock(D.hdev, 1, &D.hbuf)) return 0;
|
||||
return D.fbo;
|
||||
}
|
||||
|
||||
int dxp_present(void) {
|
||||
if (!D.active) return 0;
|
||||
D.unlock(D.hdev, 1, &D.hbuf);
|
||||
const HRESULT hr = IDXGISwapChain2_Present(D.sc, 1, 0);
|
||||
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
|
||||
fail("device removed"); dxp_shutdown(); return 0;
|
||||
}
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
void dxp_shutdown(void) {
|
||||
if (D.hdev) unregister_buffer();
|
||||
if (D.hdev) { D.close(D.hdev); D.hdev = NULL; }
|
||||
if (D.wait) { CloseHandle(D.wait); D.wait = NULL; }
|
||||
if (D.sc) { IDXGISwapChain2_Release(D.sc); D.sc = NULL; }
|
||||
if (D.ctx) { ID3D11DeviceContext_Release(D.ctx); D.ctx = NULL; }
|
||||
if (D.dev) { ID3D11Device_Release(D.dev); D.dev = NULL; }
|
||||
D.active = 0;
|
||||
}
|
||||
#endif
|
||||
@@ -105,6 +105,7 @@
|
||||
#include "present_thread.h"
|
||||
|
||||
#include "psx_sdl.h"
|
||||
#include "gl_dxgi_present.h"
|
||||
#if defined(PSX_SDL3)
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
#else
|
||||
@@ -10195,6 +10196,9 @@ static int pt_cb_ctx(void *user, int current) {
|
||||
s_ptp_fbo[i] = 0;
|
||||
s_ptp_gen[i] = 0;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
dxp_shutdown();
|
||||
#endif
|
||||
glFlush();
|
||||
SDL_GL_MakeCurrent(s_win, NULL);
|
||||
return 1;
|
||||
@@ -10211,6 +10215,29 @@ static void gl_window_swap(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "gl_dxgi_present.h"
|
||||
#if defined(PSX_SDL3)
|
||||
static void *pt_hwnd(void) {
|
||||
return SDL_GetPointerProperty(SDL_GetWindowProperties(s_win), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
|
||||
}
|
||||
#else
|
||||
#include <SDL_syswm.h>
|
||||
static void *pt_hwnd(void) {
|
||||
SDL_SysWMinfo info;
|
||||
SDL_VERSION(&info.version);
|
||||
return SDL_GetWindowWMInfo(s_win, &info) ? (void *)info.info.win.window : NULL;
|
||||
}
|
||||
#endif
|
||||
/* The DXGI flip-model swapchain (gl_dxgi_present.c), set up on the present
|
||||
* thread at its first frame: 1 when active. */
|
||||
static int pt_dxgi(int ww, int wh) {
|
||||
static int tried;
|
||||
if (!tried) { tried = 1; void *h = pt_hwnd(); if (h) (void)dxp_init(h, ww, wh); }
|
||||
return dxp_active();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void *pt_cb_present(void *user, int slot, void *ready) {
|
||||
(void)user;
|
||||
if (s_ptp_interval_gen != s_pt_interval_gen) {
|
||||
@@ -10263,23 +10290,36 @@ static void *pt_cb_present(void *user, int slot, void *ready) {
|
||||
const int sw = s_pts_w[slot], sh = s_pts_h[slot];
|
||||
int ww = 0, wh = 0;
|
||||
gl_drawable_size( &ww, &wh);
|
||||
GLuint dx_fbo = 0;
|
||||
#ifdef _WIN32
|
||||
if (pt_dxgi(ww, wh)) dx_fbo = dxp_begin(ww, wh);
|
||||
#endif
|
||||
/* The DXGI back buffer's origin is the top: blit Y-flipped into it. */
|
||||
const int y0 = dx_fbo ? wh : 0, y1 = dx_fbo ? 0 : wh;
|
||||
p_glBindFramebuffer_raw(PSXGL_READ_FRAMEBUFFER, s_ptp_fbo[slot]);
|
||||
p_glBindFramebuffer_raw(PSXGL_DRAW_FRAMEBUFFER, 0);
|
||||
p_glBindFramebuffer_raw(PSXGL_DRAW_FRAMEBUFFER, dx_fbo);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
if (ww == sw && wh == sh) {
|
||||
p_glBlitFramebuffer(0, 0, sw, sh, 0, 0, sw, sh, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
p_glBlitFramebuffer(0, 0, sw, sh, 0, y0, sw, y1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
} else {
|
||||
/* The window changed after this frame was composed: fit it. */
|
||||
glViewport(0, 0, ww, wh);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
p_glBlitFramebuffer(0, 0, sw, sh, 0, 0, ww, wh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
p_glBlitFramebuffer(0, 0, sw, sh, 0, y0, ww, y1, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
}
|
||||
#ifdef GL_PRESENT_THREAD_TEST_HOOK
|
||||
GL_PRESENT_THREAD_TEST_HOOK(slot);
|
||||
#endif
|
||||
void *done = p_glFenceSync(PT_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
uint64_t swap_start=SDL_GetPerformanceCounter();
|
||||
#ifdef _WIN32
|
||||
if (dx_fbo) {
|
||||
p_glBindFramebuffer_raw(PSXGL_DRAW_FRAMEBUFFER, 0);
|
||||
glFlush();
|
||||
psx_video_telemetry_present(dxp_present());
|
||||
} else
|
||||
#endif
|
||||
gl_window_swap();
|
||||
atomic_fetch_add(&s_pt_window_ticks,SDL_GetPerformanceCounter()-swap_start);
|
||||
if (s_recorded_passes || s_interp_enabled) present_sample(s_pts_identity[slot],s_pts_deadline[slot]);
|
||||
|
||||
@@ -10915,7 +10915,10 @@ static void dynres_tick(void) {
|
||||
* load re-anchors pacing, the game's first seconds). */
|
||||
bool soft_tail = !held && tail > 0.0 && why && std::strcmp(why, "game entry") == 0;
|
||||
if (g_dynres.hold_request_s > 0.0) {
|
||||
if (g_dynres.hold_request_s > tail) { tail = g_dynres.hold_request_s; soft_tail = !held; }
|
||||
/* A load is not a heavy scene: its first frames re-stage VRAM, the
|
||||
* HD cache and pacing. Hold the level for the whole request (hard),
|
||||
* so that transient does not drop the resolution for seconds. */
|
||||
if (g_dynres.hold_request_s > tail) { tail = g_dynres.hold_request_s; soft_tail = false; }
|
||||
why = "savestate load";
|
||||
g_dynres.hold_request_s = 0.0;
|
||||
}
|
||||
@@ -10956,8 +10959,7 @@ static void dynres_tick(void) {
|
||||
/* A new scene: let the first sustained overrun jump several levels
|
||||
* (fast descent, dynamic_resolution.h). Render-thread start and a
|
||||
* new resolution re-initialize, which arms too. */
|
||||
if (why && (std::strcmp(why, "savestate load") == 0 ||
|
||||
std::strcmp(why, "game entry") == 0 ||
|
||||
if (why && (std::strcmp(why, "game entry") == 0 ||
|
||||
std::strcmp(why, "window resize") == 0))
|
||||
dynrt_arm_descent(&g_dynres.rt);
|
||||
dynres_tick_rt(now_s, wall, period, held, tail, why);
|
||||
|
||||
Reference in New Issue
Block a user