Fix DX12 mirror showing stale frames (copy the rotating back buffer)
The D3D12 capture path grabbed GetBuffer(0) every Present. Unlike D3D11 flip-model -- where DXGI keeps GetBuffer(0) pointing at the live back buffer -- D3D12 rotates buffers explicitly: the game renders into the buffer at GetCurrentBackBufferIndex(), which advances each Present. So buffer 0 only holds fresh content every Nth frame; the rest copied a stale buffer, and the mirror silently ran at refresh/N with duplicate frames in between. Every metric read full rate (Present counter, published FPS, generation bump, capture->display latency) because they count Presents, not unique content -- which is why it looked fine but felt like missing frames, especially on high-refresh DX12 games (DMC5/Myst at 144, Miles Morales). Query IDXGISwapChain3::GetCurrentBackBufferIndex() before the trampoline Present (so it's the just-rendered buffer) and copy that one; fall back to 0 only if the interface is unavailable. The DX11 path is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <d3d11on12.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <dxgi1_4.h>
|
||||
|
||||
#include <safetyhook.hpp>
|
||||
|
||||
@@ -247,8 +248,25 @@ bool ensure_on12_locked(ID3D12Device* dev)
|
||||
// via the On12 bridge and CopyResource it into the shared texture.
|
||||
void capture_backbuffer_d3d12(IDXGISwapChain* sc)
|
||||
{
|
||||
// CRITICAL: D3D12 rotates back buffers explicitly -- the game rendered into the
|
||||
// buffer at GetCurrentBackBufferIndex(), and that index advances every Present.
|
||||
// (Unlike D3D11 flip-model, where DXGI keeps GetBuffer(0) pointing at the live
|
||||
// back buffer.) Grabbing buffer 0 unconditionally copies a stale buffer on N-1 of
|
||||
// every N frames, so the mirror silently runs at refresh/N with duplicate frames
|
||||
// in between -- even though the Present counter, published FPS, generation bump,
|
||||
// and latency all read full rate (they count Presents, not unique content). We're
|
||||
// called before the trampoline Present, so the current index is the just-rendered
|
||||
// buffer. Query IDXGISwapChain3 for it; fall back to 0 only if unavailable.
|
||||
UINT bb_index = 0;
|
||||
IDXGISwapChain3* sc3 = nullptr;
|
||||
if (SUCCEEDED(sc->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void**>(&sc3))) && sc3 != nullptr)
|
||||
{
|
||||
bb_index = sc3->GetCurrentBackBufferIndex();
|
||||
sc3->Release();
|
||||
}
|
||||
|
||||
ID3D12Resource* bb = nullptr;
|
||||
if (FAILED(sc->GetBuffer(0, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&bb))) || bb == nullptr)
|
||||
if (FAILED(sc->GetBuffer(bb_index, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&bb))) || bb == nullptr)
|
||||
{
|
||||
if (!g_unsupported_logged)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user