【问题标题】:DesktopDuplication API produces black frames while certain applications are in fullscreen modeDesktopDuplication API 在某些应用程序处于全屏模式时产生黑框
【发布时间】:2018-07-31 12:55:40
【问题描述】:

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

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

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

在我自己的具有 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。
  • 测试用例有限,因为我无法使用生成黑色图像用例的计算机。

【问题讨论】:

  • 潜在原因:你永远不会释放copyTexture
  • 我不认为这会导致这样的问题,但还是不错的!我现在已经修好了!还将进行另一次测试运行以取得良好的效果:)
  • 是的,我现在测试了它,它在第二次调用 AquireNextFrame 时导致了 E_INVALIDARG。它还引发了某种 COM 对象异常。示例代码似乎会保留该对象,直到他们调用下一个 AcquireFrame 之后,所以我可能会在明天尝试。我以为他们这样做只是为了以后得到 rects。
  • 这些错误与comptrs有关。我重新编写了代码以避免它们并且错误消失了。我现在可以发布 copyTexture 而不会产生任何后果,但正如预期的那样,它并没有解决问题。不过还是谢谢:)

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


【解决方案1】:

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

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

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

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

由于初始化代码在测试应用程序进入全屏之前运行,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() 捕获全屏应用程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-10
    • 2022-07-19
    • 2014-10-07
    • 1970-01-01
    • 1970-01-01
    • 2022-12-07
    • 2019-06-27
    相关资源
    最近更新 更多