DX11 GPU memory leak on OpenSharedResource

Mike 0 Reputation points
2025-08-12T16:14:12.51+00:00

I'm trying to figure why i'm getting GPU memory leak on this code.

I checked using System Informer, at "Gpu Dedicated bytes" the memory usage increases on each loop iteration.

I thought it was a missing call to CloseHandle(hDxSurface);, but this throws an exception.

I'm not sure what else is causing the leak.

#include "stdafx.h"
#include <Windows.h>
#include <chrono>
#include <thread>
#include <atomic>
#include <memory>
#include <d3d11.h>
#include <dxgi1_2.h>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")



typedef HRESULT(WINAPI* PFN_DwmDxGetWindowSharedSurface)(HWND, HANDLE*, LUID*, ULONG*, ULONG*, ULONGLONG*);
PFN_DwmDxGetWindowSharedSurface DwmGetDxSharedSurface = nullptr;

bool getDwmGetDxSharedSurface()
{
    HMODULE hUser32 = LoadLibraryA("user32.dll");
    if (hUser32 != NULL)
        DwmGetDxSharedSurface = (PFN_DwmDxGetWindowSharedSurface)GetProcAddress(hUser32, "DwmGetDxSharedSurface");
    return DwmGetDxSharedSurface != nullptr;
}



class WindowCapture
{
public:    
    // DirectX resources (cached and reused)
    IDXGIFactory1*       m_pFactory = nullptr;
    IDXGIAdapter*        m_pAdapter = nullptr;
    ID3D11DeviceContext* m_pContext = nullptr;
    ID3D11Device*        m_pDevice  = nullptr;
    // Target window
    HWND m_hwnd = nullptr;
    
    // **PERFORMANCE OPTIMIZATION**: Double-buffered staging textures
    ID3D11Texture2D* m_pStagingTexture[2] = {nullptr, nullptr};
    int m_currentStagingIndex = 0;
    UINT m_lastWidth          = 0;
    UINT m_lastHeight         = 0;
    DXGI_FORMAT m_lastFormat  = DXGI_FORMAT_UNKNOWN;
    
    // DIB Section for direct pixel access (cached)
    HBITMAP m_hDIBSection = nullptr;
    void* m_pDIBBits      = nullptr;
    UINT m_dibWidth       = 0;
    UINT m_dibHeight      = 0;

    WindowCapture() {};
    bool initialize(HWND hwnd);
    bool captureToPixelBuffer(void** ppPixels, UINT* pWidth, UINT* pHeight, UINT* pPitch);
    bool captureToFile(const wchar_t* filename);
    bool initializeDirectX();
    bool createOrUpdateStagingTextures(UINT width, UINT height, DXGI_FORMAT format);
    bool createOrUpdateDIBSection(UINT width, UINT height);
    bool copyTextureDataToDIB(const D3D11_MAPPED_SUBRESOURCE& mapped, UINT width, UINT height);
};



bool WindowCapture::initialize(HWND hwnd)
{
    if (!hwnd || !IsWindow(hwnd))
        return false;
    
    m_hwnd = hwnd;
    if (!getDwmGetDxSharedSurface())
    {
        $err, "Failed to get DwmGetDxSharedSurface function";
        return false;
    }

    HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
    if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
    {
    	$err, "Failed to initialize COM", hr;
        return false;
    }

    if (!initializeDirectX())
        return false;
    return true;
}



bool WindowCapture::initializeDirectX()
{
    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&m_pFactory));
    if (FAILED(hr) || !m_pFactory)
    {
        $err, "CreateDXGIFactory1 failed", hr;
        return false;
    }

    m_pFactory->EnumAdapters(0, &m_pAdapter);
    if (!m_pAdapter)
    {
        $err, "Failed to enumerate adapters";
        m_pFactory->Release();
        return false;
	}

    const D3D_FEATURE_LEVEL featureLevels[] = 
    {
        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(m_pAdapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, 6, D3D11_SDK_VERSION, &m_pDevice, NULL, &m_pContext);
    if (FAILED(hr) || !m_pDevice)
    {
        $err, "D3D11CreateDevice failed", hr;
        m_pFactory->Release();
        return false;
    }

    return true;
}



