Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c23e3e54c |
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user