G
GRM1X2
Guest
I have a simple C++ program reduced to 272 lines of code that initializes Direct2D, then performs a loop of 1000 operation where it simply creates an ID2D1GradientStopCollection followed by the creation of the using ID2D1LinearGradientBrush, then releases them immediately (Release count falls to zero on the calls to Release thus indicating that they should normally be unallocated).
However, after each execution of this loop, I see the memory reserved by the process increasing, and it is released only at the end, once the direct2D factories and devices are released, instead of being released after each call to linearGradientBrush->Release() and gradientStopCollection->Release() as I would expect.
The strange thing is that when I create/delete a SolidColorBrush instead of a LinearGradientBrush (still creating/deleting the gradientStopCollection) then there is no more memory leak observed.
Is this something I did wrong, causing for example the gradientStopCollection to still be allocated because still used by the brush? (But I do not see in that case why the reference count after the call to Release on the two objects falls to zero; and also, if I add an AddRef or remove a Release in order to trigger the wrong count of AddRef/Release on the objects (and get a non zero reference count situation at the end of the program) then the Direct2D debug layers indicates a fault inside d2d1debug3.dll calling kernelbase.dll on the call stack, thus showing that the AddRef/Release count is correct; yet I still observe this memory leak with the TaskManager).
This has been tested on two different machines, and the leak appears on x86/x64, debug/release plateforms
Any help on how to resolve this would be appreciated
The code:
#include <windows.h>
#include <commctrl.h>
// DirectX header files.
#include <d2d1_1.h>
#include <d3d11.h>
#include <d3d11_1.h>
bool performAllocUnallocTestLoop(ID2D1DeviceContext* in_deviceContext) {
ID2D1GradientStopCollection* gradientStopCollection;
int i;
ID2D1LinearGradientBrush* linearGradientBrush;
int cnt;
ULONG nb;
D2D1_GRADIENT_STOP gradientStops[2];
HRESULT hr;
ID2D1SolidColorBrush* solidColorBrush;
gradientStops[0].color = D2D1::ColorF(D2D1::ColorF::Yellow, 1);
gradientStops[0].position = 0.0f;
gradientStops[1].color = D2D1::ColorF(D2D1::ColorF::ForestGreen, 1);
gradientStops[1].position = 1.0f;
cnt = 1000;
for (i = 0; i < cnt; i++) {
hr = in_deviceContext->CreateGradientStopCollection(gradientStops, 2, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, &gradientStopCollection);
if (!SUCCEEDED(hr)) {
return false;
}
hr = in_deviceContext->CreateLinearGradientBrush(D2D1::LinearGradientBrushProperties(D2D1:oint2F(0, 0), D2D1:oint2F(150, 150)), gradientStopCollection, &linearGradientBrush);
if (!SUCCEEDED(hr)) {
gradientStopCollection->Release();
return false;
}
/*
// This code is commented and creates a solidColorBrush instead of a linearGradientBrush.
// Uncomment this code and comment the creation of the linearGradientBrush above instead and the leak disappears.
hr = in_deviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Yellow, 1), &solidColorBrush);
if (!SUCCEEDED(hr)) {
gradientStopCollection->Release();
return false;
}
*/
nb = linearGradientBrush->Release(); // Comment this line and the linearGradientBrush creation, then uncomment the solidColorBrush creation/release instead
// and the memory leak disappears.
// nb = solidColorBrush->Release();
nb = gradientStopCollection->Release();
}
return true;
}
int main_test_function(ID2D1DeviceContext* in_deviceContext) {
int i;
MessageBoxW(NULL, (WCHAR*)L"Before", (WCHAR*)L"Before", MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND);
for (i = 0; i < 10; i++) {
if (!performAllocUnallocTestLoop(in_deviceContext)) {
MessageBoxW(NULL, (WCHAR*)L"Some creation failed", (WCHAR*)L"Some creation failed", MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND);
return 0;
}
if (MessageBoxW(NULL, (WCHAR*)L"After performed another 1000 create/delete of gradientStopCollection+linearGradientBrush (leak observed in TaskManager, resource NOT released)", (WCHAR*)L"After", MB_OKCANCEL | MB_ICONINFORMATION | MB_SETFOREGROUND) == IDCANCEL) {
break;
}
}
return 0;
}
// _____________________________________________________________
// _ WinMain part: init/dest D3D and D2D factories and devices.
// _____________________________________________________________
struct TD2DFactoriesAndDevices {
ID2D1Factory1* d2dFactory;
ID3D11Device1* d3dDevice;
IDXGIDevice* dxgiDevice;
ID2D1Device* d2dDevice;
ID2D1DeviceContext* d2dDeviceContext;
};
void destFactoriesAndDevices(TD2DFactoriesAndDevices* in_struct) {
if (in_struct == NULL) {
return;
}
if (in_struct->d3dDevice != NULL) {
in_struct->d3dDevice->Release();
in_struct->d3dDevice = NULL;
}
if (in_struct->dxgiDevice != NULL) {
in_struct->dxgiDevice->Release();
in_struct->dxgiDevice = NULL;
}
if (in_struct->d2dDevice != NULL) {
in_struct->d2dDevice->Release();
in_struct->d2dDevice = NULL;
}
if (in_struct->d2dDeviceContext != NULL) {
in_struct->d2dDeviceContext->Release();
in_struct->d2dDeviceContext = NULL;
}
if (in_struct->d2dFactory != NULL) {
in_struct->d2dFactory->Release();
in_struct->d2dFactory = NULL;
}
CoUninitialize();
}
bool initFactoriesAndDevices(TD2DFactoriesAndDevices* in_struct,
const wchar_t*& out_error) {
HRESULT hr;
D2D1_FACTORY_OPTIONS options;
ID3D11Device* d3dDevice;
UINT createDeviceFlags;
D3D_DRIVER_TYPE driverType;
if (in_struct == NULL) {
out_error = L"Can not use NULL pointer";
return false;
}
in_struct->d3dDevice = NULL;
in_struct->dxgiDevice = NULL;
in_struct->d2dDevice = NULL;
in_struct->d2dDeviceContext = NULL;
in_struct->d2dFactory = NULL;
// init DCOM
if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) {
out_error = L"Could not init DCOM";
return false;
}
// Create the Direct2D factory.
ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
#if defined(_DEBUG)
options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
// options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
#endif
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
options, &in_struct->d2dFactory);
if (!SUCCEEDED(hr)) {
in_struct->d2dFactory = NULL;
out_error = L"D2D1CreateFactory failed";
CoUninitialize();
return false;
}
__create_devices:
// Create the D3D device on default adapter.
createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
createDeviceFlags |= D3D11_CREATE_DEVICE_SINGLETHREADED;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// Creating the d3dDevice
in_struct->d3dDevice = NULL;
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
0,
createDeviceFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&d3dDevice,
nullptr,
nullptr
);
if (FAILED(hr)) {
in_struct->d3dDevice = NULL;
goto __create_failed;
}
hr = d3dDevice->QueryInterface(__uuidof(ID3D11Device1),
(void**)(&in_struct->d3dDevice));
if (FAILED(hr)) {
in_struct->d3dDevice = NULL;
goto __create_failed;
}
d3dDevice->Release();
// Get a DXGI device interface from the D3D device.
in_struct->dxgiDevice = NULL;
hr = in_struct->d3dDevice->QueryInterface(__uuidof(IDXGIDevice),
(void**)(&in_struct->dxgiDevice));
if (FAILED(hr)) {
in_struct->dxgiDevice = NULL;
goto __create_failed;
}
// Create a D2D device from the DXGI device.
hr = in_struct->d2dFactory->CreateDevice(in_struct->dxgiDevice,
&in_struct->d2dDevice);
if (FAILED(hr)) {
in_struct->d2dDevice = NULL;
goto __create_failed;
}
// Create a device context from the D2D device, to create the
// resources.
hr = in_struct->d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &in_struct->d2dDeviceContext);
if (FAILED(hr)) {
in_struct->d2dDeviceContext = NULL;
goto __create_failed;
}
// Setting isCreated
__after_create_devices:
return true;
__create_failed:
destFactoriesAndDevices(in_struct);
out_error = L"Could not create the devices";
return false;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
int result;
const wchar_t* _error;
TD2DFactoriesAndDevices _struct;
if (!initFactoriesAndDevices(&_struct, _error)) {
MessageBoxW(NULL, (WCHAR*)L"An error occured", _error,
MB_OK | MB_ICONINFORMATION);
return 0;
}
result = main_test_function(_struct.d2dDeviceContext);
destFactoriesAndDevices(&_struct);
return result;
}
Continue reading...
However, after each execution of this loop, I see the memory reserved by the process increasing, and it is released only at the end, once the direct2D factories and devices are released, instead of being released after each call to linearGradientBrush->Release() and gradientStopCollection->Release() as I would expect.
The strange thing is that when I create/delete a SolidColorBrush instead of a LinearGradientBrush (still creating/deleting the gradientStopCollection) then there is no more memory leak observed.
Is this something I did wrong, causing for example the gradientStopCollection to still be allocated because still used by the brush? (But I do not see in that case why the reference count after the call to Release on the two objects falls to zero; and also, if I add an AddRef or remove a Release in order to trigger the wrong count of AddRef/Release on the objects (and get a non zero reference count situation at the end of the program) then the Direct2D debug layers indicates a fault inside d2d1debug3.dll calling kernelbase.dll on the call stack, thus showing that the AddRef/Release count is correct; yet I still observe this memory leak with the TaskManager).
This has been tested on two different machines, and the leak appears on x86/x64, debug/release plateforms
Any help on how to resolve this would be appreciated
The code:
#include <windows.h>
#include <commctrl.h>
// DirectX header files.
#include <d2d1_1.h>
#include <d3d11.h>
#include <d3d11_1.h>
bool performAllocUnallocTestLoop(ID2D1DeviceContext* in_deviceContext) {
ID2D1GradientStopCollection* gradientStopCollection;
int i;
ID2D1LinearGradientBrush* linearGradientBrush;
int cnt;
ULONG nb;
D2D1_GRADIENT_STOP gradientStops[2];
HRESULT hr;
ID2D1SolidColorBrush* solidColorBrush;
gradientStops[0].color = D2D1::ColorF(D2D1::ColorF::Yellow, 1);
gradientStops[0].position = 0.0f;
gradientStops[1].color = D2D1::ColorF(D2D1::ColorF::ForestGreen, 1);
gradientStops[1].position = 1.0f;
cnt = 1000;
for (i = 0; i < cnt; i++) {
hr = in_deviceContext->CreateGradientStopCollection(gradientStops, 2, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, &gradientStopCollection);
if (!SUCCEEDED(hr)) {
return false;
}
hr = in_deviceContext->CreateLinearGradientBrush(D2D1::LinearGradientBrushProperties(D2D1:oint2F(0, 0), D2D1:oint2F(150, 150)), gradientStopCollection, &linearGradientBrush);
if (!SUCCEEDED(hr)) {
gradientStopCollection->Release();
return false;
}
/*
// This code is commented and creates a solidColorBrush instead of a linearGradientBrush.
// Uncomment this code and comment the creation of the linearGradientBrush above instead and the leak disappears.
hr = in_deviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Yellow, 1), &solidColorBrush);
if (!SUCCEEDED(hr)) {
gradientStopCollection->Release();
return false;
}
*/
nb = linearGradientBrush->Release(); // Comment this line and the linearGradientBrush creation, then uncomment the solidColorBrush creation/release instead
// and the memory leak disappears.
// nb = solidColorBrush->Release();
nb = gradientStopCollection->Release();
}
return true;
}
int main_test_function(ID2D1DeviceContext* in_deviceContext) {
int i;
MessageBoxW(NULL, (WCHAR*)L"Before", (WCHAR*)L"Before", MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND);
for (i = 0; i < 10; i++) {
if (!performAllocUnallocTestLoop(in_deviceContext)) {
MessageBoxW(NULL, (WCHAR*)L"Some creation failed", (WCHAR*)L"Some creation failed", MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND);
return 0;
}
if (MessageBoxW(NULL, (WCHAR*)L"After performed another 1000 create/delete of gradientStopCollection+linearGradientBrush (leak observed in TaskManager, resource NOT released)", (WCHAR*)L"After", MB_OKCANCEL | MB_ICONINFORMATION | MB_SETFOREGROUND) == IDCANCEL) {
break;
}
}
return 0;
}
// _____________________________________________________________
// _ WinMain part: init/dest D3D and D2D factories and devices.
// _____________________________________________________________
struct TD2DFactoriesAndDevices {
ID2D1Factory1* d2dFactory;
ID3D11Device1* d3dDevice;
IDXGIDevice* dxgiDevice;
ID2D1Device* d2dDevice;
ID2D1DeviceContext* d2dDeviceContext;
};
void destFactoriesAndDevices(TD2DFactoriesAndDevices* in_struct) {
if (in_struct == NULL) {
return;
}
if (in_struct->d3dDevice != NULL) {
in_struct->d3dDevice->Release();
in_struct->d3dDevice = NULL;
}
if (in_struct->dxgiDevice != NULL) {
in_struct->dxgiDevice->Release();
in_struct->dxgiDevice = NULL;
}
if (in_struct->d2dDevice != NULL) {
in_struct->d2dDevice->Release();
in_struct->d2dDevice = NULL;
}
if (in_struct->d2dDeviceContext != NULL) {
in_struct->d2dDeviceContext->Release();
in_struct->d2dDeviceContext = NULL;
}
if (in_struct->d2dFactory != NULL) {
in_struct->d2dFactory->Release();
in_struct->d2dFactory = NULL;
}
CoUninitialize();
}
bool initFactoriesAndDevices(TD2DFactoriesAndDevices* in_struct,
const wchar_t*& out_error) {
HRESULT hr;
D2D1_FACTORY_OPTIONS options;
ID3D11Device* d3dDevice;
UINT createDeviceFlags;
D3D_DRIVER_TYPE driverType;
if (in_struct == NULL) {
out_error = L"Can not use NULL pointer";
return false;
}
in_struct->d3dDevice = NULL;
in_struct->dxgiDevice = NULL;
in_struct->d2dDevice = NULL;
in_struct->d2dDeviceContext = NULL;
in_struct->d2dFactory = NULL;
// init DCOM
if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) {
out_error = L"Could not init DCOM";
return false;
}
// Create the Direct2D factory.
ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
#if defined(_DEBUG)
options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
// options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
#endif
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
options, &in_struct->d2dFactory);
if (!SUCCEEDED(hr)) {
in_struct->d2dFactory = NULL;
out_error = L"D2D1CreateFactory failed";
CoUninitialize();
return false;
}
__create_devices:
// Create the D3D device on default adapter.
createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
createDeviceFlags |= D3D11_CREATE_DEVICE_SINGLETHREADED;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// Creating the d3dDevice
in_struct->d3dDevice = NULL;
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
0,
createDeviceFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&d3dDevice,
nullptr,
nullptr
);
if (FAILED(hr)) {
in_struct->d3dDevice = NULL;
goto __create_failed;
}
hr = d3dDevice->QueryInterface(__uuidof(ID3D11Device1),
(void**)(&in_struct->d3dDevice));
if (FAILED(hr)) {
in_struct->d3dDevice = NULL;
goto __create_failed;
}
d3dDevice->Release();
// Get a DXGI device interface from the D3D device.
in_struct->dxgiDevice = NULL;
hr = in_struct->d3dDevice->QueryInterface(__uuidof(IDXGIDevice),
(void**)(&in_struct->dxgiDevice));
if (FAILED(hr)) {
in_struct->dxgiDevice = NULL;
goto __create_failed;
}
// Create a D2D device from the DXGI device.
hr = in_struct->d2dFactory->CreateDevice(in_struct->dxgiDevice,
&in_struct->d2dDevice);
if (FAILED(hr)) {
in_struct->d2dDevice = NULL;
goto __create_failed;
}
// Create a device context from the D2D device, to create the
// resources.
hr = in_struct->d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &in_struct->d2dDeviceContext);
if (FAILED(hr)) {
in_struct->d2dDeviceContext = NULL;
goto __create_failed;
}
// Setting isCreated
__after_create_devices:
return true;
__create_failed:
destFactoriesAndDevices(in_struct);
out_error = L"Could not create the devices";
return false;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
int result;
const wchar_t* _error;
TD2DFactoriesAndDevices _struct;
if (!initFactoriesAndDevices(&_struct, _error)) {
MessageBoxW(NULL, (WCHAR*)L"An error occured", _error,
MB_OK | MB_ICONINFORMATION);
return 0;
}
result = main_test_function(_struct.d2dDeviceContext);
destFactoriesAndDevices(&_struct);
return result;
}
Continue reading...