Potential memory leak when releasing a Gradient Brush in direct2D? - c++

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).
Any help on how to resolve this would be appreciated
#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::Point2F(0, 0), D2D1::Point2F(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;
}
This has been tested on two different machines, and the leak appears on x86/x64, debug/release plateforms

This is expected behaviour, considering your code.
Internally, Direct3D11 does some resource tracking, and calling release will only mark the resource for deletion, resource will be effectively deleted if the following conditions are met:
DeviceContext is Flushed
Resource external counter is 0 (the one returned by Release)
Resource internal counter is 0, this is incremented when your resource is attached, detached from the pipeline (in your case Direct2D handle it)
Since in your loop you do not flush at all, the resources are created on the GPU, but only marked for deletion.
There are several ways to flush device:
When you call EndDraw (or present on the Swapchain), to display on the screen.
When you manually perform Flush on the Direct3D Device Context (please note that you should not call Flush manually if you actually perform rendering on the screen, since Present/EndDraw will take care of that)
When you map a resource for cpu readback (out of scope in your use case I guess)
When the internal command buffer is full (since in your case you do not perform any drawing calls, if never gets filled).
In Direct3d11 case, you should also eventually call ClearState (this resets the whole pipeline tho), but since Direct2D normally takes care of unassigning internal resources, this should not be needed either in your use case.

Related

How to use IDXGIOutputDuplication to capture multiple screens?

