当某些应用程序处于全屏模式时,DesktopDuplication API会产生黑框

时间:2018-07-31 12:55:40

标签: c++ graphics directx-11 dxgi desktop-duplication

我正在构建一个用于通过网络在多个客户端之间实时获取和共享屏幕截图的应用程序。

我正在使用MS Desktop Duplication API来获取图像数据,并且除某些边缘情况外,它工作顺利。

我一直在使用四款游戏作为测试应用程序,以测试屏幕捕获在全屏模式下的表现,它们是风暴英雄,彩虹六号围攻,反恐精英和PlayerUnknown的战场。

在拥有GeForce GTX 1070显卡的我自己的计算机上;对于所有测试应用程序,无论在全屏模式下还是在全屏模式下,一切正常。但是,在另外两台运行GeForce GTX 980的计算机上;除PUBG以外的所有测试应用程序均可使用。当PUBG在全屏模式下运行时,我的桌面复制会产生全黑图像,我不知道为什么 Desktop Duplication Sample适用于所有测试机和测试应用程序。

除了从像素数据中提取像素数据并创建自己的SDL(OpenGL)纹理,而不是直接使用获取的ID3D11Texture2D之外,我的操作与示例基本相同。

为什么GTX 980上的PUBG全屏显示是唯一失败的测试用例?

获取帧,处理“ DXGI_ERROR_ACCESS_LOST”错误或我如何从GPU复制数据的方式有问题吗?

声明:

IDXGIOutputDuplication* m_OutputDup = nullptr;
Microsoft::WRL::ComPtr<ID3D11Device> m_Device = nullptr;
ID3D11DeviceContext* m_DeviceContext = nullptr;
D3D11_TEXTURE2D_DESC m_TextureDesc;

初始化:

