D3D11 увеличил количество ссылок из ниоткуда?

Я работаю с d3d11 уже довольно давно, и после обнаружения отладчика DirectX я недавно обнаружил, что моя программа повсюду пропускает память из-за всех com-объектов, которые не освобождаются должным образом. После небольшого поиска и нескольких часов изучения кода я разработал несколько методов, чтобы изолировать, где я получаю эти неожиданные увеличения счетчиков ссылок.

во-первых, все объекты обернуты в std::shared_ptrs с пользовательскими удалениями, которые вызывают соответствующие функции освобождения. Я сделал это, чтобы мне никогда не приходилось вызывать addref, а первый вызов для освобождения, тот, что в средстве удаления, вызывался только тогда, когда объект выходил за пределы области видимости. Это будет выглядеть примерно так:

// 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-объекты. Есть ли такой способ, которым я могу просто вручную удалить их, а не использовать закулисную ерунду подсчета ссылок?


person FatalCatharsis    schedule 30.11.2012    source источник
comment
Если вы вручную удалили их, в то время как что-то еще имело на них ссылку, ваша программа рухнет. Счетчики ссылок выше, потому что вы создали новые структуры, которые содержат ссылки на исходные структуры. Однако это здорово, вы можете освободить свою ссылку и не беспокоиться об этом, когда новые структуры будут уничтожены, тогда старые структуры сбросят свои счетчики ссылок.   -  person David Schwartz    schedule 30.11.2012


Ответы (2)


Каждый раз, когда вы создаете буфер, шейдер или что-то еще, что зависит от устройства, этот объект, скорее всего, будет содержать ссылку на устройство, поэтому счетчик ссылок будет увеличиваться, чтобы гарантировать, что он не будет удален, пока он все еще его использует.

Похоже, ваш подход может хорошо работать в целом, поскольку вы, по сути, сохраните одну единственную ссылку на устройство в своем коде, чтобы предотвратить его удаление, и когда все ваши внутренние ссылки исчезнут, отпустите его. Однако d3d по-прежнему будет выполнять собственный подсчет ссылок, поэтому счетчик ссылок упадет до нуля только тогда, когда вы отпустите каждую ссылку на любой другой связанный объект. Даже простое создание цепочки подкачки и устройства приведет к созданию обратных буферов и т. д., для которых, вероятно, потребуется поддерживать ссылку на устройство.

Некоторое время я пробовал эту же идею... И, в конце концов, оказалось, что гораздо проще просто

#include <atlbase>

Затем используйте

CComPtr<ID311Device> m_Device

поскольку это именно то, для чего предназначен этот класс, и он более легкий, чем std::shader_ptr, поскольку объекты уже имеют счетчик ссылок, поэтому нет необходимости хранить отдельный счетчик.

person jcoder    schedule 30.11.2012
comment
я отчаянно хочу использовать объект CComPtr, но, по-видимому, он недоступен в экспресс-выпуске Visual Studio 2010. Вот почему мне приходится обходиться с использованием std::shared_ptr. Итак, в основном, что вы говорите, если я могу гарантировать уничтожение всех других объектов d3d11, тогда выпуск в пользовательском удалении должен работать нормально? Думаю, тогда я пойду охотиться за другим объектом, который не разрушается. Спасибо за помощь! Кроме того, это std::shader_ptr опечатка, или вы хотели написать это? - person FatalCatharsis; 30.11.2012
comment
Это тип, извините. И о, извините, я не знал, что это не было доступно в экспресс-версии - person jcoder; 30.11.2012
comment
опечатка... к сожалению, типичная - в любом случае, вероятно, не будет слишком сложно написать минимальную замену CComPtr, это просто простая оболочка, которая вызывает Release и AddRef, когда это необходимо. - person jcoder; 30.11.2012

Использование shared_ptr некорректно для объектов Direct3D (COM), даже если вы используете пользовательские средства удаления.

Во-первых, COM-объекты используют интрузивный подсчет ссылок, что означает, что счетчик ссылок хранится в самом объекте. С другой стороны, shared_ptr использует ненавязчивый подсчет ссылок, что означает, что счетчик ссылок хранится в объекте интеллектуального указателя. Таким образом, использование shared_ptr для COM-объектов означает наличие двух отдельных независимых счетчиков ссылок: COM-объекта и shared_ptr.

Во-вторых, использование пользовательского удаления решает проблему правильного освобождения объекта, но не решает проблему правильного получения ссылки на объект. Присвоение COM-объекта shared_ptr увеличит счетчик ссылок shared_ptr, но не объекта.

Это объясняет, почему вы пропускаете объекты: методы D3D увеличивают счетчики ссылок на объекты, но вы используете shared_ptrs, которые уменьшают счетчик ссылок объекта только один раз за все время существования объекта (когда все shared_ptrs, указывающие на объект, уничтожаются).

Итак, вам нужно использовать интеллектуальный указатель COM, такой как CComPtr.

person user1610015    schedule 30.11.2012
comment
Хотя я согласен с тем, что использование shared_ptr для COM-объектов, как правило, является плохой идеей, вложенный подсчет ссылок не страдает от описанной вами проблемы утечки объектов. В этом случае shared_ptr содержит одну ссылку на COM-объект, и пользовательское средство удаления освобождает ее, когда все ссылки на объект shared_ptr исчезают, что является правильным поведением. Так что, хотя это и нехорошая привычка, она все же правильная. Причиной дополнительных ссылок (как уже указывал @J99) являются зависимости между различными COM-объектами. - person Stacker; 30.11.2012