1 Commits

Author SHA1 Message Date
itsRevela 2c23e3e54c wip: flip mode resize buffers experiment (blocked by deferred context IntRefs)
Experimental branch that tried to enable screen tearing by switching the swap chain from DXGI_SWAP_EFFECT_DISCARD (bitblt) to DXGI_SWAP_EFFECT_FLIP_DISCARD with the ALLOW_TEARING flag. Tearing in flip mode requires ResizeBuffers on window resize because flip swap chains take exclusive ownership of the HWND and cannot be destroyed-and-recreated like bitblt ones.

ResizeBuffers fails with DXGI_ERROR_INVALID_CALL (0x887A0001) every time. Diagnosis path got us to the authoritative cause via the DXGI debug layer's info queue and ID3D11Debug::ReportLiveDeviceObjects: both physical backbuffers of the flip chain have an internal reference count of 5 (D3D11 IntRef, not public COM refcount). DXGI's outstanding-references check looks at IntRef, which is why probing public refcount always showed 2 and nothing we released dropped it.

The internal refs come from the 4J Renderer library's use of a deferred context. Renderer::InitialiseContext calls ID3D11Device::CreateDeferredContext and records commands into it via Renderer::Context (stored in thread-local storage). Commands that reference the backbuffer or an RTV wrapping it pin IntRefs on the backbuffer texture until the command list is Finished and then Released. The Renderer library owns that command list and we have no handle to it, so we cannot drop those refs from the outside without reverse engineering significantly more of the 4J library.

This commit preserves the instrumentation and the failed flip mode experiment so we can return to it later if a cleaner approach becomes available. Retained in this file:
- DXGI_FEATURE_PRESENT_ALLOW_TEARING probe and g_bTearingSupported global
- FLIP_DISCARD swap chain config with ALLOW_TEARING flag
- ResizeBuffers call path in ResizeD3D with fallback recovery for when it fails
- Dedicated file logger (FlipTestLog) writing to flip_test.log
- D3D11 and DXGI info queue capture helpers
- ID3D11Debug / IDXGIDebug ReportLiveObjects wiring
- Granular refcount probes throughout the resize cleanup chain
- GPU idle wait via ID3D11Query fence