bool WindowCapture::createOrUpdateStagingTextures(UINT width, UINT height, DXGI_FORMAT format)
{
    // Reuse existing staging textures if dimensions and format match
    if (m_pStagingTexture[0] && m_lastWidth == width && m_lastHeight == height && m_lastFormat == format)
        return true;
    
    // Release old staging textures
    for (int i = 0; i < 2; ++i)
    {
        if (m_pStagingTexture[i])
        {
            m_pStagingTexture[i]->Release();
            m_pStagingTexture[i] = nullptr;
        }
    }
    
    // Create new staging textures (double-buffered)
    D3D11_TEXTURE2D_DESC stagingDesc = {};
    stagingDesc.Width              = width;
    stagingDesc.Height             = height;
    stagingDesc.MipLevels          = 1;
    stagingDesc.ArraySize          = 1;
    stagingDesc.Format             = format;
    stagingDesc.SampleDesc.Count   = 1;
    stagingDesc.SampleDesc.Quality = 0;
    stagingDesc.Usage              = D3D11_USAGE_STAGING;
    stagingDesc.BindFlags          = 0;
    stagingDesc.CPUAccessFlags     = D3D11_CPU_ACCESS_READ;
    stagingDesc.MiscFlags          = 0;
    
    for (int i = 0; i < 2; ++i)
    {
        HRESULT hr = m_pDevice->CreateTexture2D(&stagingDesc, nullptr, &m_pStagingTexture[i]);
        if (FAILED(hr))
        {
			$err, "Failed to create staging texture %1", i, hr;
            for (int j = 0; j < i; ++j)
            {
                if (m_pStagingTexture[j])
                {
                    m_pStagingTexture[j]->Release();
                    m_pStagingTexture[j] = nullptr;
                }
            }
            return false;
        }
    }
    
    // Cache dimensions
    m_lastWidth  = width;
    m_lastHeight = height;
    m_lastFormat = format;
    m_currentStagingIndex = 0;
    
    return true;
}



bool WindowCapture::createOrUpdateDIBSection(UINT width, UINT height)
{
    // Reuse existing DIB if dimensions match
    if (m_hDIBSection && m_dibWidth == width && m_dibHeight == height)
        return true;
    
    // Release old DIB
    if (m_hDIBSection)
    {
        DeleteObject(m_hDIBSection);
        m_hDIBSection = nullptr;
        m_pDIBBits = nullptr;
    }
    
    // Create new DIB Section
    BITMAPINFO bi              = {};
    bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth       = static_cast<LONG>(width);
    bi.bmiHeader.biHeight      = -static_cast<LONG>(height); // Top-down DIB
    bi.bmiHeader.biPlanes      = 1;
    bi.bmiHeader.biBitCount    = 32; // 32-bit BGRA
    bi.bmiHeader.biCompression = BI_RGB;
    bi.bmiHeader.biSizeImage   = 0;
    
    m_hDIBSection = CreateDIBSection(nullptr, &bi, DIB_RGB_COLORS, &m_pDIBBits, nullptr, 0);    
    if (!m_hDIBSection)
    {
		$err, "Failed to create DIB section", GetLastError();
        return false;
    }
    
    m_dibWidth  = width;
    m_dibHeight = height;
    
    return true;
}



bool WindowCapture::copyTextureDataToDIB(const D3D11_MAPPED_SUBRESOURCE& mapped, UINT width, UINT height)
{
    if (!m_pDIBBits)
        return false;
    
    const UINT8* pSrc = static_cast<const UINT8*>(mapped.pData);
    UINT8* pDst = static_cast<UINT8*>(m_pDIBBits);
    UINT srcPitch = mapped.RowPitch;
    UINT dstPitch = ((width * 32 + 31) / 32) * 4;
    
    UINT rowBytes = width * 4; // BGRA pixels
    
    // Add memory validation and alignment checks
    if (!pSrc || !pDst || rowBytes == 0 || height == 0)
        return false;    
    // Validate that we don't exceed buffer bounds
    if (rowBytes > dstPitch || rowBytes > srcPitch)
        return false;

    if (srcPitch == dstPitch)
    {
        // Single memcpy for entire image
        memcpy(pDst, pSrc, height * srcPitch);
        return true;
    }

    // Standard memcpy
    for (UINT y = 0; y < height; ++y)
    {
        const UINT8* srcRow = pSrc + y * srcPitch;
        UINT8* dstRow = pDst + y * dstPitch;
        
        // Validate pointers before memcpy
        if (srcRow && dstRow)
            memcpy(dstRow, srcRow, rowBytes);
    }
    
    return true;
}



