mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
graphics: address perf issues with DX9 image resize logic (#712)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Move resize logic from `EndScene` to `Present`/`PresentEx` (resolves Live2D frame drops in some extreme settings) Plug small memory leak Remove unnecessary surface Lock/Unlock ## Testing Tested IIDX, SDVX, DDR.
This commit is contained in:
@@ -1458,6 +1458,15 @@ void graphics_d3d9_on_present(
|
|||||||
IDirect3DDevice9 *device,
|
IDirect3DDevice9 *device,
|
||||||
IDirect3DDevice9 *wrapped_device) {
|
IDirect3DDevice9 *wrapped_device) {
|
||||||
|
|
||||||
|
// image resize / orientation swap. run here (the present path) rather than from `EndScene`,
|
||||||
|
// which may fire several times per frame on multi-pass / render-to-texture games. this is the
|
||||||
|
// single point guaranteed to be after the game's last `EndScene` and before the real `Present`,
|
||||||
|
// so the back buffer is fully drawn and the expensive StretchRect work happens exactly once per
|
||||||
|
// frame. it must run before the overlay is rendered so the overlay isn't scaled with the image.
|
||||||
|
if (cfg::SCREENRESIZE->enable_screen_resize || GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||||
|
SurfaceHook(device);
|
||||||
|
}
|
||||||
|
|
||||||
// Do overlay init as many d3d9 hooks create a dummy instance to get vtable offsets and never
|
// Do overlay init as many d3d9 hooks create a dummy instance to get vtable offsets and never
|
||||||
// call `Present`. This avoids race conditions on `IDirect3D9::CreateDevice` like with
|
// call `Present`. This avoids race conditions on `IDirect3D9::CreateDevice` like with
|
||||||
// `dx9osd.dll` for pfreepanic.
|
// `dx9osd.dll` for pfreepanic.
|
||||||
|
|||||||
@@ -760,18 +760,15 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::BeginScene() {
|
|||||||
|
|
||||||
static IDirect3DSurface9 *topSurface = nullptr;
|
static IDirect3DSurface9 *topSurface = nullptr;
|
||||||
static IDirect3DSurface9 *backbuffer = nullptr;
|
static IDirect3DSurface9 *backbuffer = nullptr;
|
||||||
static LPDIRECT3DSWAPCHAIN9 mSwapChain = nullptr;
|
static IDirect3DTexture9 *tex = nullptr;
|
||||||
static IDirect3DTexture9* tex;
|
|
||||||
|
|
||||||
static UINT topSurface_width = 0;
|
static UINT topSurface_width = 0;
|
||||||
static UINT topSurface_height = 0;
|
static UINT topSurface_height = 0;
|
||||||
static float topSurface_aspect_ratio = 1.f;
|
static float topSurface_aspect_ratio = 1.f;
|
||||||
|
static UINT backbuffer_width = 0;
|
||||||
|
static UINT backbuffer_height = 0;
|
||||||
|
|
||||||
void SurfaceHook(IDirect3DDevice9 *pReal) {
|
void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||||
D3DPRESENT_PARAMETERS param {};
|
|
||||||
|
|
||||||
pReal->GetSwapChain(0, &mSwapChain);
|
|
||||||
mSwapChain->GetPresentParameters(¶m);
|
|
||||||
|
|
||||||
// phase 0 - create a surface that has the same aspect ratio as the original back buffer
|
// phase 0 - create a surface that has the same aspect ratio as the original back buffer
|
||||||
// but larger in size. this is needed to ensure that when the user zooms out, we have
|
// but larger in size. this is needed to ensure that when the user zooms out, we have
|
||||||
@@ -779,8 +776,20 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
|||||||
//
|
//
|
||||||
// (only done once)
|
// (only done once)
|
||||||
if (!topSurface) {
|
if (!topSurface) {
|
||||||
topSurface_width = param.BackBufferWidth * 3;
|
// GetSwapChain/GetBackBuffer AddRef their out parameters; the swap chain is only
|
||||||
topSurface_height = param.BackBufferHeight * 3;
|
// needed here (during one-time init), so release it after use to avoid a per-frame
|
||||||
|
// ref leak. The backbuffer ref is intentionally retained for the lifetime of the
|
||||||
|
// cached resources.
|
||||||
|
LPDIRECT3DSWAPCHAIN9 swapChain = nullptr;
|
||||||
|
D3DPRESENT_PARAMETERS param {};
|
||||||
|
|
||||||
|
pReal->GetSwapChain(0, &swapChain);
|
||||||
|
swapChain->GetPresentParameters(¶m);
|
||||||
|
|
||||||
|
backbuffer_width = param.BackBufferWidth;
|
||||||
|
backbuffer_height = param.BackBufferHeight;
|
||||||
|
topSurface_width = backbuffer_width * 3;
|
||||||
|
topSurface_height = backbuffer_height * 3;
|
||||||
topSurface_aspect_ratio = (float)topSurface_width / (float)topSurface_height;
|
topSurface_aspect_ratio = (float)topSurface_width / (float)topSurface_height;
|
||||||
if (pReal->CreateTexture(topSurface_width, topSurface_height, 1,
|
if (pReal->CreateTexture(topSurface_width, topSurface_height, 1,
|
||||||
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
|
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
|
||||||
@@ -793,16 +802,18 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
|||||||
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
|
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
|
||||||
|
|
||||||
tex->GetSurfaceLevel(0, &topSurface);
|
tex->GetSurfaceLevel(0, &topSurface);
|
||||||
if (mSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer) != D3D_OK) {
|
if (swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer) != D3D_OK) {
|
||||||
log_warning("graphics::d3d9", "SurfaceHook - GetBackBuffer failed");
|
log_warning("graphics::d3d9", "SurfaceHook - GetBackBuffer failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swapChain->Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
// pre-calculate dimensions used for phase 1
|
// pre-calculate dimensions used for phase 1
|
||||||
const int rectLeft = param.BackBufferWidth;
|
const int rectLeft = backbuffer_width;
|
||||||
const int rectTop = param.BackBufferHeight;
|
const int rectTop = backbuffer_height;
|
||||||
const int w = param.BackBufferWidth;
|
const int w = backbuffer_width;
|
||||||
const int h = param.BackBufferHeight;
|
const int h = backbuffer_height;
|
||||||
|
|
||||||
// this code used to clear the surface using ColorFill on every call, but
|
// this code used to clear the surface using ColorFill on every call, but
|
||||||
// this turned out to be very expensive, leading to major frame drops in
|
// this turned out to be very expensive, leading to major frame drops in
|
||||||
@@ -812,9 +823,7 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
|||||||
cfg::SCREENRESIZE->need_surface_clean = false;
|
cfg::SCREENRESIZE->need_surface_clean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
D3DLOCKED_RECT rect;
|
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
|
||||||
|
|
||||||
// phase 1 - copy the original back buffer onto the new surface.
|
// phase 1 - copy the original back buffer onto the new surface.
|
||||||
// we draw it 1:1 in the center of the surface.
|
// we draw it 1:1 in the center of the surface.
|
||||||
@@ -877,7 +886,6 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
|||||||
originRect2.left, originRect2.top, originRect2.right, originRect2.bottom);
|
originRect2.left, originRect2.top, originRect2.right, originRect2.bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
topSurface->UnlockRect();
|
|
||||||
|
|
||||||
// phase 2 - viewport calculation - do the actual zoom / offset math
|
// phase 2 - viewport calculation - do the actual zoom / offset math
|
||||||
// figure out what region of the surface to copy back to the back buffer.
|
// figure out what region of the surface to copy back to the back buffer.
|
||||||
@@ -952,7 +960,6 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
|||||||
// targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
|
// targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
|
||||||
|
|
||||||
// phase 3 - draw the surface to back buffer
|
// phase 3 - draw the surface to back buffer
|
||||||
backbuffer->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
|
||||||
bool use_linear_filter = true;
|
bool use_linear_filter = true;
|
||||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||||
use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter;
|
use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter;
|
||||||
@@ -961,7 +968,6 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
|||||||
topSurface, &targetRect,
|
topSurface, &targetRect,
|
||||||
backbuffer, nullptr,
|
backbuffer, nullptr,
|
||||||
use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
|
use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
|
||||||
backbuffer->UnlockRect();
|
|
||||||
if (hr != D3D_OK) {
|
if (hr != D3D_OK) {
|
||||||
log_warning(
|
log_warning(
|
||||||
"graphics::d3d9",
|
"graphics::d3d9",
|
||||||
@@ -973,10 +979,6 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
|||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() {
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() {
|
||||||
WRAP_DEBUG;
|
WRAP_DEBUG;
|
||||||
|
|
||||||
if (cfg::SCREENRESIZE->enable_screen_resize || GRAPHICS_FS_ORIENTATION_SWAP) {
|
|
||||||
SurfaceHook(pReal);
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::once_flag printed;
|
static std::once_flag printed;
|
||||||
std::call_once(printed, []() {
|
std::call_once(printed, []() {
|
||||||
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::EndScene");
|
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::EndScene");
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ static const GUID IID_WrappedIDirect3DDevice9 = {
|
|||||||
0x6dec0d40, 0x1339, 0x4bda, { 0xa5, 0xf2, 0x22, 0x31, 0xd4, 0x1, 0xf, 0xd1 }
|
0x6dec0d40, 0x1339, 0x4bda, { 0xa5, 0xf2, 0x22, 0x31, 0xd4, 0x1, 0xf, 0xd1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// applies the image resize / orientation swap by copying the back buffer through an
|
||||||
|
// oversized intermediate surface and back. expects the real (unwrapped) device.
|
||||||
|
void SurfaceHook(IDirect3DDevice9 *pReal);
|
||||||
|
|
||||||
struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||||
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9 *orig)
|
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9 *orig)
|
||||||
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(false) {
|
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(false) {
|
||||||
|
|||||||
@@ -103,19 +103,10 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
void ScreenResize::build_fullscreen_config() {
|
void ScreenResize::build_fullscreen_config() {
|
||||||
|
|
||||||
if (avs::game::is_model("KFC")) {
|
ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f),
|
||||||
ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f),
|
"Warning: Enabling Image Resize uses more GPU\n"
|
||||||
"Warning: Enabling Image Resize uses more GPU\n"
|
"resources and may lower framerate on some\n"
|
||||||
"resources and may significantly lower framerate\n"
|
"lower-end PCs.");
|
||||||
"for songs with Live2D! Results may vary, use at\n"
|
|
||||||
"your own risk.");
|
|
||||||
} else {
|
|
||||||
ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f),
|
|
||||||
"Warning: Enabling Image Resize uses more GPU\n"
|
|
||||||
"resources and may significantly lower framerate\n"
|
|
||||||
"in some situations! Results may vary, use at\n"
|
|
||||||
"your own risk.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable checkbox
|
// enable checkbox
|
||||||
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
|
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
|
||||||
|
|||||||
Reference in New Issue
Block a user