Is there a way to screen capture multi monitor setups with windows duplication api (IDXIOutputDuplication) rather than gdi? Windows duplication is now the preferred (faster and more efficient) way of screen capturing on Windows machines. This is a question on the Microsoft forum but the link to the code is broken and I can't find it anywhere on their website 1. Examples of multi monitor screen casting with GDI can be found but they use a separate API 2,3.
I have successfully captured one monitor in C++ using the links for Windows duplication api found here 4. The problem is that the api requires an adapter and an output. I could possibly create two threads and run the windows api on each for separate adapters. I was just wondering if there was a more elegant/better performance solution.
P.S. I have had trouble posting questions on StackOverflow in the past. Before closing this question, please let me know how I can change the content to better suit the rules:)
In response to the request for code, here is a snippet from the header file
ID3D11Device* D3DDevice = nullptr;
ID3D11DeviceContext* D3DDeviceContext = nullptr;
IDXGIOutputDuplication* DeskDupl = nullptr;
DXGI_OUTPUT_DESC OutputDesc;
from the cpp file (there are two parts, initializing and capuring):
From the initializing function:
// Create device
for (size_t i = 0; i < numDriverTypes; i++) {
hr = D3D11CreateDevice(nullptr, driverTypes[i], nullptr, 0, featureLevels, (UINT) numFeatureLevels,
D3D11_SDK_VERSION, &D3DDevice, &featureLevel, &D3DDeviceContext);
if (SUCCEEDED(hr))
break;
}
if (FAILED(hr))
return tsf::fmt("D3D11CreateDevice failed: %v", hr);
// Initialize the Desktop Duplication system
//m_OutputNumber = Output;
// Get DXGI device
IDXGIDevice* dxgiDevice = nullptr;
hr = D3DDevice->QueryInterface(__uuidof(IDXGIDevice), (void**) &dxgiDevice);
if (FAILED(hr))
return tsf::fmt("D3DDevice->QueryInterface failed: %v", hr);
// Get DXGI adapter
IDXGIAdapter* dxgiAdapter = nullptr;
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**) &dxgiAdapter);
dxgiDevice->Release();
dxgiDevice = nullptr;
if (FAILED(hr)) {
return tsf::fmt("dxgiDevice->GetParent failed: %v", hr);
//return ProcessFailure(m_Device, L"Failed to get parent DXGI Adapter", L"Error", hr, SystemTransitionsExpectedErrors);
}
// Get output
IDXGIOutput* dxgiOutput = nullptr;
hr = dxgiAdapter->EnumOutputs(OutputNumber, &dxgiOutput);
dxgiAdapter->Release();
dxgiAdapter = nullptr;
if (FAILED(hr)) {
return tsf::fmt("dxgiAdapter->EnumOutputs failed: %v", hr);
//return ProcessFailure(m_Device, L"Failed to get specified output in DUPLICATIONMANAGER", L"Error", hr, EnumOutputsExpectedErrors);
}
dxgiOutput->GetDesc(&OutputDesc);
// QI for Output 1
IDXGIOutput1* dxgiOutput1 = nullptr;
hr = dxgiOutput->QueryInterface(__uuidof(dxgiOutput1), (void**) &dxgiOutput1);
dxgiOutput->Release();
dxgiOutput = nullptr;
if (FAILED(hr))
return tsf::fmt("dxgiOutput->QueryInterface failed: %v", hr);
// Create desktop duplication
hr = dxgiOutput1->DuplicateOutput(D3DDevice, &DeskDupl);
dxgiOutput1->Release();
dxgiOutput1 = nullptr;
if (FAILED(hr)) {
if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) {
//MessageBoxW(nullptr, L"There is already the maximum number of applications using the Desktop Duplication API running, please close one of those applications and then try again.", L"Error", MB_OK);
return "Too many desktop recorders already active";
}
return tsf::fmt("DuplicateOutput failed: %v", hr);
//return ProcessFailure(m_Device, L"Failed to get duplicate output in DUPLICATIONMANAGER", L"Error", hr, CreateDuplicationExpectedErrors);
}
From the capturing function:
IDXGIResource* deskRes = nullptr;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
hr = DeskDupl->AcquireNextFrame(0, &frameInfo, &deskRes);
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
// nothing to see here
return false;
}
if (FAILED(hr)) {
// perhaps shutdown and reinitialize
auto msg = tsf::fmt("Acquire failed: %x\n", hr);
OutputDebugStringA(msg.c_str());
return false;
}
HaveFrameLock = true;
ID3D11Texture2D* gpuTex = nullptr;
hr = deskRes->QueryInterface(__uuidof(ID3D11Texture2D), (void**) &gpuTex);
deskRes->Release();
deskRes = nullptr;
if (FAILED(hr)) {
// not expected
return false;
}
bool ok = true;
D3D11_TEXTURE2D_DESC desc;
gpuTex->GetDesc(&desc);
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.MiscFlags = 0; // D3D11_RESOURCE_MISC_GDI_COMPATIBLE ?
ID3D11Texture2D* cpuTex = nullptr;
hr = D3DDevice->CreateTexture2D(&desc, nullptr, &cpuTex);
if (SUCCEEDED(hr)) {
D3DDeviceContext->CopyResource(cpuTex, gpuTex);
} else {
// not expected
ok = false;
}
//UINT subresource = D3D11CalcSubresource(0, 0, 0);
D3D11_MAPPED_SUBRESOURCE sr;
hr = D3DDeviceContext->Map(cpuTex, 0, D3D11_MAP_READ, 0, &sr);
DeskDupl is tied to a specific adapter and output.
You will need to obtain an IDXGIOutputDuplication interface for each IDXGIOutput you wish to have duplicated. Whether those outputs are on one adapter or multiple doesn't matter. I can't speak to whether you will need to run each duplication loop on a dedicated thread.

CLR host doesn't execute WPF applications but it does WinForms

