D3D11的引用计数从何处增加?

3

我已经在使用d3d11有一段时间了,最近发现了directx调试器,发现我的程序由于许多com对象没有正确释放而到处泄漏内存。经过一番搜查和几个小时的代码分析,我已经开发出了一些方法来定位这些意外的引用计数增加的位置。

首先,所有对象都被封装在带有自定义删除器的std::shared_ptrs中,这些删除器调用它们各自的释放函数。我这样做是为了永远不必调用addref,并且第一次调用release(在删除器中)将只在对象超出作用域时调用。它看起来像这样:

// in D3D11Renderer.h
...
// declaration
std::shared_ptr<ID3D11Device *> m_Device;
...

// after call to ID3D11CreateDeviceAndSwapChain
m_Device.reset(device, [](ID3D11Device * ptr){ptr->Release();})

问题在于某些api调用中的随机函数会随机增加引用计数,预期我稍后需要处理它。

在诊断中我发现有个函数看起来像这样:

template <typename T>
int getRefCount(T object) 
{
    object->AddRef();
    return object->Release();
}

通过增加和减少计数来获取该对象当前的引用计数。使用这个方法,我发现,在调用自定义删除器之前,有10个未释放的引用指向了我创建的1个ID3D11Device。很奇怪,我开始倒退调用这个函数,一直回溯到最初创建它的地方。有趣的是,就在我第一次创建对象之后(甚至在 shared_ptr 接管所有权之前),未释放的引用数量已经是 3!这发生在这里之后立即发生。

result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, 
                           D3D11_SDK_VERSION, &swapChainDesc, &swapChain, &device, NULL, &deviceContext);
    if(FAILED(result))
    {
        return false;
    }

这是我第一次调用创建设备的函数,当我立即检查引用数量时,它显示有3个!显然,我对处理这些com对象的方式存在误解。是否有手动删除它们的方法,而不是使用后台引用计数机制?


如果您在其他程序还在引用它们时手动删除了它们,您的程序将会崩溃。由于您创建了保存对原始结构的引用的新结构,所以引用计数更高。这很好,因为您可以释放您的引用并不必担心它,当新结构被销毁时,旧结构将降低它们的引用计数。 - David Schwartz
2个回答

7
每次创建缓冲区、着色器或任何依赖于设备的对象时,该对象通常都会包含对设备的引用,因此会增加其引用计数以确保在仍在使用设备时不会被删除。
从整体上看,您的方法似乎会起作用,因为您的代码中基本上只保留了对设备的单个引用来防止其被删除,并且当所有内部引用消失时释放它。但是,d3d仍然会进行自己的引用计数,因此只有在释放与每个其他相关对象的每个引用时,引用计数才会降至零。即使只是创建交换链和设备,也会生成需要维护对设备的引用的后备缓冲区等内容。
我曾经尝试过同样的想法……最后发现直接这样做要容易得多。
#include <atlbase>

然后使用。
CComPtr<ID311Device> m_Device

因为这个类的设计完全是为了这个目的,而且比std::shader_ptr更轻量级,因为对象中已经有一个引用计数器,所以不需要保留单独的引用计数器。


我非常希望能够使用CComPtr对象,但显然在Visual Studio 2010 Express版中不可用。这就是为什么我必须绕道使用std::shared_ptr的原因。所以基本上你的意思是,如果我可以确保销毁所有其他d3d11对象,那么自定义删除器中的释放应该可以正常工作?我想我会去寻找没有被销毁的其他对象。感谢您的帮助!另外,“std::shader_ptr”是打错字了吗,还是您本来就是要写成这样? - FatalCatharsis
那是一种类型错误,抱歉。哦,抱歉我不知道这在 Express 版本中不可用。 - jcoder
笔误...哎呀,这很典型 - 也许写一个最小的替代CComPtr并不太难,它只是一个简单的包装器,在适当的时候调用Release和AddRef。 - jcoder

2
使用shared_ptr不适用于Direct3D(COM)对象,即使使用自定义删除器也不行。
首先,COM对象使用侵入式引用计数,这意味着引用计数存储在对象本身中。另一方面,shared_ptr使用非侵入式引用计数,这意味着引用计数存储在智能指针对象中。因此,为COM对象使用shared_ptr意味着您有两个独立的引用计数:COM对象的引用计数和shared_ptr的引用计数。
其次,使用自定义删除器解决了正确释放对象的问题,但并未解决正确获取对象引用的问题。将COM对象分配给shared_ptr会增加shared_ptr的引用计数,但不会增加对象的引用计数。
这就解释了为什么您会泄漏对象:D3D方法会增加对象的引用计数,但您正在使用shared_ptr,它仅在对象的整个生命周期内减少对象的引用计数一次(当所有指向该对象的shared_ptr都被销毁时)。
因此,您需要使用COM智能指针,例如ATL的CComPtr

虽然我同意在 COM 对象上使用 shared_ptr 通常是不好的想法,但嵌套引用计数并不会遭受您描述的对象泄漏问题。在这种情况下,shared_ptr 持有 COM 对象的一个引用,自定义删除器在共享指针对象的所有引用都消失后释放它,这是正确的行为。因此,即使它不是好习惯,它仍然是正确的。额外引用的原因是(如@J99已经指出的那样)不同COM对象之间的依赖关系。 - Stacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接