Not a fix. The next iteration goes back to bitblt and tries exclusive fullscreen for tearing instead.
2026-04-11 05:00:04 -05:00
@@ -3,6 +3,12 @@
#include "stdafx.h"
// FLIP-MODE TEST: headers for IDXGIFactory5 (tearing support probe) and
// ID3D11InfoQueue (debug layer message capture).
#include <dxgi1_5.h>
#include <d3d11sdklayers.h>
#include <dxgidebug.h>
#include <assert.h>
#include <iostream>
#include <ShellScalingApi.h>
@@ -471,6 +477,9 @@ ID3D11Device* g_pd3dDevice = nullptr;
ID3D11DeviceContext* g_pImmediateContext = nullptr;
IDXGISwapChain* g_pSwapChain = nullptr;
bool g_bVSync = false;
// FLIP-MODE TEST: set by the DXGI_FEATURE_PRESENT_ALLOW_TEARING probe in InitDevice.
// Drives the ALLOW_TEARING swap chain flag and the DXGI_PRESENT_ALLOW_TEARING Present flag.
static bool g_bTearingSupported = false;
static bool g_bPendingExclusiveFullscreen = false;
static bool g_bPendingExclusiveFullscreenValue = false;
@@ -551,6 +560,263 @@ ID3D11Texture2D* g_pDepthStencilBuffer = nullptr;
static const float kClearColorWhite[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
static const float kClearColorBlack[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
// FLIP-MODE TEST: toggle for the D3D11 debug layer.
// The debug layer has significant per-frame overhead (observed FPS drop),
// so default it off. Flip to true, rebuild, and re-run when you need to
// capture ResizeBuffers error details via the info queue.
static const bool kFlipTestEnableDebugLayer = true;
// FLIP-MODE TEST: dedicated file logger that works in release builds.
// Writes to flip_test.log next to the current working directory (normally the
// folder containing the exe). Unbuffered so we see output even if the game
// crashes mid-operation. Also echoes to OutputDebugString so DebugView / a
// debugger sees it too.
static FILE* g_pFlipTestLog = nullptr;
static void FlipTestLogEnsureOpen()
{
if (g_pFlipTestLog) return;
fopen_s(&g_pFlipTestLog, "flip_test.log", "w");
if (!g_pFlipTestLog) return;
setvbuf(g_pFlipTestLog, nullptr, _IONBF, 0); // unbuffered
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(g_pFlipTestLog,
"=== FLIP TEST LOG (started %04d-%02d-%02d %02d:%02d:%02d) ===\n",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
}
static void FlipTestLog(const char* fmt, ...)
{
FlipTestLogEnsureOpen();
char buf[2048];
va_list args;
va_start(args, fmt);
_vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args);
va_end(args);
if (g_pFlipTestLog) { fputs(buf, g_pFlipTestLog); fflush(g_pFlipTestLog); }
OutputDebugStringA(buf);
}
// FLIP-MODE TEST: drain and print every stored D3D11 debug layer message.
// Call this after operations we want to diagnose (CreateDevice, ResizeBuffers).
// Silently does nothing if the debug layer is unavailable (returns NULL for
// the InfoQueue interface), but still writes a log line so we know it was
// called.
static void DumpD3D11InfoQueue(const char* label)
{
if (!g_pd3dDevice) { FlipTestLog("[InfoQueue] %s: no device\n", label); return; }
ID3D11InfoQueue* pInfoQueue = nullptr;
HRESULT hr = g_pd3dDevice->QueryInterface(__uuidof(ID3D11InfoQueue), (void**)&pInfoQueue);
if (FAILED(hr) || !pInfoQueue)
{
FlipTestLog("[InfoQueue] %s: unavailable (debug layer not active)\n", label);
return;
}
UINT64 numMessages = pInfoQueue->GetNumStoredMessages();
FlipTestLog("[InfoQueue] %s: %llu queued message(s)\n",
label, (unsigned long long)numMessages);
for (UINT64 i = 0; i < numMessages; i++)
{
SIZE_T msgLen = 0;
hr = pInfoQueue->GetMessage(i, nullptr, &msgLen);
if (FAILED(hr) || msgLen == 0) continue;
D3D11_MESSAGE* pMsg = (D3D11_MESSAGE*)malloc(msgLen);
if (!pMsg) continue;
hr = pInfoQueue->GetMessage(i, pMsg, &msgLen);
if (SUCCEEDED(hr))
{
const char* sev = "?";
switch (pMsg->Severity)
{
case D3D11_MESSAGE_SEVERITY_CORRUPTION: sev = "CORRUPTION"; break;
case D3D11_MESSAGE_SEVERITY_ERROR: sev = "ERROR"; break;
case D3D11_MESSAGE_SEVERITY_WARNING: sev = "WARNING"; break;
case D3D11_MESSAGE_SEVERITY_INFO: sev = "INFO"; break;
case D3D11_MESSAGE_SEVERITY_MESSAGE: sev = "MESSAGE"; break;
}
FlipTestLog(" [%s] #%d: %.*s\n", sev, (int)pMsg->ID,
(int)pMsg->DescriptionByteLength, pMsg->pDescription);
}
free(pMsg);
}
pInfoQueue->ClearStoredMessages();
pInfoQueue->Release();
}
// FLIP-MODE TEST: configure the D3D11 debug layer's info queue.
static void SetupD3D11InfoQueue()
{
if (!g_pd3dDevice) return;
ID3D11InfoQueue* pInfoQueue = nullptr;
HRESULT hr = g_pd3dDevice->QueryInterface(__uuidof(ID3D11InfoQueue), (void**)&pInfoQueue);
if (FAILED(hr) || !pInfoQueue)
{
FlipTestLog("[InfoQueue] not available (debug layer not installed or not enabled)\n");
return;
}
D3D11_INFO_QUEUE_FILTER filter = {};
D3D11_MESSAGE_SEVERITY denied[] = { D3D11_MESSAGE_SEVERITY_INFO };
filter.DenyList.NumSeverities = ARRAYSIZE(denied);
filter.DenyList.pSeverityList = denied;
pInfoQueue->PushStorageFilter(&filter);
// Raise the storage limit well above the default (1024) so ReportLive
// output from busy games doesn't get truncated.
pInfoQueue->SetMessageCountLimit((UINT64)-1);
FlipTestLog("[InfoQueue] installed, CORRUPTION/ERROR/WARNING captured, limit=unlimited\n");
pInfoQueue->Release();
}
// FLIP-MODE TEST: the D3D11 InfoQueue only captures D3D11 messages.
// IDXGISwapChain::ResizeBuffers is a DXGI call, so its errors go to the
// SEPARATE DXGI info queue. We obtain it via DXGIGetDebugInterface from
// dxgidebug.dll (loaded dynamically to avoid an import table dependency).
//
// kDXGIDebugAll is a GUID declared in dxgidebug.h via DEFINE_GUID, which
// only produces an `extern` declaration unless `dxguid.lib` is linked OR
// `INITGUID` is defined before the include. Our link line doesn't have
// dxguid.lib, so we define the GUID locally as a static const to avoid
// adding a lib dependency or fighting with INITGUID ordering.
static const GUID kDXGIDebugAll =
{ 0xe48ae283, 0xda80, 0x490b, { 0x87, 0xe6, 0x43, 0xe9, 0xa9, 0xcf, 0xda, 0x08 } };
static IDXGIInfoQueue* g_pDXGIInfoQueue = nullptr;
// Forward declarations (ReportLiveObjects calls DumpDXGIInfoQueue which is defined below)
static void DumpDXGIInfoQueue(const char* label);
static void SetupDXGIInfoQueue()
{
HMODULE hDxgiDebug = LoadLibraryA("dxgidebug.dll");
if (!hDxgiDebug)
{
FlipTestLog("[DXGIInfoQueue] dxgidebug.dll not loaded (GetLastError=%lu)\n", GetLastError());
return;
}
typedef HRESULT (WINAPI *PFN_DXGI_GET_DEBUG_INTERFACE)(REFIID, void**);
PFN_DXGI_GET_DEBUG_INTERFACE pGet =
(PFN_DXGI_GET_DEBUG_INTERFACE)GetProcAddress(hDxgiDebug, "DXGIGetDebugInterface");
if (!pGet)
{
FlipTestLog("[DXGIInfoQueue] DXGIGetDebugInterface symbol not found\n");
return;
}
HRESULT hr = pGet(__uuidof(IDXGIInfoQueue), (void**)&g_pDXGIInfoQueue);
if (FAILED(hr) || !g_pDXGIInfoQueue)
{
FlipTestLog("[DXGIInfoQueue] DXGIGetDebugInterface failed hr=0x%08X\n", (unsigned)hr);
return;
}
// Filter out INFO noise, keep CORRUPTION/ERROR/WARNING.
DXGI_INFO_QUEUE_FILTER filter = {};
DXGI_INFO_QUEUE_MESSAGE_SEVERITY denied[] = { DXGI_INFO_QUEUE_MESSAGE_SEVERITY_INFO };
filter.DenyList.NumSeverities = ARRAYSIZE(denied);
filter.DenyList.pSeverityList = denied;
g_pDXGIInfoQueue->PushStorageFilter(kDXGIDebugAll, &filter);
g_pDXGIInfoQueue->SetMessageCountLimit(kDXGIDebugAll, (UINT64)-1);
FlipTestLog("[DXGIInfoQueue] installed, DXGI CORRUPTION/ERROR/WARNING captured, limit=unlimited\n");
}
// FLIP-MODE TEST: dump every live D3D11 and DXGI object via ReportLiveObjects.
// This is the definitive way to see what's holding refs to the swap chain
// buffers. The output goes to the info queues as MESSAGE-severity items, which
// our filter lets through.
static void ReportLiveObjects(const char* label)
{
FlipTestLog("[ReportLive] %s:\n", label);
// D3D11 side
if (g_pd3dDevice)
{
ID3D11Debug* pDebug = nullptr;
HRESULT hr = g_pd3dDevice->QueryInterface(__uuidof(ID3D11Debug), (void**)&pDebug);
if (SUCCEEDED(hr) && pDebug)
{
FlipTestLog(" calling ID3D11Debug::ReportLiveDeviceObjects(DETAIL)\n");
pDebug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
pDebug->Release();
}
else
{
FlipTestLog(" ID3D11Debug not available hr=0x%08X\n", (unsigned)hr);
}
}
// DXGI side (via dxgidebug.dll dynamic load)
HMODULE hDxgiDebug = LoadLibraryA("dxgidebug.dll");
if (hDxgiDebug)
{
typedef HRESULT (WINAPI *PFN_DXGI_GET_DEBUG_INTERFACE)(REFIID, void**);
PFN_DXGI_GET_DEBUG_INTERFACE pGet =
(PFN_DXGI_GET_DEBUG_INTERFACE)GetProcAddress(hDxgiDebug, "DXGIGetDebugInterface");
if (pGet)
{
IDXGIDebug* pDXGIDebug = nullptr;
HRESULT hr = pGet(__uuidof(IDXGIDebug), (void**)&pDXGIDebug);
if (SUCCEEDED(hr) && pDXGIDebug)
{
FlipTestLog(" calling IDXGIDebug::ReportLiveObjects(ALL, RLO_ALL)\n");
// DXGI_DEBUG_RLO_ALL = 0x7 (SUMMARY | DETAIL | IGNORE_INTERNAL)
pDXGIDebug->ReportLiveObjects(kDXGIDebugAll, DXGI_DEBUG_RLO_ALL);
pDXGIDebug->Release();
}
else
{
FlipTestLog(" IDXGIDebug not available hr=0x%08X\n", (unsigned)hr);
}
}
}
// Drain both info queues to capture the ReportLive output
DumpD3D11InfoQueue("ReportLive (D3D11)");
DumpDXGIInfoQueue("ReportLive (DXGI)");
}
static void DumpDXGIInfoQueue(const char* label)
{
if (!g_pDXGIInfoQueue) { FlipTestLog("[DXGIInfoQueue] %s: not installed\n", label); return; }
UINT64 numMessages = g_pDXGIInfoQueue->GetNumStoredMessages(kDXGIDebugAll);
FlipTestLog("[DXGIInfoQueue] %s: %llu queued message(s)\n",
label, (unsigned long long)numMessages);
for (UINT64 i = 0; i < numMessages; i++)
{
SIZE_T msgLen = 0;
HRESULT hr = g_pDXGIInfoQueue->GetMessage(kDXGIDebugAll, i, nullptr, &msgLen);
if (FAILED(hr) || msgLen == 0) continue;
DXGI_INFO_QUEUE_MESSAGE* pMsg = (DXGI_INFO_QUEUE_MESSAGE*)malloc(msgLen);
if (!pMsg) continue;
hr = g_pDXGIInfoQueue->GetMessage(kDXGIDebugAll, i, pMsg, &msgLen);
if (SUCCEEDED(hr))
{
const char* sev = "?";
switch (pMsg->Severity)
{
case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION: sev = "CORRUPTION"; break;
case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR: sev = "ERROR"; break;
case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_WARNING: sev = "WARNING"; break;
case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_INFO: sev = "INFO"; break;
case DXGI_INFO_QUEUE_MESSAGE_SEVERITY_MESSAGE: sev = "MESSAGE"; break;
}
FlipTestLog(" DXGI [%s] #%d: %.*s\n", sev, (int)pMsg->ID,
(int)pMsg->DescriptionByteLength, pMsg->pDescription);
}
free(pMsg);
}
g_pDXGIInfoQueue->ClearStoredMessages(kDXGIDebugAll);
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
@@ -876,10 +1142,16 @@ HRESULT InitDevice()
height = g_rScreenHeight;
//app.DebugPrintf("width: %d, height: %d\n", width, height);
FlipTestLog("[InitDevice] entering, requested %dx%d\n", width, height);
UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// FLIP-MODE TEST: debug layer costs a lot of FPS, so only enable it when
// the test toggle is set AND we actually want to capture info queue data.
if (kFlipTestEnableDebugLayer)
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
D3D_DRIVER_TYPE driverTypes[] =
{
@@ -897,44 +1169,89 @@ HRESULT InitDevice()
};
UINT numFeatureLevels = ARRAYSIZE( featureLevels );
// Use the legacy bitblt DISCARD swap model (SwapEffect left as default 0).
// DXGI_SWAP_EFFECT_FLIP_DISCARD gives lower latency and tearing support, but
// takes exclusive ownership of the HWND — which makes window resize via
// CreateSwapChain fail with E_ACCESSDENIED and ResizeBuffers fail with
// DXGI_ERROR_INVALID_CALL (the closed-source 4J Renderer holds hidden
// backbuffer refs we can't release). Bitblt DISCARD has no HWND lock, so
// the "destroy old, create new" resize path in ResizeD3D() works cleanly.
// VSync toggle still works via the SyncInterval parameter on Present().
// FLIP-MODE TEST: probe for tearing support before creating the swap chain.
// Needs IDXGIFactory5 (Windows 10 Anniversary Update+).
{
IDXGIFactory5* factory5 = nullptr;
if (SUCCEEDED(CreateDXGIFactory1(__uuidof(IDXGIFactory5), (void**)&factory5)))
{
BOOL allowTearing = FALSE;
if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing))))
g_bTearingSupported = (allowTearing == TRUE);
factory5->Release();
}
FlipTestLog("[InitDevice] tearing support = %s\n", g_bTearingSupported ? "yes" : "no");
}
// FLIP-MODE TEST: FLIP_DISCARD swap chain for low-latency presentation
// with tearing support. BufferCount must be >= 2 for flip mode.
// SHADER_INPUT is valid for flip mode (per DXGI docs) so we keep it.
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory( &sd, sizeof( sd ) );
sd.BufferCount = 1;
sd.BufferCount = 2;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.RefreshRate.Numerator = 0;
sd.BufferDesc.RefreshRate.Denominator = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
FlipTestLog("[InitDevice] calling D3D11CreateDeviceAndSwapChain (flags=0x%X, flip=yes, bc=2, tearing=%s)\n",
createDeviceFlags, g_bTearingSupported ? "yes" : "no");
for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
{
g_driverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain( nullptr, g_driverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &g_featureLevel, &g_pImmediateContext );
FlipTestLog("[InitDevice] driver %u result hr=0x%08X\n", driverTypeIndex, (unsigned)hr);
if( HRESULT_SUCCEEDED( hr ) )
break;
}
// FLIP-MODE TEST: if the debug layer isn't installed, retry without the flag
// so the game still boots. We lose info queue capture but at least we see
// if flip mode itself works.
if (FAILED(hr) && (createDeviceFlags & D3D11_CREATE_DEVICE_DEBUG))
{
FlipTestLog("[InitDevice] retrying without debug flag\n");
createDeviceFlags &= ~D3D11_CREATE_DEVICE_DEBUG;
for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
{
g_driverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain( nullptr, g_driverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &g_featureLevel, &g_pImmediateContext );
if( HRESULT_SUCCEEDED( hr ) )
break;
}
}
if( FAILED( hr ) )
{
FlipTestLog("[InitDevice] FAILED final hr=0x%08X\n", (unsigned)hr);
return hr;
}
FlipTestLog("[InitDevice] D3D11CreateDeviceAndSwapChain OK, device=%p swapchain=%p context=%p\n",
g_pd3dDevice, g_pSwapChain, g_pImmediateContext);
// FLIP-MODE TEST: install BOTH the D3D11 info queue (for D3D11 errors) and
// the DXGI info queue (for DXGI/swap-chain errors — which is what ResizeBuffers
// emits). These are separate layers and need separate setup.
SetupD3D11InfoQueue();
SetupDXGIInfoQueue();
DumpD3D11InfoQueue("after CreateDevice");
DumpDXGIInfoQueue("after CreateDevice");
// Create a render target view
ID3D11Texture2D* pBackBuffer = nullptr;
hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBackBuffer );
if( FAILED( hr ) )
return hr;
FlipTestLog("[InitDevice] initial backbuffer (GetBuffer 0 at init) = %p\n", (void*)pBackBuffer);
// Create a depth stencil buffer
D3D11_TEXTURE2D_DESC descDepth;
@@ -1002,9 +1319,10 @@ void Render()
//--------------------------------------------------------------------------------------
static bool ResizeD3D(int newW, int newH)
{
if (newW <= 0 || newH <= 0) return false;
if (!g_pSwapChain) return false;
if (!g_bResizeReady) return false;
FlipTestLog("[ResizeD3D] called with %dx%d\n", newW, newH);
if (newW <= 0 || newH <= 0) { FlipTestLog("[ResizeD3D] bad size, skip\n"); return false; }
if (!g_pSwapChain) { FlipTestLog("[ResizeD3D] no swap chain, skip\n"); return false; }
if (!g_bResizeReady) { FlipTestLog("[ResizeD3D] not ready, skip\n"); return false; }
int bbW = newW;
int bbH = newH;
@@ -1054,77 +1372,160 @@ static bool ResizeD3D(int newW, int newH)
*pRM_BBWidth, *pRM_BBHeight, oldScDesc.BufferDesc.Width, oldScDesc.BufferDesc.Height);
}
// FLIP-MODE TEST: granular refcount probing to find who holds the hidden
// backbuffer ref. Probe returns the backbuffer refcount AFTER dropping the
// ref we added via GetBuffer. In flip mode, the expected steady-state count
// is typically 1 (DXGI) or 2 (DXGI + DWM composition).
auto probeBBRefcount = [](const char* stage) {
if (!g_pSwapChain) { FlipTestLog("[ResizeD3D] refcount %s = (no swap chain)\n", stage); return; }
ID3D11Texture2D* pBB = nullptr;
HRESULT hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBB);
if (FAILED(hr) || !pBB) { FlipTestLog("[ResizeD3D] refcount %s = (GetBuffer failed hr=0x%08X)\n", stage, (unsigned)hr); return; }
ULONG rc = pBB->Release();
FlipTestLog("[ResizeD3D] refcount %s = %lu (backbuffer ptr=%p)\n", stage, rc, (void*)pBB);
};
probeBBRefcount("at ResizeD3D entry");
FlipTestLog("[ResizeD3D] g_pRenderTargetView=%p g_pDepthStencilView=%p g_pDepthStencilBuffer=%p\n",
(void*)g_pRenderTargetView, (void*)g_pDepthStencilView, (void*)g_pDepthStencilBuffer);
FlipTestLog("[ResizeD3D] Renderer slots on entry: RTV=%p SRV=%p DSV=%p\n",
(void*)*ppRM_RTV, (void*)*ppRM_SRV, (void*)*ppRM_DSV);
RenderManager.Suspend();
while (!RenderManager.Suspended()) { Sleep(1); }
probeBBRefcount("after RenderManager.Suspend");
PostProcesser::GetInstance().Cleanup();
probeBBRefcount("after PostProcesser::Cleanup");
// Explicitly unbind all render targets BEFORE ClearState. ClearState should
// also do this, but being explicit costs nothing and protects against
// driver quirks.
{
ID3D11RenderTargetView* nullRTV[8] = {};
g_pImmediateContext->OMSetRenderTargets(8, nullRTV, NULL);
}
probeBBRefcount("after OMSetRenderTargets(null)");
g_pImmediateContext->ClearState();
g_pImmediateContext->Flush();
probeBBRefcount("after ClearState");
// Release OUR views and depth buffer
g_pImmediateContext->Flush();
probeBBRefcount("after Flush");
// FLIP-MODE TEST: wait for GPU to finish all pending work before touching
// the swap chain. Flush() submits commands but doesn't wait. An event
// query gives us a proper GPU-side fence.
{
D3D11_QUERY_DESC qd = {};
qd.Query = D3D11_QUERY_EVENT;
ID3D11Query* pQuery = nullptr;
if (SUCCEEDED(g_pd3dDevice->CreateQuery(&qd, &pQuery)) && pQuery)
{
g_pImmediateContext->End(pQuery);
BOOL done = FALSE;
int spins = 0;
while (spins++ < 1000)
{
HRESULT hrQ = g_pImmediateContext->GetData(pQuery, &done, sizeof(done), 0);
if (hrQ == S_OK && done) break;
Sleep(1);
}
FlipTestLog("[ResizeD3D] GPU idle wait: %d spins, done=%d\n", spins, (int)done);
pQuery->Release();
}
}
probeBBRefcount("after GPU idle wait");
// Probe refcounts on the RTV objects themselves. AddRef/Release trick:
// Release returns the new refcount after dropping our AddRef, which tells
// us how many other refs existed.
if (g_pRenderTargetView) {
g_pRenderTargetView->AddRef();
ULONG rtvRc = g_pRenderTargetView->Release();
FlipTestLog("[ResizeD3D] g_pRenderTargetView refcount (self) = %lu\n", rtvRc);
}
if (*ppRM_RTV) {
(*ppRM_RTV)->AddRef();
ULONG rmRtvRc = (*ppRM_RTV)->Release();
FlipTestLog("[ResizeD3D] *ppRM_RTV refcount (self) = %lu\n", rmRtvRc);
}
FlipTestLog("[ResizeD3D] about to Release(g_pRenderTargetView=%p)\n", (void*)g_pRenderTargetView);
if (g_pRenderTargetView) { g_pRenderTargetView->Release(); g_pRenderTargetView = NULL; }
probeBBRefcount("after Release(g_pRenderTargetView)");
if (g_pDepthStencilView) { g_pDepthStencilView->Release(); g_pDepthStencilView = NULL; }
if (g_pDepthStencilBuffer) { g_pDepthStencilBuffer->Release(); g_pDepthStencilBuffer = NULL; }
probeBBRefcount("after Release(g_pDepth*)");
// Release the Renderer's internal RTV (at offset 0x28). This is a
// DIFFERENT RTV object from g_pRenderTargetView, created by something
// during Renderer::Initialise, that also wraps the backbuffer. If
// ResizeBuffers fails, the fallback recovery path will recreate it from
// the current swap chain so in-world rendering keeps working.
if (*ppRM_RTV) {
FlipTestLog("[ResizeD3D] releasing *ppRM_RTV=%p\n", (void*)*ppRM_RTV);
(*ppRM_RTV)->Release();
*ppRM_RTV = nullptr;
probeBBRefcount("after Release(*ppRM_RTV)");
}
gdraw_D3D11_PreReset();
probeBBRefcount("after gdraw_D3D11_PreReset");
// Get IDXGIFactory from the existing device BEFORE destroying the old swap
// chain. If anything fails before we have a new swap chain, we abort
// without destroying the old one — leaving the Renderer in a valid
// (old-size) state.
IDXGISwapChain* pOldSwapChain = g_pSwapChain;
// FLIP-MODE TEST: use ResizeBuffers instead of destroy-and-create.
IDXGISwapChain* pOldSwapChain = g_pSwapChain; // kept for recovery-path symmetry
bool success = false;
HRESULT hr;
IDXGIDevice* dxgiDevice = NULL;
IDXGIAdapter* dxgiAdapter = NULL;
IDXGIFactory* dxgiFactory = NULL;
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
if (FAILED(hr)) goto postReset;
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
if (FAILED(hr)) { dxgiDevice->Release(); goto postReset; }
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
dxgiAdapter->Release();
dxgiDevice->Release();
if (FAILED(hr)) goto postReset;
FlipTestLog("[ResizeD3D] pre-resize cleanup done, about to probe backbuffer\n");
// Create a brand-new swap chain at the target size and swap it in.
// Must use the SAME swap-chain config as InitDevice (legacy bitblt
// DISCARD model), otherwise DXGI may return E_ACCESSDENIED. The
// Renderer's old RTV/SRV/DSV are intentionally NOT released here — they
// become orphaned with the old swap chain (tiny leak, but avoids
// fighting unknown refs inside the closed-source Renderer library).
// Probe the backbuffer refcount just before ResizeBuffers. If it's > 1,
// something else is holding a hidden ref and ResizeBuffers will fail.
{
DXGI_SWAP_CHAIN_DESC sd = {};
sd.BufferCount = 1;
sd.BufferDesc.Width = bbW;
sd.BufferDesc.Height = bbH;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
IDXGISwapChain* pNewSwapChain = NULL;
hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain);
dxgiFactory->Release();
if (FAILED(hr) || pNewSwapChain == NULL)
ID3D11Texture2D* pBB = nullptr;
HRESULT hrProbe = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBB);
if (SUCCEEDED(hrProbe) && pBB != nullptr)
{
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X — keeping old swap chain\n", (unsigned)hr);
goto postReset;
ULONG rc = pBB->Release(); // Release returns the new refcount
FlipTestLog("[ResizeD3D] backbuffer refcount just before ResizeBuffers = %lu (want 1)\n", rc);
}
else
{
FlipTestLog("[ResizeD3D] GetBuffer probe failed hr=0x%08X\n", (unsigned)hrProbe);
}
// New swap chain created successfully — NOW destroy the old one.
pOldSwapChain->Release();
g_pSwapChain = pNewSwapChain;
}
// Patch Renderer's swap chain pointer to the new raw swap chain.
*ppRM_SC = g_pSwapChain;
// Call ResizeBuffers. Pass 0 for BufferCount and DXGI_FORMAT_UNKNOWN so DXGI
// preserves whatever the swap chain was originally created with. Flags MUST
// match the creation flags (ALLOW_TEARING must be set if it was set at create).
{
UINT resizeFlags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
// FLIP-MODE TEST: right before ResizeBuffers, ask both debug layers to
// enumerate every live object. DXGI's error #19 says buffer references
// are outstanding but doesn't name them; ReportLiveObjects will.
ReportLiveObjects("pre-ResizeBuffers");
FlipTestLog("[ResizeD3D] calling ResizeBuffers(0, %d, %d, DXGI_FORMAT_UNKNOWN, 0x%X)\n",
bbW, bbH, resizeFlags);
// Drain any stale DXGI messages from before the call so the post-call dump is clean
DumpDXGIInfoQueue("pre-ResizeBuffers (drain)");
hr = g_pSwapChain->ResizeBuffers(0, (UINT)bbW, (UINT)bbH, DXGI_FORMAT_UNKNOWN, resizeFlags);
FlipTestLog("[ResizeD3D] ResizeBuffers returned hr=0x%08X\n", (unsigned)hr);
if (FAILED(hr))
{
DumpD3D11InfoQueue("after ResizeBuffers fail");
DumpDXGIInfoQueue("after ResizeBuffers fail");
goto postReset;
}
DumpD3D11InfoQueue("after ResizeBuffers success");
DumpDXGIInfoQueue("after ResizeBuffers success");
}
// Swap chain pointer is unchanged after ResizeBuffers. The Renderer's
// internal m_pSwapChain field (offset 0x20) still points to the correct
// object, so no patch needed.
// Create render target views from new backbuffer
{
@@ -1229,6 +1630,20 @@ postReset:
pBB->Release();
}
}
// FLIP-MODE TEST: restore *ppRM_RTV if we nullified it, so in-world
// rendering keeps working after a failed ResizeBuffers. Create a new
// RTV against the current (unchanged) backbuffer.
if (*ppRM_RTV == NULL)
{
ID3D11Texture2D* pBB2 = NULL;
if (SUCCEEDED(g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBB2)))
{
HRESULT hrRe = g_pd3dDevice->CreateRenderTargetView(pBB2, NULL, ppRM_RTV);
FlipTestLog("[ResizeD3D] recovery: recreated *ppRM_RTV hr=0x%08X new=%p\n",
(unsigned)hrRe, (void*)*ppRM_RTV);
pBB2->Release();
}
}
if (g_pDepthStencilView == NULL)
{
D3D11_TEXTURE2D_DESC dd = {};
@@ -1260,7 +1675,7 @@ postReset:
ui.updateScreenSize(recW, recH);
}
app.DebugPrintf("[RESIZE] FAILED but recovered views at %dx%d\n", g_rScreenWidth, g_rScreenHeight);
FlipTestLog("[ResizeD3D] FAILED path: recovered views at %dx%d\n", g_rScreenWidth, g_rScreenHeight);
}
gdraw_D3D11_PostReset();
@@ -1272,6 +1687,8 @@ postReset:
if (success)
PostProcesser::GetInstance().Init();
FlipTestLog("[ResizeD3D] returning %s (final backbuffer %dx%d)\n",
success ? "success" : "FAILURE", g_rScreenWidth, g_rScreenHeight);
return success;
}
@@ -1860,10 +2277,12 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
// Present the frame.
// RenderManager.Present() hardcodes SyncInterval=1 internally.
// When VSync is off, bypass it and call the swap chain directly
// with SyncInterval=0.
// with SyncInterval=0 and DXGI_PRESENT_ALLOW_TEARING so DWM lets the
// frame tear instead of VSync-locking us to the refresh rate.
if (!g_bVSync && g_pSwapChain)
{
HRESULT hrPresent = g_pSwapChain->Present(0, 0);
UINT presentFlags = g_bTearingSupported ? DXGI_PRESENT_ALLOW_TEARING : 0;
HRESULT hrPresent = g_pSwapChain->Present(0, presentFlags);
// If the direct Present fails (e.g. during fullscreen transition),
// fall back to the library's VSync'd Present for this frame.
if (FAILED(hrPresent))