DesktopDuplication API создает черные рамки, в то время как некоторые приложения находятся в полноэкранном режиме

Я создаю приложение, которое используется для создания и обмена скриншотами в режиме реального времени между несколькими клиентами по сети.

Я использую MS Desktop Duplication API для получения данных изображения, и он работает гладко, за исключением некоторых крайних случаев.

Я использовал четыре игры в качестве тестовых приложений, чтобы проверить, как снимок экрана ведет себя в полноэкранном режиме. Это Герои Шторма, Радуга Шести Осада, Counter Strike и PlayerUnknown's Battlegrounds.

На моей машине с видеокартой GeForce GTX 1070; все отлично работает как в полноэкранном режиме, так и вне его для всех тестовых приложений. Однако на двух других машинах, на которых установлен GeForce GTX 980; все тестовые приложения кроме PUBG работает. Когда PUBG работает в полноэкранном режиме, мое дублирование на рабочем столе вместо этого создает полностью черное изображение, и я не могу понять, почему, поскольку Образец дублирования рабочего стола работает нормально для всех тестовых компьютеров и тестовых приложений.

То, что я делаю, в основном то же самое, что и пример, за исключением того, что я извлекаю пиксельные данные и создаю свою собственную текстуру 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 (https://stackru.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) не запускается ни в одном сценарии тестирования.
  • В любом сценарии тестирования "tryupounter" никогда не превышает 2.
  • Тестовые случаи ограничены, так как у меня нет доступа к компьютеру, который создает черный корпус изображения.

1 ответ

Решение

Виновником был CopyResource() и то, как я создал текстуру доступа к процессору. CopyResource() возвращает void, и поэтому я не смотрел на это раньше; Я не думал, что это может потерпеть неудачу каким-либо существенным образом, так как я ожидал, что он вернет bool или HRESULT, если это так

Однако в документации CopyResource() раскрыто несколько случаев сбоя.

Этот метод необычен тем, что заставляет графический процессор выполнять операцию копирования (аналогично memcpy для процессора). В результате у него есть несколько ограничений, предназначенных для повышения производительности. Например, исходный и целевой ресурсы:

  • Должны быть разные ресурсы.
  • Должно быть того же типа.
  • Должны иметь одинаковые размеры (включая ширину, высоту, глубину и размер в зависимости от ситуации).
  • Должны иметь совместимые форматы DXGI, что означает, что форматы должны быть идентичными или, по крайней мере, из одной группы типов.
  • В настоящее время не может быть сопоставлено.

Так как код инициализации запускается до того, как тестовое приложение переходит в полноэкранный режим, описание текстуры доступа к ЦП настраивается с использованием разрешения рабочего стола, формата и т. Д. Это приводило к тому, что 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() на поверхности рабочего стола.

Другие вопросы по тегам