3

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

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

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

在我自己的具有 GeForce GTX 1070 显卡的机器上;对于所有测试应用程序,在全屏和全屏外一切正常。然而,在另外两台运行 GeForce GTX 980 的机器上;除 PUBG 外的所有测试应用程序都有效。当 PUBG 全屏运行时,我的桌面复制会生成全黑图像,我无法弄清楚为什么 桌面复制示例适用于所有测试机器和测试应用程序。

我所做的与示例基本相同,只是我正在提取像素数据并从该数据中创建自己的 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。
  • 测试用例是有限的,因为我无法访问产生黑色图像案例的计算机。
4

1 回答 1

0

罪魁祸首是 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() 捕获全屏应用程序。

于 2018-08-06T08:21:56.110 回答