bool WindowCapture::captureToPixelBuffer(void** ppPixels, UINT* pWidth, UINT* pHeight, UINT* pPitch)
{
    if (!IsWindow(m_hwnd))
        return false;

    HANDLE hDxSurface = nullptr;
    HRESULT hr = DwmGetDxSharedSurface(m_hwnd, &hDxSurface, NULL, NULL, NULL, NULL);
    if (FAILED(hr) || !hDxSurface)
    {
		$err, "Failed to get shared surface %1\nhDxSurface: %2", hr, hDxSurface;
        return false;
    }

    ID3D11Texture2D* pSharedTexture;
    hr = m_pDevice->OpenSharedResource(hDxSurface, IID_PPV_ARGS(&pSharedTexture));
    if (FAILED(hr) || !pSharedTexture)
    {
        $err, "Failed to open shared resource", hr;
		return false;
    }

    D3D11_TEXTURE2D_DESC desc;
    pSharedTexture->GetDesc(&desc);

    // Create or reuse staging textures and DIB
    if (!createOrUpdateStagingTextures(desc.Width, desc.Height, desc.Format) ||
        !createOrUpdateDIBSection(desc.Width, desc.Height))
    {
        pSharedTexture->Release();
        return false;
    }

    // **DOUBLE-BUFFER STRATEGY**: Use alternating staging textures
    ID3D11Texture2D* pCurrentStaging = m_pStagingTexture[m_currentStagingIndex];
    ID3D11Texture2D* pPreviousStaging = m_pStagingTexture[1 - m_currentStagingIndex];
    
    // Copy to current staging texture (GPU operation)
    m_pContext->CopyResource(pCurrentStaging, pSharedTexture);
    
    // Try to map the previous staging texture first
    // This avoids waiting for the current copy to complete
    D3D11_MAPPED_SUBRESOURCE mapped;
    ID3D11Texture2D* pTextureToCopy = pPreviousStaging;
    
    hr = m_pContext->Map(pPreviousStaging, 0, D3D11_MAP_READ, D3D11_MAP_FLAG_DO_NOT_WAIT, &mapped);
            
    if (hr == DXGI_ERROR_WAS_STILL_DRAWING)
    {
        // Previous texture still busy, try current texture with optimized retry                
        // Force completion of current copy
        m_pContext->Flush();
        
        // Try current staging texture with retry logic
        const int MAX_RETRIES = 3;
        int retryCount = 0;
        
        do 
        {
            hr = m_pContext->Map(pCurrentStaging, 0, D3D11_MAP_READ, D3D11_MAP_FLAG_DO_NOT_WAIT, &mapped);
            
            if (hr == DXGI_ERROR_WAS_STILL_DRAWING && retryCount < MAX_RETRIES)
            {
                retryCount++;
                if (retryCount == 1) // Quick yield
                    std::this_thread::yield();
                else if (retryCount == 2) // Short sleep
                    std::this_thread::sleep_for(std::chrono::microseconds(50));
                else
                {
                    // Blocking map as last resort
                    hr = m_pContext->Map(pCurrentStaging, 0, D3D11_MAP_READ, 0, &mapped);
                    break;
                }
            }
        } 
        while (hr == DXGI_ERROR_WAS_STILL_DRAWING && retryCount < MAX_RETRIES);
        
        pTextureToCopy = pCurrentStaging;
    }
     
	bool success = false;
    if (SUCCEEDED(hr))
    {
        if (copyTextureDataToDIB(mapped, desc.Width, desc.Height))
        {
            // Return direct pointer to pixel data
            *ppPixels = m_pDIBBits;
            *pWidth   = desc.Width;
            *pHeight  = desc.Height;
            *pPitch   = ((desc.Width * 32 + 31) / 32) * 4;                    
            success   = true;
        }
        else
            $err, "Failed to copy texture data to DIB section";
    
        // Unmap the texture we used
        m_pContext->Unmap(pTextureToCopy, 0);
    }
    else
		$err, "Failed to map staging texture", hr;
     
    // Swap staging buffer indices for next frame
    m_currentStagingIndex = 1 - m_currentStagingIndex; 
    pSharedTexture->Release();

	// Calling CloseHandle on the shared surface THROW EXCEPTION!!!
    //CloseHandle(hDxSurface);
    return success;
}



bool WindowCapture::captureToFile(const wchar_t* filename)
{
    void* pPixels = nullptr;
    UINT width, height, pitch;
    
    if (!captureToPixelBuffer(&pPixels, &width, &height, &pitch))
        return false;
    
    // Use existing DIB section with GDI+ for file saving
    Gdiplus::Bitmap bitmap(width, height, pitch, PixelFormat32bppARGB, static_cast<BYTE*>(pPixels));
    
    CLSID pngClsid;
    UINT num = 0, size = 0;
    Gdiplus::GetImageEncodersSize(&num, &size);
    if (size > 0)
    {
        auto pImageCodecInfo = std::make_unique<Gdiplus::ImageCodecInfo[]>(size / sizeof(Gdiplus::ImageCodecInfo));
        Gdiplus::GetImageEncoders(num, size, pImageCodecInfo.get());
        
        for (UINT j = 0; j < num; ++j)
        {
            if (wcscmp(pImageCodecInfo[j].MimeType, L"image/png") == 0)
            {
                pngClsid = pImageCodecInfo[j].Clsid;
                break;
            }
        }
    }
    
    Gdiplus::Status status = bitmap.Save(filename, &pngClsid, nullptr);
    return status == Gdiplus::Ok;
}



int main(int argc, char *argv[])
{
    SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
    
    HWND hwnd = FindWindowA(NULL, "Device");    
    if (!hwnd)
    {
        $err, "Window not found";
        return 1;
    }

    WindowCapture capture;
    if (!capture.initialize(hwnd))
        return 1;

    std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
    std::chrono::milliseconds interval(1000);

    for (int i = 0; ; i++)
    {
        void* pPixels = nullptr;
        UINT width, height, pitch;
        
        if (capture.captureToPixelBuffer(&pPixels, &width, &height, &pitch))
        {
            wchar_t filename[256];
            swprintf_s(filename, L"captured\\capture_%06d.png", i);
            capture.captureToFile(filename);
        }
        else
			$err, "Capture failed (attempt %1)", i;
        
        startTime = startTime + interval;
        std::this_thread::sleep_until(startTime);
    }
    
    return 0;
}
Developer technologies | C++
0 comments No comments
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.