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

DesktopDuplication API produces black frames while certain applications are in fullscreen mode

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

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

我一直在使用四款游戏作为测试应用程序,以测试屏幕截图在全屏下的表现,它们是风暴英雄、彩虹六号围攻、反恐精英和绝地求生。

在我自己的机器上,有 GeForce GTX 1070 显卡;对于所有测试应用程序,无论是全屏还是全屏,一切都正常。然而,在另外两台运行 GeForce GTX 980 的机器上;除 PUBG 外的所有测试应用程序均有效。当 PUBG 全屏 运行 时,我的桌面复制反而产生全黑图像,我不明白为什么 Desktop Duplication Sample 适用于所有测试机器和测试应用程序。

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

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

我获取帧的方式、处理 "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 (

    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;
}

一些注意事项:

罪魁祸首是 CopyResource() 以及我如何创建 CPU 访问纹理。 CopyResource() return 无效,这就是我之前没有研究它的原因;我认为它不会以任何重大方式失败,因为我预计它会 return bool 或 HRESULT 如果是这样的话。

然而,在 CopyResource() 的文档中确实披露了几个失败案例。

This method is unusual in that it causes the GPU to perform the copy operation (similar to a memcpy by the CPU). As a result, it has a few restrictions designed for improving performance. For instance, the source and destination resources:

  • Must be different resources.
  • Must be the same type.
  • Must have identical dimensions (including width, height, depth, and size as appropriate).
  • Must have compatible DXGI formats, which means the formats must be identical or at least from the same type group.
  • Can't be currently mapped.

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

总而言之;我只是将 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() 捕获全屏应用程序。