I made a code very similar to https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0. The difference between both is that I'm loading the executable from memory while he's doing it from file. The other difference is that he calls some random method while I want to call Main method.
My goal is to be something like .NET Reactor's native loader.
The code works fine with Console Apps and WinForms. The only issue is that it won't load WPF applications and more specifically it fails on this line: pMethodInfo->Invoke_3(v2, p2, &v); with hr = 0x80131604 (COR_E_TARGETINVOCATION). The WPF project was created in VS 2019 and the targeting architecture (x86) and .NET Framework (4.0) match with loader's.
In the image below the Main function of the WPF application is present. I have literally no idea why it might be causing TargetInvocation.
Code here:
int LoadAssembly(LPVOID bytes, DWORD size)
{
HRESULT hr;
ICLRMetaHost* pMetaHost = NULL;
ICLRRuntimeInfo* pRuntimeInfo = NULL;
ICorRuntimeHost* pCorRuntimeHost = NULL;
IUnknownPtr UnkAppDomain = NULL;
_AppDomainPtr AppDomain = NULL;
_AssemblyPtr pAssembly = NULL;
_MethodInfoPtr pMethodInfo = NULL;
SAFEARRAY* params = NULL;
SAFEARRAY* sa = NULL;
bool bSuccess = false;
while (!bSuccess)
{
// STAThread
CoInitialize(NULL);
// Load and start the .NET runtime.
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
if (FAILED(hr))
break;
// Get the ICLRRuntimeInfo corresponding to a particular CLR version. It
// supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
if (FAILED(hr))
break;
// Check if the specified runtime can be loaded into the process. This
// method will take into account other runtimes that may already be
// loaded into the process and set pbLoadable to TRUE if this runtime can
// be loaded in an in-process side-by-side fashion.
BOOL fLoadable;
hr = pRuntimeInfo->IsLoadable(&fLoadable);
if (FAILED(hr) || !fLoadable)
break;
// Load the CLR into the current process and return a runtime interface
// pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting
// interfaces supported by CLR 4.0. Here we demo the ICorRuntimeHost
// interface that was provided in .NET v1.x, and is compatible with all
// .NET Frameworks.
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(&pCorRuntimeHost));
if (FAILED(hr))
break;
// Start the CLR
hr = pCorRuntimeHost->Start();
if (FAILED(hr))
break;
// Get a pointer to the default AppDomain in the CLR
hr = pCorRuntimeHost->GetDefaultDomain(&UnkAppDomain);
if (FAILED(hr))
break;
hr = UnkAppDomain->QueryInterface(IID_PPV_ARGS(&AppDomain));
if (FAILED(hr))
break;
SAFEARRAYBOUND sab[1];
sab[0].lLbound = 0;
sab[0].cElements = size;
sa = SafeArrayCreate(VT_UI1, 1, sab);
if (!sa)
break;
void* sa_raw;
hr = SafeArrayAccessData(sa, &sa_raw);
if (FAILED(hr))
break;
memcpy(sa_raw, bytes, size);
SafeArrayUnaccessData(sa);
hr = AppDomain->Load_3(sa, &pAssembly);
if (FAILED(hr))
break;
hr = pAssembly->get_EntryPoint(&pMethodInfo);
if (FAILED(hr))
break;
SAFEARRAY* mtd_params;
hr = pMethodInfo->GetParameters(&mtd_params);
if (FAILED(hr))
break;
SAFEARRAY* p2;
if (mtd_params->rgsabound->cElements != 0)
{
INT argc;
WCHAR** _argv = CommandLineToArgvW(GetCommandLineW(), &argc);
params = SafeArrayCreateVector(VT_BSTR, 0, argc);
if (!params)
break;
for (int i = 0; i < argc; i++)
{
long lIndex = i;
hr = SafeArrayPutElement(params, &lIndex, SysAllocString(_argv[i]));
if (FAILED(hr))
break;
}
p2 = SafeArrayCreateVector(VT_VARIANT, 0, 1);
LONG l2 = 0;
VARIANT vp2;
vp2.vt = VT_ARRAY | VT_BSTR;
vp2.parray = params;
hr = SafeArrayPutElement(p2, &l2, &vp2);
if (FAILED(hr))
break;
}
else
{
SAFEARRAYBOUND sabc[1];
sabc[0].cElements = 0;
sabc[0].lLbound = 0;
p2 = SafeArrayCreate(VT_VARIANT, 1, sabc);
}
VARIANT v;
VARIANT v2;
VariantInit(&v);
VariantInit(&v2);
hr = pMethodInfo->Invoke_3(v2, p2, &v);
VariantClear(&v);
VariantClear(&v2);
if (FAILED(hr))
break;
bSuccess = true;
}
if (pMetaHost)
pMetaHost->Release();
if (pRuntimeInfo)
pRuntimeInfo->Release();
if (pCorRuntimeHost)
{
// Please note that after a call to Stop, the CLR cannot be
// reinitialized into the same process. This step is usually not
// necessary. You can leave the .NET runtime loaded in your process.
pCorRuntimeHost->Stop();
pCorRuntimeHost->Release();
}
if (sa)
SafeArrayDestroy(sa);
if (params)
SafeArrayDestroy(params);
return hr;
}
Edit:
I tried to load a WPF application from C# using System.Reflection and it didn't work. There is the code which nearly the same as my C++ one except that the C++ one fixes the arguments:
byte[] data = File.ReadAllBytes("Test2.exe");
Assembly assembly = Assembly.Load(data);
assembly.EntryPoint.Invoke(null, new object[0]);
This is the same error I received in C++'s code except that I'm not quite sure how to check for the Inner Exceptions there.
I did some research and I realized that I have to change ResourceAssembly as they did here: Load WPF application from the memory. The new code looked like this:
byte[] data = File.ReadAllBytes("Test2.exe");
Assembly assembly = Assembly.Load(data);
Type type = typeof(Application);
FieldInfo field = type.GetField("_resourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
field.SetValue(null, assembly);
Type helper = typeof(BaseUriHelper);
PropertyInfo property = helper.GetProperty("ResourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
property.SetValue(null, assembly, null);
assembly.EntryPoint.Invoke(null, new object[0]);
The result was that the code loaded the WPF application. Now, the question is how can I do that in my C++ code? Seems like nobody dealt with that issue. The only solution I found was by loading the file from disk and not memory - http://sbytestream.pythonanywhere.com/blog/clr-hosting.

DirectX11 DXGI Capture Screen when in full screen

I'm currently trying to develop a personal project where I will light up LEDs according to what's happening on the screen of my computer.
I've tried several solutions to capture my screen, and DirectX11 with DXGI is the fastest way I found to have a good FPS Rate.
My only issue is the following: When in full screen (for example, watching Netflix trogh Win10 App or playing any game in fullscreen), it seems that nothing is captured. I have two functions (one to set up and another one to capture a frame:
Setup Up Function:
bool DXGIScreenCapturer::Init() {
int lresult(-1);
D3D_FEATURE_LEVEL lFeatureLevel;
HRESULT hr(E_FAIL);
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
gFeatureLevels,
gNumFeatureLevels,
D3D11_SDK_VERSION,
&_lDevice,
&lFeatureLevel,
&_lImmediateContext);
if (FAILED(hr))
return false;
if (!_lDevice)
return false;
// Get DXGI device
ComPtr<IDXGIDevice> lDxgiDevice;
hr = _lDevice.As(&lDxgiDevice);
if (FAILED(hr))
return false;
// Get DXGI adapter
ComPtr<IDXGIAdapter> lDxgiAdapter;
hr = lDxgiDevice->GetParent(__uuidof(IDXGIAdapter), &lDxgiAdapter);
if (FAILED(hr))
return false;
lDxgiDevice.Reset();
UINT Output = 0;
// Get output
ComPtr<IDXGIOutput> lDxgiOutput;
hr = lDxgiAdapter->EnumOutputs(Output, &lDxgiOutput);
if (FAILED(hr))
return false;
lDxgiAdapter.Reset();
hr = lDxgiOutput->GetDesc(&_lOutputDesc);
if (FAILED(hr))
return false;
// QI for Output 1
ComPtr<IDXGIOutput1> lDxgiOutput1;
hr = lDxgiOutput.As(&lDxgiOutput1);
if (FAILED(hr))
return false;
lDxgiOutput.Reset();
// Create desktop duplication
hr = lDxgiOutput1->DuplicateOutput(_lDevice.Get(), &_lDeskDupl);
if (FAILED(hr))
return false;
lDxgiOutput1.Reset();
// Create GUI drawing texture
_lDeskDupl->GetDesc(&_lOutputDuplDesc);
// Create CPU access texture
_desc.Width = _lOutputDuplDesc.ModeDesc.Width;
_desc.Height = _lOutputDuplDesc.ModeDesc.Height;
_desc.Format = _lOutputDuplDesc.ModeDesc.Format;
std::cout << _desc.Width << "x" << _desc.Height << "\n\n\n";
_desc.ArraySize = 1;
_desc.BindFlags = 0;
_desc.MiscFlags = 0;
_desc.SampleDesc.Count = 1;
_desc.SampleDesc.Quality = 0;
_desc.MipLevels = 1;
_desc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_READ;
_desc.Usage = D3D11_USAGE::D3D11_USAGE_STAGING;
while (!CaptureScreen(_result));
_result = cv::Mat(_desc.Height, _desc.Width, CV_8UC4, _resource.pData);
return true;
}
And the capture function:
bool DXGIScreenCapturer::CaptureScreen(cv::Mat& output)
{
HRESULT hr(E_FAIL);
ComPtr<IDXGIResource> lDesktopResource = nullptr;
DXGI_OUTDUPL_FRAME_INFO lFrameInfo;
ID3D11Texture2D* currTexture = NULL;
hr = _lDeskDupl->AcquireNextFrame(999, &lFrameInfo, &lDesktopResource);
if (FAILED(hr))
return false;
if (lFrameInfo.LastPresentTime.HighPart == 0) // not interested in just mouse updates, which can happen much faster than 60fps if you really shake the mouse
{
hr = _lDeskDupl->ReleaseFrame();
return false;
}
//int accum_frames = lFrameInfo.AccumulatedFrames;
//if (accum_frames > 1) {// && current_frame != 1) {
// // TOO MANY OF THESE is the problem
// // especially after having to wait >17ms in AcquireNextFrame()
//}
// QI for ID3D11Texture2D
hr = lDesktopResource.As(&_lAcquiredDesktopImage);
// Copy image into a newly created CPU access texture
hr = _lDevice->CreateTexture2D(&_desc, NULL, &currTexture);
if (FAILED(hr))
{
hr = _lDeskDupl->ReleaseFrame();
return false;
}
if (!currTexture)
{
hr = _lDeskDupl->ReleaseFrame();
return false;
}
_lImmediateContext->CopyResource(currTexture, _lAcquiredDesktopImage.Get());
UINT subresource = D3D11CalcSubresource(0, 0, 0);
_lImmediateContext->Map(currTexture, subresource, D3D11_MAP_READ, 0, &_resource);
_lImmediateContext->Unmap(currTexture, 0);
currTexture->Release();
hr = _lDeskDupl->ReleaseFrame();
output = _result;
return true;
}

Not releasing filter com object causing a crash

I am using Viveks capture filter with vlc (http://tmhare.mvps.org/downloads/vcam.zip) to emulate a capture source. When the filter is open and I close vlc I get a crash. The stack trace indicates that 2 COM objects still exist (Im guessing the filter and pin) which should be released before a CoUninitialize call. My problem is I am not sure where to release the filter and pin COM objects, I have a destructor for the fitler and pin but they are never called when vlc closes. Someone who had a similar problem (Unreleased DirectShow CSource filter makes program crash at process shutdown).
This is the important registering part of the dll.
STDAPI RegisterFilters( BOOL bRegister )
{
HRESULT hr = NOERROR;
WCHAR achFileName[MAX_PATH];
char achTemp[MAX_PATH];
ASSERT(g_hInst != 0);
if( 0 == GetModuleFileNameA(g_hInst, achTemp, sizeof(achTemp)))
return AmHresultFromWin32(GetLastError());
MultiByteToWideChar(CP_ACP, 0L, achTemp, lstrlenA(achTemp) + 1,
achFileName, NUMELMS(achFileName));
hr = CoInitialize(0);
if(bRegister)
{
hr = AMovieSetupRegisterServer(CLSID_VirtualCam, L"Virtual Cam", achFileName, L"Both", L"InprocServer32");
}
if( SUCCEEDED(hr) )
{
IFilterMapper2 *fm = 0;
hr = CreateComObject( CLSID_FilterMapper2, IID_IFilterMapper2, fm );
if( SUCCEEDED(hr) )
{
if(bRegister)
{
IMoniker *pMoniker = 0;
REGFILTER2 rf2;
rf2.dwVersion = 1;
rf2.dwMerit = MERIT_DO_NOT_USE;
rf2.cPins = 1;
rf2.rgPins = &AMSPinVCam;
hr = fm->RegisterFilter(CLSID_VirtualCam, L"Virtual Cam", &pMoniker, &CLSID_VideoInputDeviceCategory, NULL, &rf2);
}
else
{
hr = fm->UnregisterFilter(&CLSID_VideoInputDeviceCategory, 0, CLSID_VirtualCam);
}
}
// release interface
//
if(fm)
fm->Release();
}
if( SUCCEEDED(hr) && !bRegister )
hr = AMovieSetupUnregisterServer( CLSID_VirtualCam );
CoFreeUnusedLibraries();
CoUninitialize();
return hr;
}
STDAPI DllRegisterServer()
{
return RegisterFilters(TRUE);
}
STDAPI DllUnregisterServer()
{
return RegisterFilters(FALSE);
}
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}
The important filter part
CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) :
CSourceStream(NAME("Virtual Cam"),phr, pParent, pPinName), m_pParent(pParent)
{
// Set the default media type as 320x240x24#15
GetMediaType(4, &m_mt);
}
CVCamStream::~CVCamStream()
{
m_pParent->Release();
}
Leaked COM references are a sort of hard to nail down. Assuming that COM client - in your case VLC - is doing everything right (which might be not the case, but it's okay to start with this assumption), the problem is on your code. It is typically one of the two:
You are dealing with raw pointers and there is somewhere no matching Release for AddRef done earlier
There are circular references and object keep each other alive
As you already see you have two objects floating around, a good strategy would be to identify what classes are they exactly, and trace reference counter changes to see where is the lost IUnknown::Release.

DirectShow: webcam preview and image capture

After looking at a very similar question and seeing almost identical code, I've decided to ask this question separately. I want to show a video preview of the webcam's video stream to the default window DirectShow uses, and I also want the ability to "take a picture" of the video stream at any given moment.
I started with the DirectShow examples on MSDN, as well as the AMCap sample code, and have something I believe should should the preview part, but does not. I've found no examples of grabbing an image from the video stream except using SampleGrabber, which is deprecated and therefore I am trying not to use it.
Below is my code, line for line. Note that most of the code in EnumerateCameras is commented out. That code would've been for attaching to another window, which I don't want to do. In the MSDN documentation, it explicitly states that the VMR_7 creates its own window to display the video stream. I get no errors in my app, but this window never appears.
My question then is this: What am I doing wrong? Alternatively, if you know of a simple example of what I am trying to do, link me to it. AMCap is not a simple example, for reference.
NOTE: InitalizeVMR is for running in windowless state, which is my ultimate goal (integrating into a DirectX game). For now, however, i just want it to run in the simplest mode possible.
EDIT: The first portion of this question, that is previewing the camera stream, is solved. I am now just looking for an alternative to the deprecated SampleGrabber class so I can snap a photo at any moment and save it to a file.
EDIT: After looking for almost an hour on google, the general concensus seems to be that you HAVE to use ISampleGrabber. Please let me know if you find anything different.
Testing code (main.cpp):
CWebcam* camera = new CWebcam();
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
MessageBox(NULL, L"text", L"caption", NULL);
if (SUCCEEDED(hr))
{
camera->Create();
camera->EnumerateCameras();
camera->StartCamera();
}
int d;
cin >> d;
Webcam.cpp:
#include "Webcam.h"
CWebcam::CWebcam() {
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
//m_pTexInst = nullptr;
//m_pTexRes = nullptr;
}
CWebcam::~CWebcam() {
CoUninitialize();
m_pDeviceMonikers->Release();
m_pMediaController->Release();
}
BOOL CWebcam::Create() {
InitCaptureGraphBuilder(&m_pFilterGraph, &m_pCaptureGraph);
hr = m_pFilterGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaController);
return TRUE;
}
void CWebcam::Destroy() {
}
void CWebcam::EnumerateCameras() {
HRESULT hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &m_pDeviceMonikers);
if (SUCCEEDED(hr))
{
//DisplayDeviceInformation(m_pDeviceMonikers);
//m_pDeviceMonikers->Release();
IMoniker *pMoniker = NULL;
if(m_pDeviceMonikers->Next(1, &pMoniker, NULL) == S_OK)
{
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pCameraFilter);
if (SUCCEEDED(hr))
{
hr = m_pFilterGraph->AddFilter(m_pCameraFilter, L"Capture Filter");
}
}
// connect the output pin to the video renderer
if(SUCCEEDED(hr))
{
hr = m_pCaptureGraph->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
m_pCameraFilter, NULL, NULL);
}
//InitializeVMR(hwnd, m_pFilterGraph, &m_pVMRControl, 1, FALSE);
//get the video window that will be displayed from the filter graph
IVideoWindow *pVideoWindow = NULL;
hr = m_pFilterGraph->QueryInterface(IID_IVideoWindow, (void **)&pVideoWindow);
/*if(hr != NOERROR)
{
printf("This graph cannot preview properly");
}
else
{
//get the video stream configurations
hr = m_pCaptureGraph->FindInterface(&PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video, m_pCameraFilter,
IID_IAMStreamConfig, (void **)&m_pVideoStreamConfig);
//Find out if this is a DV stream
AM_MEDIA_TYPE *pMediaTypeDV;
//fake window handle
HWND window = NULL;
if(m_pVideoStreamConfig && SUCCEEDED(m_pVideoStreamConfig->GetFormat(&pMediaTypeDV)))
{
if(pMediaTypeDV->formattype == FORMAT_DvInfo)
{
// in this case we want to set the size of the parent window to that of
// current DV resolution.
// We get that resolution from the IVideoWindow.
IBasicVideo* pBasivVideo;
// If we got here, gcap.pVW is not NULL
//ASSERT(pVideoWindow != NULL);
hr = pVideoWindow->QueryInterface(IID_IBasicVideo, (void**)&pBasivVideo);
/*if(SUCCEEDED(hr))
{
HRESULT hr1, hr2;
long lWidth, lHeight;
hr1 = pBasivVideo->get_VideoHeight(&lHeight);
hr2 = pBasivVideo->get_VideoWidth(&lWidth);
if(SUCCEEDED(hr1) && SUCCEEDED(hr2))
{
ResizeWindow(lWidth, abs(lHeight));
}
}
}
}
RECT rc;
pVideoWindow->put_Owner((OAHWND)window); // We own the window now
pVideoWindow->put_WindowStyle(WS_CHILD); // you are now a child
GetClientRect(window, &rc);
pVideoWindow->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
pVideoWindow->put_Visible(OATRUE);
}*/
}
}
BOOL CWebcam::StartCamera() {
if(m_bIsStreaming == FALSE)
{
m_bIsStreaming = TRUE;
hr = m_pMediaController->Run();
if(FAILED(hr))
{
// stop parts that ran
m_pMediaController->Stop();
return FALSE;
}
return TRUE;
}
return FALSE;
}
void CWebcam::EndCamera() {
if(m_bIsStreaming)
{
hr = m_pMediaController->Stop();
m_bIsStreaming = FALSE;
//invalidate client rect as well so that it must redraw
}
}
BOOL CWebcam::CaptureToTexture() {
return TRUE;
}
HRESULT CWebcam::InitCaptureGraphBuilder(
IGraphBuilder **ppGraph, // Receives the pointer.
ICaptureGraphBuilder2 **ppBuild // Receives the pointer.
)
{
if (!ppGraph || !ppBuild)
{
return E_POINTER;
}
IGraphBuilder *pGraph = NULL;
ICaptureGraphBuilder2 *pBuild = NULL;
// Create the Capture Graph Builder.
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuild );
if (SUCCEEDED(hr))
{
// Create the Filter Graph Manager.
hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// Initialize the Capture Graph Builder.
pBuild->SetFiltergraph(pGraph);
// Return both interface pointers to the caller.
*ppBuild = pBuild;
*ppGraph = pGraph; // The caller must release both interfaces.
return S_OK;
}
else
{
pBuild->Release();
}
}
return hr; // Failed
}
HRESULT CWebcam::EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
// Create the System Device Enumerator.
ICreateDevEnum *pSystemDeviceEnumerator;
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSystemDeviceEnumerator));
if (SUCCEEDED(hr))
{
// Create an enumerator for the category.
hr = pSystemDeviceEnumerator->CreateClassEnumerator(category, ppEnum, 0);
if (hr == S_FALSE)
{
hr = VFW_E_NOT_FOUND; // The category is empty. Treat as an error.
}
pSystemDeviceEnumerator->Release();
}
return hr;
}
void CWebcam::DisplayDeviceInformation(IEnumMoniker *pEnum)
{
IMoniker *pMoniker = NULL;
int counter = 0;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
{
IPropertyBag *pPropBag;
HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
if (FAILED(hr))
{
pMoniker->Release();
continue;
}
VARIANT var;
VariantInit(&var);
// Get description or friendly name.
hr = pPropBag->Read(L"Description", &var, 0);
if (FAILED(hr))
{
hr = pPropBag->Read(L"FriendlyName", &var, 0);
}
if (SUCCEEDED(hr))
{
printf("%d: %S\n", counter, var.bstrVal);
VariantClear(&var);
}
hr = pPropBag->Write(L"FriendlyName", &var);
// WaveInID applies only to audio capture devices.
hr = pPropBag->Read(L"WaveInID", &var, 0);
if (SUCCEEDED(hr))
{
printf("%d: WaveIn ID: %d\n", counter, var.lVal);
VariantClear(&var);
}
hr = pPropBag->Read(L"DevicePath", &var, 0);
if (SUCCEEDED(hr))
{
// The device path is not intended for display.
printf("%d: Device path: %S\n", counter, var.bstrVal);
VariantClear(&var);
}
pPropBag->Release();
pMoniker->Release();
counter++;
}
}
HRESULT CWebcam::InitializeVMR(
HWND hwndApp, // Application window.
IGraphBuilder* pFG, // Pointer to the Filter Graph Manager.
IVMRWindowlessControl** ppWc, // Receives the interface.
DWORD dwNumStreams, // Number of streams to use.
BOOL fBlendAppImage // Are we alpha-blending a bitmap?
)
{
IBaseFilter* pVmr = NULL;
IVMRWindowlessControl* pWc = NULL;
*ppWc = NULL;
// Create the VMR and add it to the filter graph.
HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL,
CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr);
if (FAILED(hr))
{
return hr;
}
hr = pFG->AddFilter(pVmr, L"Video Mixing Renderer");
if (FAILED(hr))
{
pVmr->Release();
return hr;
}
// Set the rendering mode and number of streams.
IVMRFilterConfig* pConfig;
hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig);
if (SUCCEEDED(hr))
{
pConfig->SetRenderingMode(VMRMode_Windowless);
// Set the VMR-7 to mixing mode if you want more than one video
// stream, or you want to mix a static bitmap over the video.
// (The VMR-9 defaults to mixing mode with four inputs.)
if (dwNumStreams > 1 || fBlendAppImage)
{
pConfig->SetNumberOfStreams(dwNumStreams);
}
pConfig->Release();
hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc);
if (SUCCEEDED(hr))
{
pWc->SetVideoClippingWindow(hwndApp);
*ppWc = pWc; // The caller must release this interface.
}
}
pVmr->Release();
// Now the VMR can be connected to other filters.
return hr;
}
In windowless mode VMR would not create separate window. Since you started initialization for widnowless mode, you have to follow SetVideoClippingWindow with IVMRWindowlessControl::SetVideoPosition call to provide position within the window, see VMR Windowless Mode on MSDN.
Another sample code snippet for you: http://www.assembla.com/code/roatl-utilities/subversion/nodes/trunk/FullScreenWindowlessVmrSample01/MainDialog.h#ln188