bool InitializeScreenCapture()
{
    HRESULT result = E_FAIL;
    if (!m_Device)
    {
        D3D_FEATURE_LEVEL featureLevels = D3D_FEATURE_LEVEL_11_0;
        D3D_FEATURE_LEVEL featureLevel;
        result = D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            0,
            &featureLevels,
            1,
            D3D11_SDK_VERSION,
            &m_Device,
            &featureLevel,
            &m_DeviceContext);
        if (FAILED(result) || !m_Device)
        {
            Log("Failed to create D3DDevice);
            return false;
        }
    }

    // Get DXGI device
    ComPtr<IDXGIDevice> DxgiDevice;
    result = m_Device.As(&DxgiDevice);
    if (FAILED(result))
    {
        Log("Failed to get DXGI device);
        return false;
    }

    // Get DXGI adapter
    ComPtr<IDXGIAdapter> DxgiAdapter;
    result = DxgiDevice->GetParent(__uuidof(IDXGIAdapter), &DxgiAdapter);
    if (FAILED(result))
    {
        Log("Failed to get DXGI adapter);
        return false;
    }

    DxgiDevice.Reset();

    // Get output
    UINT Output = 0;
    ComPtr<IDXGIOutput> DxgiOutput;
    result = DxgiAdapter->EnumOutputs(Output, &DxgiOutput);
    if (FAILED(result))
    {
        Log("Failed to get DXGI output);
        return false;
    }

    DxgiAdapter.Reset();

    ComPtr<IDXGIOutput1> DxgiOutput1;
    result = DxgiOutput.As(&DxgiOutput1);
    if (FAILED(result))
    {
        Log("Failed to get DXGI output1);
        return false;
    }

    DxgiOutput.Reset();

    // Create desktop duplication
    result = DxgiOutput1->DuplicateOutput(m_Device.Get(), &m_OutputDup);
    if (FAILED(result))
    {
        Log("Failed to create output duplication);
        return false;
    }

    DxgiOutput1.Reset();

    DXGI_OUTDUPL_DESC outputDupDesc;
    m_OutputDup->GetDesc(&outputDupDesc);

    // Create CPU access texture description
    m_TextureDesc.Width = outputDupDesc.ModeDesc.Width;
    m_TextureDesc.Height = outputDupDesc.ModeDesc.Height;
    m_TextureDesc.Format = outputDupDesc.ModeDesc.Format;
    m_TextureDesc.ArraySize = 1;
    m_TextureDesc.BindFlags = 0;
    m_TextureDesc.MiscFlags = 0;
    m_TextureDesc.SampleDesc.Count = 1;
    m_TextureDesc.SampleDesc.Quality = 0;
    m_TextureDesc.MipLevels = 1;
    m_TextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_READ;
    m_TextureDesc.Usage = D3D11_USAGE::D3D11_USAGE_STAGING;

    return true;
}

屏幕截图:

void TeamSystem::CaptureScreen()
{
    if (!m_ScreenCaptureInitialized)
    {
        Log("Attempted to capture screen without ScreenCapture being initialized");
        return false;
    }

    HRESULT result = E_FAIL;
    DXGI_OUTDUPL_FRAME_INFO frameInfo;
    ComPtr<IDXGIResource> desktopResource = nullptr;
    ID3D11Texture2D* copyTexture = nullptr;
    ComPtr<ID3D11Resource> image;

    int32_t attemptCounter = 0;
    DWORD startTicks = GetTickCount();
    do // Loop until we get a non empty frame
    {
        m_OutputDup->ReleaseFrame();
        result = m_OutputDup->AcquireNextFrame(1000, &frameInfo, &desktopResource);
        if (FAILED(result))
        {
            if (result == DXGI_ERROR_ACCESS_LOST) // Access may be lost when changing from/to fullscreen mode(any application); when this happens we need to reaquirce the outputdup
            {
                m_OutputDup->ReleaseFrame();
                m_OutputDup->Release();
                m_OutputDup = nullptr;
                m_ScreenCaptureInitialized = InitializeScreenCapture();
                if (m_ScreenCaptureInitialized)
                {
                    result = m_OutputDup->AcquireNextFrame(1000, &frameInfo, &desktopResource);
                }
                else
                {
                    Log("Failed to reinitialize screen capture after access was lost");
                    return false;
                }
            }

            if (FAILED(result))
            {
                Log("Failed to acquire next frame);
                return false;
            }
        }
        attemptCounter++;

        if (GetTickCount() - startTicks > 3000)
        {
            Log("Screencapture timed out after " << attemptCounter << " attempts");
            return false;
        }

    } while(frameInfo.TotalMetadataBufferSize <= 0 || frameInfo.LastPresentTime.QuadPart <= 0); // This is how you wait for an image containing image data according to SO (https://stackoverflow.com/questions/49481467/acquirenextframe-not-working-desktop-duplication-api-d3d11)

    Log("ScreenCapture succeeded after " << attemptCounter << " attempt(s)");

    // Query for IDXGIResource interface
    result = desktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&copyTexture));
    desktopResource->Release();
    desktopResource = nullptr;
    if (FAILED(result))
    {
        Log("Failed to acquire texture from resource);
        m_OutputDup->ReleaseFrame();
        return false;
    }

    // Copy image into a CPU access texture
    ID3D11Texture2D* stagingTexture = nullptr;
    result = m_Device->CreateTexture2D(&m_TextureDesc, nullptr, &stagingTexture);
    if (FAILED(result) || stagingTexture == nullptr)
    {
        Log("Failed to copy image data to access texture);
        m_OutputDup->ReleaseFrame();
        return false;
    }

    D3D11_MAPPED_SUBRESOURCE mappedResource;
    m_DeviceContext->CopyResource(stagingTexture, copyTexture);
    m_DeviceContext->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &mappedResource);
    void* copy = malloc(m_TextureDesc.Width * m_TextureDesc.Height * 4);
    memcpy(copy, mappedResource.pData, m_TextureDesc.Width * m_TextureDesc.Height * 4);
    m_DeviceContext->Unmap(stagingTexture, 0);
    stagingTexture->Release();
    m_OutputDup->ReleaseFrame();

    // Create a new SDL texture from the data in the copy varialbe

    free(copy);
    return true;
}

一些注意事项:

  • 我已经修改了原始代码以使其更具可读性,因此缺少一些清理和登录错误处理的功能。
  • 在任何测试情况下都不会触发错误或超时情况(DXGI_ERROR_ACCESS_LOST除外)。
  • “ attemptCounter”在任何测试情况下都不会超过2。
  • 测试用例受到限制,因为我无法访问产生黑色图像用例的计算机。

1 个答案:

答案 0 :(得分:0)

罪魁祸首是CopyResource()以及我如何创建CPU访问纹理。 CopyResource()返回void,这就是为什么我之前没有研究过它的原因;我不认为它会以任何重大方式失败,因为在这种情况下,我希望它返回bool或HRESULT。

但是,在CopyResource()的文档中并未披露一些失败案例。

  

此方法很不寻常,因为它会导致GPU执行复制操作(类似于CPU的memcpy)。因此,它具有一些旨在提高性能的限制。例如,源和目标资源:

     
      
  • 必须是不同的资源。
  •   
  • 必须是同一类型。
  •   
  • 必须具有相同的尺寸(适当时包括宽度,高度,深度和大小)。
  •   
  • 必须具有兼容的DXGI格式,这意味着格式必须相同或至少来自同一类型组。
  •   
  • 当前无法映射。
  •   

由于初始化代码在测试应用程序进入全屏之前运行,因此使用桌面分辨率,格式等来设置CPU访问纹理描述。这导致CopyResouce()静默失败,现在只需将任何内容写入 stagingTexture < / em>在使用非本机分辨率的测试用例中进行测试。

总结;我只是将 m_TextureDescription 设置移至CaptureScreen()并使用了 copyTexture 的描述来获取我不想在纹理之间更改的变量。

// Create CPU access texture
D3D11_TEXTURE2D_DESC copyTextureDesc;
copyTexture->GetDesc(&copyTextureDesc);

D3D11_TEXTURE2D_DESC textureDesc;
textureDesc.Width = copyTextureDesc.Width;
textureDesc.Height = copyTextureDesc.Height;
textureDesc.Format = copyTextureDesc.Format;
textureDesc.ArraySize = copyTextureDesc.ArraySize;
textureDesc.BindFlags = 0;
textureDesc.MiscFlags = 0;
textureDesc.SampleDesc = copyTextureDesc.SampleDesc;
textureDesc.MipLevels = copyTextureDesc.MipLevels;
textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_READ;
textureDesc.Usage = D3D11_USAGE::D3D11_USAGE_STAGING;

ID3D11Texture2D* stagingTexture = nullptr;
result = m_Device->CreateTexture2D(&textureDesc, nullptr, &stagingTexture);

这解决了我遇到的问题;我仍然不知道为什么在处理DXGI_ERROR_ACCESS_LOST时重新初始化仍然无法解决问题。 DesctopDuplicationDescription是否使用与 copyTexture 相同的尺寸和格式?

我也不知道为什么在配备更新显卡的计算机上没有以相同的方式失败。但是我确实注意到这些机器能够使用桌面表面的简单BitBlt()捕获全屏应用程序。