Search code examples
c++windowsdirectx-11dxgi

C++ capture screen using DirectX11


I am trying to use DirectX11 in C++ to Capture the screen and convert it to a texture I can render with OpenGL or ImGui. I have used the Windows API with BitBlt, this however results in fps lower than 15, so this is not an option.

I am using Windows 11, with a Nvidia graphics card.

Library Import:

#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")

Initializing DirectX11:

    bool DirectX11::initDirectX11() {
        HRESULT hr = S_OK;
        IDXGIFactory* DXGIFactory = nullptr;
        IDXGIAdapter* Adapter = nullptr;
        D3D_FEATURE_LEVEL FeatureLevel = D3D_FEATURE_LEVEL_11_0;
        Output = nullptr;
        deskDupl = nullptr;


        hr = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)(&DXGIFactory));
        if (FAILED(hr)) {
            std::cout << "Failed to create DXGI Factory" << std::endl;
            return false;
        }


        hr = DXGIFactory->EnumAdapters(0, &Adapter);
        if (FAILED(hr)) {
            std::cout << "Failed to create adapter" << std::endl;
            return false;
        }


        hr = Adapter->EnumOutputs(0, &Output);
        if (FAILED(hr)) {
            std::cout << "Failed to create output" << std::endl;
            return false;
        }

        hr = Output->QueryInterface(__uuidof(Output), (void**)(&deskDupl));
        if (FAILED(hr)) {
            std::cout << "Failed to create output1" << std::endl;
            return false;
        }


        hr = Output->GetDesc(&OutputDesc);
        if (FAILED(hr)) {
            std::cout << "Failed to get output description" << std::endl;
            return false;
        }

        hr = D3D11CreateDevice(Adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, NULL, 0, D3D11_SDK_VERSION, &device, &FeatureLevel, &deviceContext);
        if (FAILED(hr)) {
            std::cout << "Failed to create D3D11 device" << std::endl;
            return false;
        }

        DXGIFactory->Release();
        Adapter->Release();
        return true;
    }

Capturing the Screen:

    void  DirectX11::captureScreen() {
        HRESULT hr = S_OK;

        IDXGIResource* DesktopResource;
        DXGI_OUTDUPL_FRAME_INFO FrameInfo;

        // Get new frame
        hr = deskDupl->AcquireNextFrame(500, &FrameInfo, &DesktopResource);
        if (FAILED(hr)) {
            std::cout << "Failed to acquire next frame" << std::endl;
            return;
        }

        
        // Store the frame as a texture
        // Trows exception
        // Read access violation
        // DesktopResource was 0xFFFFFFFFFFFFFFFF   
        hr = DesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&AcquiredDesktopImage));
        if (FAILED(hr)) {
            std::cout << "Failed to query interface" << std::endl;
            return;
        }

    }

The problem is that it keeps throwing a read acces violation exception.

Read access violation
DesktopResource was 0xFFFFFFFFFFFFFFFF  

Solution

  • This code

    hr = Output->QueryInterface(__uuidof(Output), (void**)(&deskDupl));
    

    Is invalid as it writes an IDXGIOutput reference to an IDXGIOutputDuplication reference.

    To get an IDXGIOutputDuplication reference, you must call the IDXGIOutput1::DuplicateOutput method, so you must get an IDXGIOutput1 first, like this:

    IDXGIOutput1* o1;
    hr = Output->QueryInterface(__uuidof(IDXGIOutput1), (void**)(&o1));
    

    And then

    hr = o1->DuplicateOutput(device, &deskDupl);
    

    But for this to succeed, you must create the device from an IDXGIFactory1 or from an IDXGIFactory factory created from CreateDXGIFactory1 (DXGI 1.1), so replace the call CreateDXGIFactory by CreateDXGIFactory1.

    Note: to avoid casting to dangerous (void**) for QueryInterface and many other COM calls, and so avoid raw casting errors (some of it at least) when working with Visual Studio you can use the IID_PPV_ARGS macro. So you can write this:

    hr = Output->QueryInterface(IID_PPV_ARGS(&o1));
    

    And nowadays, IUnknown defining headers (Windows SDK's Unknwnbase.h) also defines some more useful C++ templates, so you can even call QueryInterface like this directly:

    hr = Output->QueryInterface(&o1);
    

    PS: note sure this exists with other compilers than MSVC.