Files
renderdoc/util/test/demos/d3d11/d3d11_refcount_check.cpp
baldurk 856c838def Update copyright years to 2026 and fix copyright ranges
* In a previous update in 2021 many copyright ranges were truncated
  accidentally, and some files have been copy-pasted with wrong years. These
  dates have been fixed based on git history and original copyright messages.
2026-01-05 14:17:28 +00:00

1055 lines
31 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2018-2026 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "d3d11_test.h"
typedef int(WINAPI *PFN_BEGIN_EVENT)(DWORD, WCHAR *);
typedef int(WINAPI *PFN_END_EVENT)();
RD_TEST(D3D11_Refcount_Check, D3D11GraphicsTest)
{
static constexpr const char *Description =
"Ensures that the device etc doesn't delete itself when there are still outstanding "
"references, and also that it *does* delete itself when any cycle is detected.";
D3D11GraphicsTest reftest;
void Prepare(int argc, char **argv)
{
reftest.headless = true;
reftest.Prepare(argc, argv);
D3D11GraphicsTest::Prepare(argc, argv);
}
#define CHECK_REFCOUNT(obj, expected) \
{ \
ULONG count = GetRefcount(obj); \
if(count != expected) \
{ \
if(!failed) \
DEBUG_BREAK(); \
failed = true; \
TEST_WARN(#obj " has wrong reference count. Got %u expected %u", count, expected); \
} \
}
bool IsICDLoaded()
{
if(rdoc || reftest.rdoc)
{
// renderdoc keeps driver DLLs around to avoid race condition bugs, so we can't check on those
// DLLs being unloaded.
// Instead we hack by calling D3D9's Begin/EndEvent. If there's no D3D11 device alive it
// always returns 0, otherwise it returns the nesting level minus 1. Since we don't have a
// device/context to force it to drain the annotation queue we take advantage of the nesting
// level starting at 0, so calling an unbalanced end() will return -1.
static HMODULE d3d9 = LoadLibraryA("d3d9.dll");
static PFN_BEGIN_EVENT begin = (PFN_BEGIN_EVENT)GetProcAddress(d3d9, "D3DPERF_BeginEvent");
static PFN_END_EVENT end = (PFN_END_EVENT)GetProcAddress(d3d9, "D3DPERF_EndEvent");
// don't care about these being unbalanced
return end() != 0;
}
// a bit of a hack but I don't know of a better way to test if the device was really destroyed
return GetModuleHandleA("nvwgf2um.dll") != NULL || GetModuleHandleA("nvwgf2umx.dll") != NULL ||
GetModuleHandleA("atidxx32.dll") != NULL || GetModuleHandleA("atidxx64.dll") != NULL ||
GetModuleHandleA("igd10iumd32.dll") != NULL ||
GetModuleHandleA("igd10iumd64.dll") != NULL;
}
bool HasMessages(ID3D11InfoQueue * infoQueue, const std::vector<std::string> &haystacks)
{
std::string concat;
UINT64 num = infoQueue->GetNumStoredMessages();
for(UINT64 i = 0; i < num; i++)
{
SIZE_T len = 0;
infoQueue->GetMessage(i, NULL, &len);
char *msgbuf = new char[len];
D3D11_MESSAGE *message = (D3D11_MESSAGE *)msgbuf;
infoQueue->GetMessage(i, message, &len);
if(message->Severity == D3D11_MESSAGE_SEVERITY_INFO)
concat += "INFO: ";
concat += message->pDescription;
concat += "\n";
delete[] msgbuf;
}
infoQueue->ClearStoredMessages();
bool ret = true;
for(const std::string &haystack : haystacks)
ret &= (concat.find(haystack) != std::string::npos);
return ret;
}
bool HasMessage(ID3D11InfoQueue * infoQueue, const std::string &haystack)
{
return HasMessages(infoQueue, {haystack});
}
int main()
{
// force a debug device
reftest.debugDevice = true;
if(!reftest.Init())
return 4;
{
D3D_FEATURE_LEVEL features[] = {D3D_FEATURE_LEVEL_11_0};
ULONG ret = 0;
UINT dummy[] = {16, 16, 16, 16, 16};
bool failed = false;
static const GUID unwrappedID3D11InfoQueue__uuid = {
0x3fc4e618, 0x3f70, 0x452a, {0x8b, 0x8f, 0xa7, 0x3a, 0xcc, 0xb5, 0x8e, 0x3d}};
// for the first device enable INFO for creation/destruction
ID3D11InfoQueue *infoQueue = NULL;
// try first with renderdoc's GUID to get the unwrapped queue for testing against
reftest.dev.QueryInterface(unwrappedID3D11InfoQueue__uuid, &infoQueue);
if(infoQueue == NULL)
reftest.dev.QueryInterface(__uuidof(ID3D11InfoQueue), &infoQueue);
infoQueue->ClearStorageFilter();
infoQueue->ClearRetrievalFilter();
infoQueue->ClearStoredMessages();
ID3D11Debug *dbg = NULL;
reftest.dev.QueryInterface(__uuidof(ID3D11Debug), &dbg);
// remove our references to everything but vb which we take locally
reftest.defaultLayout = NULL;
reftest.swap = NULL;
reftest.bbTex = NULL;
reftest.bbRTV = NULL;
reftest.dev1 = NULL;
reftest.dev2 = NULL;
reftest.dev3 = NULL;
reftest.dev4 = NULL;
reftest.dev5 = NULL;
reftest.ctx = NULL;
reftest.ctx1 = NULL;
reftest.ctx2 = NULL;
reftest.ctx3 = NULL;
reftest.ctx4 = NULL;
reftest.annot = NULL;
reftest.swapBlitVS = NULL;
reftest.swapBlitPS = NULL;
reftest.DefaultTriVS = NULL;
reftest.DefaultTriPS = NULL;
reftest.DefaultTriVB = NULL;
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
// reference counting behaviour is NOT CONTRACTUAL but some applications check for it anyway.
// this is particularly annoying when they're checking for implementation details, like
// whether a resource hits 0 refcount even if it's still bound somewhere, etc.
// The below refcounting behaviour was accurate for the D3D11 runtime at time of writing, and
// we check it against renderdoc which is based on emulating that behaviour enough to fit this
// test.
// grab the device into a local pointer so we can AddRef / Release manually
ID3D11Device *localdev = reftest.dev;
localdev->AddRef();
reftest.dev = NULL;
ID3D11DeviceContext *localctx = NULL;
localdev->GetImmediateContext(&localctx);
////////////////////////////////////////////////////////////
// Create a VB and test basic 'child resource' <-> device refcounting
ID3D11BufferPtr buf = D3D11BufferCreator(localdev).Vertex().Data(DefaultTri);
ID3D11Buffer *localvb = buf;
localvb->AddRef();
buf = NULL;
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
// the device should have 5 references - localdev, localctx, localvb, dbg, and infoQueue
CHECK_REFCOUNT(localdev, 5);
// the VB has one reference
CHECK_REFCOUNT(localvb, 1);
// add 3 refs to the vertex buffer
localvb->AddRef();
localvb->AddRef();
localvb->AddRef();
// the device should still only have 5 references, localvb only holds one on the device
CHECK_REFCOUNT(localdev, 5);
// but the VB has 4 references
CHECK_REFCOUNT(localvb, 4);
localvb->Release();
localvb->Release();
localvb->Release();
CHECK_REFCOUNT(localdev, 5);
CHECK_REFCOUNT(localvb, 1);
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
////////////////////////////////////////////////////////////
// in spite of being cached, state objects should not refcount strangely (apart from
// duplicates sharing a pointer)
{
D3D11_RASTERIZER_DESC rsdesc = {};
// ensure this isn't the default rasterizer state
rsdesc.CullMode = D3D11_CULL_BACK;
rsdesc.FillMode = D3D11_FILL_WIREFRAME;
rsdesc.DepthBias = 55;
ID3D11RasterizerState *rs1 = NULL, *rs2 = NULL, *rs3 = NULL;
localdev->CreateRasterizerState(&rsdesc, &rs1);
CHECK_REFCOUNT(localdev, 6);
CHECK_REFCOUNT(rs1, 1);
// change the state, get a new object
rsdesc.DepthBias = 99;
localdev->CreateRasterizerState(&rsdesc, &rs2);
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(rs1, 1);
CHECK_REFCOUNT(rs2, 1);
// keep the same state, get the same object
localdev->CreateRasterizerState(&rsdesc, &rs3);
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(rs1, 1);
CHECK_REFCOUNT(rs2, 2);
CHECK_REFCOUNT(rs3, 2);
if(rs2 != rs3)
{
failed = true;
TEST_ERROR("Expected to get the same state object back");
}
rs1->Release();
rs2->Release();
rs3->Release();
}
CHECK_REFCOUNT(localdev, 5);
////////////////////////////////////////////////////////////
// create a texture and check view <-> resource <-> device refcounting
ID3D11Texture2DPtr tex =
D3D11TextureCreator(localdev, DXGI_FORMAT_BC1_UNORM, 128, 128, 1).SRV();
ID3D11Texture2D *localtex = tex;
localtex->AddRef();
tex = NULL;
// device has a new reference
CHECK_REFCOUNT(localdev, 6);
CHECK_REFCOUNT(localtex, 1);
ID3D11ShaderResourceViewPtr srv = D3D11ViewCreator(localdev, ViewType::SRV, localtex);
ID3D11ShaderResourceView *localsrv = srv;
localsrv->AddRef();
srv = NULL;
// the device has a new ref from the texture, AND from the SRV
CHECK_REFCOUNT(localdev, 7);
// the texture doesn't get a ref from the view
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 1);
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
// release the texture. It is kept alive by the SRV, but the device refcount goes down too
localtex->Release();
CHECK_REFCOUNT(localdev, 6);
CHECK_REFCOUNT(localtex, 0);
CHECK_REFCOUNT(localsrv, 1);
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
{
ID3D11Texture2D *texretrieve = NULL;
ID3D11Resource *resretrieve = NULL;
localsrv->GetResource(&resretrieve);
resretrieve->QueryInterface(__uuidof(ID3D11Texture2D), (void **)&texretrieve);
ID3D11Texture2D *texcast = (ID3D11Texture2D *)resretrieve;
if(texretrieve != localtex)
{
failed = true;
TEST_ERROR("Expected texture to come back identically");
}
if(texcast != localtex)
{
failed = true;
TEST_ERROR("Expected texture to come back identically");
}
texretrieve->Release();
resretrieve->Release();
}
localtex->AddRef();
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 1);
localsrv->AddRef();
localsrv->AddRef();
localsrv->AddRef();
// external SRV references only apply to the SRV, not the texture or device. Same as any other
// ID3D11DeviceChild
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 4);
localsrv->Release();
localsrv->Release();
localsrv->Release();
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 1);
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
////////////////////////////////////////////////////////////
// check refcounting on a deferred context
ID3D11DeviceContext *localdefctx;
localdev->CreateDeferredContext(0, &localdefctx);
// device gets another reference
CHECK_REFCOUNT(localdev, 8);
CHECK_REFCOUNT(localdefctx, 1);
localdefctx->ClearState();
// bind the VB. Doesn't change any public refcounts
localdefctx->IASetVertexBuffers(0, 1, &localvb, dummy, dummy);
CHECK_REFCOUNT(localdev, 8);
CHECK_REFCOUNT(localdefctx, 1);
CHECK_REFCOUNT(localvb, 1);
// VB is now held alive by the defctx
localvb->Release();
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(localdefctx, 1);
CHECK_REFCOUNT(localvb, 0);
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
{
ID3D11Buffer *vbretrieve = NULL;
localdefctx->IAGetVertexBuffers(0, 1, &vbretrieve, NULL, NULL);
if(vbretrieve != localvb)
{
failed = true;
TEST_ERROR("Expected buffer to come back identically");
}
vbretrieve->Release();
}
localvb->AddRef();
CHECK_REFCOUNT(localdev, 8);
CHECK_REFCOUNT(localdefctx, 1);
CHECK_REFCOUNT(localvb, 1);
localdefctx->Draw(0, 0);
ID3D11CommandList *locallist = NULL;
localdefctx->FinishCommandList(FALSE, &locallist);
infoQueue->ClearStoredMessages();
// extra refcount for the list, but otherwise unchanged
CHECK_REFCOUNT(localdev, 9);
CHECK_REFCOUNT(localdefctx, 1);
CHECK_REFCOUNT(locallist, 1);
CHECK_REFCOUNT(localvb, 1);
ret = localdefctx->Release();
// this should release it
if(ret != 0)
{
failed = true;
TEST_ERROR("localdefctx still has outstanding references");
}
if(!HasMessage(infoQueue, "INFO: Destroy ID3D11Context"))
{
failed = true;
TEST_ERROR("Expected localdefctx to be really destroyed");
}
CHECK_REFCOUNT(localdev, 8);
CHECK_REFCOUNT(locallist, 1);
CHECK_REFCOUNT(localvb, 1);
// the VB is held alive by the list now, though we can't retrieve it anymore
// we skip this test because although the runtime is smart enough to keep refs
// on the necessary objects, we aren't and we hope no-one actually takes advantage of this.
if(0)
{
localvb->Release();
CHECK_REFCOUNT(localvb, 0);
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
localvb->AddRef();
CHECK_REFCOUNT(localvb, 1);
if(HasMessage(infoQueue, "INFO: Destroy"))
{
failed = true;
TEST_ERROR("localvb should not have been destroyed");
}
}
CHECK_REFCOUNT(localdev, 8);
CHECK_REFCOUNT(locallist, 1);
CHECK_REFCOUNT(localvb, 1);
ret = locallist->Release();
localctx->Flush();
// this should release it
if(ret != 0)
{
failed = true;
TEST_ERROR("locallist still has outstanding references");
}
if(!HasMessage(infoQueue, "INFO: Destroy ID3D11CommandList"))
{
failed = true;
TEST_ERROR("Expected locallist to be really destroyed");
}
CHECK_REFCOUNT(localdev, 7);
////////////////////////////////////////////////////////////
// check that resources which are bound but don't have an external ref stay alive
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
infoQueue->ClearStoredMessages();
// another new device refcount
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(localctx, 1);
CHECK_REFCOUNT(localvb, 1);
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 1);
// binding doesn't change public refcounts
localctx->ClearState();
localctx->IASetVertexBuffers(0, 1, &localvb, dummy, dummy);
localctx->PSSetShaderResources(0, 1, &localsrv);
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(localctx, 1);
CHECK_REFCOUNT(localvb, 1);
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 1);
// but it means we can release things and they stay alive
localvb->Release();
localtex->Release();
localsrv->Release();
localctx->Flush();
CHECK_REFCOUNT(localvb, 0);
CHECK_REFCOUNT(localtex, 0);
CHECK_REFCOUNT(localsrv, 0);
if(HasMessage(infoQueue, "INFO: Destroy"))
{
failed = true;
TEST_ERROR("Expected nothing to be destroyed");
}
localvb->AddRef();
localtex->AddRef();
localsrv->AddRef();
CHECK_REFCOUNT(localdev, 7);
CHECK_REFCOUNT(localctx, 1);
CHECK_REFCOUNT(localvb, 1);
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 1);
localctx->ClearState();
localctx->Flush();
////////////////////////////////////////////////////////////
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
ret = localvb->Release();
localctx->Flush();
// this should release it
if(ret != 0)
{
failed = true;
TEST_ERROR("localvb still has outstanding references");
}
if(!HasMessage(infoQueue, "INFO: Destroy ID3D11Buffer"))
{
failed = true;
TEST_ERROR("Expected localvb to be really destroyed");
}
CHECK_REFCOUNT(localdev, 6);
ret = localsrv->Release();
localctx->Flush();
// this should release it
if(ret != 0)
{
failed = true;
TEST_ERROR("localsrv still has outstanding references");
}
if(!HasMessage(infoQueue, "INFO: Destroy ID3D11ShaderResourceView"))
{
failed = true;
TEST_ERROR("Expected localsrv to be really destroyed");
}
CHECK_REFCOUNT(localdev, 5);
ret = localtex->Release();
localctx->Flush();
// this should release it
if(ret != 0)
{
failed = true;
TEST_ERROR("localtex still has outstanding references");
}
if(!HasMessage(infoQueue, "INFO: Destroy ID3D11Texture2D"))
{
failed = true;
TEST_ERROR("Expected localtex to be really destroyed");
}
// the device should have 4 references - localdev, localctx, dbg and infoQueue
CHECK_REFCOUNT(localdev, 4);
dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
// ID3DUserDefinedAnnotation shares the context's refcount
{
CHECK_REFCOUNT(localctx, 1);
ID3DUserDefinedAnnotationPtr annottest = localctx;
if(annottest)
{
CHECK_REFCOUNT(localctx, 2);
ID3DUserDefinedAnnotation *localannot = annottest;
CHECK_REFCOUNT(localctx, 2);
CHECK_REFCOUNT(localannot, 2);
}
}
CHECK_REFCOUNT(localctx, 1);
CHECK_REFCOUNT(localdev, 4);
localctx->Release();
localctx = NULL;
dbg->Release();
dbg = NULL;
infoQueue->Release();
infoQueue = NULL;
// the device should only have this reference - localdev
CHECK_REFCOUNT(localdev, 1);
bool before = IsICDLoaded();
ret = localdev->Release();
if(ret != 0)
{
failed = true;
TEST_ERROR("localdev still has outstanding references");
}
bool after = IsICDLoaded();
if(!before)
{
TEST_WARN("Couldn't detect ICD at all - unclear if device really was destroyed");
}
else if(before && after)
{
failed = true;
TEST_ERROR("Device leaked - ICD dll stayed present");
}
///////////////////////////////////////////////////////////////////////////
// test a device staying alive based on an unbound child resource
reftest.CreateDevice({}, NULL, features, D3D11_CREATE_DEVICE_DEBUG);
localdev = reftest.dev;
localdev->AddRef();
reftest.dev = NULL;
reftest.ctx = NULL;
CHECK_REFCOUNT(localdev, 1);
buf = D3D11BufferCreator(localdev).Vertex().Data(DefaultTri);
localvb = buf;
localvb->AddRef();
buf = NULL;
CHECK_REFCOUNT(localdev, 2);
CHECK_REFCOUNT(localvb, 1);
localdev->Release();
CHECK_REFCOUNT(localdev, 1);
CHECK_REFCOUNT(localvb, 1);
// release the device with the VB
before = IsICDLoaded();
ret = localvb->Release();
if(ret != 0)
{
failed = true;
TEST_ERROR("localvb still has outstanding references");
}
after = IsICDLoaded();
if(!before)
{
TEST_WARN("Couldn't detect ICD at all - unclear if device really was destroyed");
}
else if(before && after)
{
failed = true;
TEST_ERROR("Device leaked - ICD dll stayed present");
}
///////////////////////////////////////////////////////////////////////////
// test a device staying alive based on a *bound* child resource on the immediate context
reftest.CreateDevice({}, NULL, features, D3D11_CREATE_DEVICE_DEBUG);
localdev = reftest.dev;
localdev->AddRef();
reftest.dev = NULL;
reftest.ctx = NULL;
CHECK_REFCOUNT(localdev, 1);
buf = D3D11BufferCreator(localdev).Vertex().Data(DefaultTri);
localvb = buf;
localvb->AddRef();
buf = NULL;
CHECK_REFCOUNT(localdev, 2);
CHECK_REFCOUNT(localvb, 1);
localdev->Release();
CHECK_REFCOUNT(localdev, 1);
CHECK_REFCOUNT(localvb, 1);
localdev->GetImmediateContext(&localctx);
localctx->IASetVertexBuffers(0, 1, &localvb, dummy, dummy);
localctx->Flush();
localctx->Release();
localctx = NULL;
before = IsICDLoaded();
ret = localvb->Release();
if(ret != 0)
{
failed = true;
TEST_ERROR("localvb still has outstanding references");
}
after = IsICDLoaded();
if(!before)
{
TEST_WARN("Couldn't detect ICD at all - unclear if device really was destroyed");
}
else if(before && after)
{
failed = true;
TEST_ERROR("Device leaked - ICD dll stayed present");
}
///////////////////////////////////////////////////////////////////////////
// test a resource or view being destroyed when unbound
reftest.CreateDevice({}, NULL, features, D3D11_CREATE_DEVICE_DEBUG);
localdev = reftest.dev;
localdev->AddRef();
reftest.dev = NULL;
reftest.ctx = NULL;
CHECK_REFCOUNT(localdev, 1);
buf = D3D11BufferCreator(localdev).Vertex().Data(DefaultTri);
localvb = buf;
localvb->AddRef();
buf = NULL;
tex = D3D11TextureCreator(localdev, DXGI_FORMAT_BC1_UNORM, 128, 128, 1).SRV();
localtex = tex;
localtex->AddRef();
tex = NULL;
srv = D3D11ViewCreator(localdev, ViewType::SRV, localtex);
localsrv = srv;
localsrv->AddRef();
srv = NULL;
CHECK_REFCOUNT(localdev, 4);
CHECK_REFCOUNT(localvb, 1);
CHECK_REFCOUNT(localtex, 1);
CHECK_REFCOUNT(localsrv, 1);
localdev->GetImmediateContext(&localctx);
// try first with renderdoc's GUID to get the unwrapped queue for testing against
localdev->QueryInterface(unwrappedID3D11InfoQueue__uuid, (void **)&infoQueue);
if(infoQueue == NULL)
localdev->QueryInterface(__uuidof(ID3D11InfoQueue), (void **)&infoQueue);
infoQueue->ClearStorageFilter();
infoQueue->ClearRetrievalFilter();
infoQueue->ClearStoredMessages();
CHECK_REFCOUNT(localdev, 6);
CHECK_REFCOUNT(localvb, 1);
localctx->IASetVertexBuffers(0, 1, &localvb, dummy, dummy);
localctx->PSSetShaderResources(0, 1, &localsrv);
localctx->Flush();
localvb->Release();
localtex->Release();
localsrv->Release();
localctx->Flush();
CHECK_REFCOUNT(localdev, 3);
CHECK_REFCOUNT(localvb, 0);
CHECK_REFCOUNT(localtex, 0);
CHECK_REFCOUNT(localsrv, 0);
if(HasMessage(infoQueue, "INFO: Destroy"))
{
failed = true;
TEST_ERROR("Expected nothing to be destroyed");
}
localctx->ClearState();
localctx->Flush();
CHECK_REFCOUNT(localdev, 3);
if(!HasMessages(infoQueue, {"INFO: Destroy ID3D11Buffer", "INFO: Destroy ID3D11Texture2D",
"INFO: Destroy ID3D11ShaderResourceView"}))
{
failed = true;
TEST_ERROR("Expected buffer, texture and SRV to be destroyed on unbind");
}
localctx->Release();
localctx = NULL;
infoQueue->Release();
infoQueue = NULL;
CHECK_REFCOUNT(localdev, 1);
before = IsICDLoaded();
ret = localdev->Release();
if(ret != 0)
{
failed = true;
TEST_ERROR("localdev still has outstanding references");
}
after = IsICDLoaded();
if(!before)
{
TEST_WARN("Couldn't detect ICD at all - unclear if device really was destroyed");
}
else if(before && after)
{
failed = true;
TEST_ERROR("Device leaked - ICD dll stayed present");
}
///////////////////////////////////////////////////////////////////////////
// test that resources which temporarily bounce off 0 refcounts in a naive bind/unbind don't
// get destroyed.
reftest.CreateDevice({}, NULL, features, D3D11_CREATE_DEVICE_DEBUG);
localdev = reftest.dev;
localdev->AddRef();
reftest.dev = NULL;
reftest.ctx = NULL;
CHECK_REFCOUNT(localdev, 1);
buf = D3D11BufferCreator(localdev).Vertex().Data(DefaultTri);
localvb = buf;
localvb->AddRef();
buf = NULL;
buf = D3D11BufferCreator(localdev).Vertex().Data(DefaultTri);
ID3D11Buffer *localvb2 = buf;
localvb2->AddRef();
buf = NULL;
buf = D3D11BufferCreator(localdev).Vertex().Data(DefaultTri);
ID3D11Buffer *localvb3 = buf;
localvb3->AddRef();
buf = NULL;
CHECK_REFCOUNT(localdev, 4);
CHECK_REFCOUNT(localvb, 1);
CHECK_REFCOUNT(localvb2, 1);
CHECK_REFCOUNT(localvb3, 1);
localdev->GetImmediateContext(&localctx);
// try first with renderdoc's GUID to get the unwrapped queue for testing against
localdev->QueryInterface(unwrappedID3D11InfoQueue__uuid, (void **)&infoQueue);
if(infoQueue == NULL)
localdev->QueryInterface(__uuidof(ID3D11InfoQueue), (void **)&infoQueue);
infoQueue->ClearStorageFilter();
infoQueue->ClearRetrievalFilter();
infoQueue->ClearStoredMessages();
CHECK_REFCOUNT(localdev, 6);
CHECK_REFCOUNT(localvb, 1);
CHECK_REFCOUNT(localvb2, 1);
CHECK_REFCOUNT(localvb3, 1);
ID3D11Buffer *firstBuffers[] = {localvb, localvb2, localvb3};
localctx->IASetVertexBuffers(0, 3, firstBuffers, dummy, dummy);
localctx->Flush();
localvb->Release();
localvb2->Release();
localvb3->Release();
localctx->Flush();
CHECK_REFCOUNT(localdev, 3);
CHECK_REFCOUNT(localvb, 0);
CHECK_REFCOUNT(localvb2, 0);
CHECK_REFCOUNT(localvb3, 0);
if(HasMessage(infoQueue, "INFO: Destroy"))
{
failed = true;
TEST_ERROR("Expected nothing to be destroyed");
}
// in a naive approach, when replacing localvb with localvb3 in slot 0, localvb is truly not
// referenced anywhere at all. This test ensures that we don't immediately destroy localvb
// when it's unbound from slot 0 because it's soon to be bound to slot 1. Note the same then
// happens with localvb2 which is temporarily reference-less when it's unbound from slot 1
ID3D11Buffer *secondBuffers[] = {localvb3, localvb, localvb2};
localctx->IASetVertexBuffers(0, 3, secondBuffers, dummy, dummy);
localctx->Flush();
CHECK_REFCOUNT(localdev, 3);
CHECK_REFCOUNT(localvb, 0);
CHECK_REFCOUNT(localvb2, 0);
CHECK_REFCOUNT(localvb3, 0);
if(HasMessage(infoQueue, "INFO: Destroy"))
{
failed = true;
TEST_ERROR("Expected nothing to be destroyed");
}
// clearing the state should still unbind and destroy the buffers
localctx->ClearState();
localctx->Flush();
CHECK_REFCOUNT(localdev, 3);
if(!HasMessage(infoQueue, "INFO: Destroy ID3D11Buffer"))
{
failed = true;
TEST_ERROR("Expected buffer, texture and SRV to be destroyed on unbind");
}
localctx->Release();
localctx = NULL;
infoQueue->Release();
infoQueue = NULL;
CHECK_REFCOUNT(localdev, 1);
before = IsICDLoaded();
ret = localdev->Release();
if(ret != 0)
{
failed = true;
TEST_ERROR("localdev still has outstanding references");
}
after = IsICDLoaded();
if(!before)
{
TEST_WARN("Couldn't detect ICD at all - unclear if device really was destroyed");
}
else if(before && after)
{
failed = true;
TEST_ERROR("Device leaked - ICD dll stayed present");
}
///////////////////////////////////////////////////////////////////////////
if(failed)
{
TEST_ERROR("Encountered refcounting errors, aborting test");
return 5;
}
}
// initialise, create window, create device, etc
if(!Init())
return 3;
ID3D11InfoQueuePtr infoQueue = dev;
if(infoQueue)
{
infoQueue->ClearStorageFilter();
infoQueue->ClearRetrievalFilter();
}
// run a normal test that we can capture from, so the checker can see that we got this far
// without failing
ID3DBlobPtr vsblob = Compile(D3DDefaultVertex, "main", "vs_4_0");
ID3DBlobPtr psblob = Compile(D3DDefaultPixel, "main", "ps_4_0");
CreateDefaultInputLayout(vsblob);
ID3D11VertexShaderPtr vs = CreateVS(vsblob);
ID3D11PixelShaderPtr ps = CreatePS(psblob);
ID3D11BufferPtr vb = MakeBuffer().Vertex().Data(DefaultTri);
// destroy backbuffer RTV
bbRTV = NULL;
ctx->Flush();
// save the backbuffer texture
ID3D11Texture2D *localbbtex = bbTex;
// release the backbuffer texture
bbTex = NULL;
ctx->Flush();
if(GetRefcount(localbbtex) != 0)
TEST_FATAL("backbuffer texture isn't 0 refcount!");
// get it back again
CHECK_HR(swap->GetBuffer(0, __uuidof(ID3D11Texture2D), (void **)&bbTex));
if(bbTex != localbbtex)
TEST_FATAL("Expected backbuffer texture to be identical after obtaining it again");
// recreate the RTV
CHECK_HR(dev->CreateRenderTargetView(bbTex, NULL, &bbRTV));
while(Running())
{
ClearRenderTargetView(bbRTV, {0.2f, 0.2f, 0.2f, 1.0f});
IASetVertexBuffer(vb, sizeof(DefaultA2V), 0);
ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ctx->IASetInputLayout(defaultLayout);
ctx->VSSetShader(vs, NULL, 0);
ctx->PSSetShader(ps, NULL, 0);
RSSetViewport({0.0f, 0.0f, (float)screenWidth, (float)screenHeight, 0.0f, 1.0f});
ctx->OMSetRenderTargets(1, &bbRTV.GetInterfacePtr(), NULL);
setMarker("Color Draw");
ctx->Draw(3, 0);
Present();
}
return 0;
}
};
REGISTER_TEST();