Я создаю приложение, которое используется для создания и обмена скриншотами в режиме реального времени между несколькими клиентами по сети.
Я использую API дублирования рабочего стола MS чтобы получить данные изображения, и он работает гладко, за исключением некоторых крайних случаев.
Я использовал четыре игры в качестве тестовых приложений, чтобы проверить, как снимок экрана ведет себя в полноэкранном режиме. Это Герои Шторма, Радуга Шести Осада, 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://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**>(©Texture));
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 () и то, как я создал текстуру доступа к процессору.
CopyResource () возвращает void, и поэтому я не смотрел на это раньше; Я не думал, что это может потерпеть неудачу каким-либо существенным образом, так как я ожидал, что он вернет bool или HRESULT, если это так.
Однако в документации CopyResource () раскрыто несколько случаев сбоя.
Этот метод необычен тем, что заставляет графический процессор выполнять операцию копирования (аналогично memcpy для процессора). В результате у него есть несколько ограничений, предназначенных для повышения производительности. Например, исходный и целевой ресурсы:
- Должны быть разные ресурсы.
- Должно быть того же типа.
- Должны иметь одинаковые размеры (включая ширину, высоту, глубину и размер в зависимости от ситуации).
- Должны иметь совместимые форматы DXGI, что означает, что форматы должны быть идентичными или, по крайней мере, из одной группы типов.
- В настоящее время не может быть сопоставлено.
Поскольку код инициализации запускается до того, как тестовое приложение переходит в полноэкранный режим, описание текстуры доступа к ЦП настраивается с использованием разрешения рабочего стола, формата и т. Д. Это приводило к автоматическому сбою CopyResouce () и простой записи чего-либо в stagingTexture в тестовых случаях, когда для тестового приложения использовалось нестандартное разрешение.
В заключение; Я только что переместил m_TextureDescription установить CaptureScreen () и использовать описание copyTexture чтобы получить переменные, которые я не хотел менять между текстурами.
// Create CPU access texture
D3D11_TEXTURE2D_DESC copyTextureDesc;
copyTexture->GetDesc(©TextureDesc);
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 () на поверхности рабочего стола.
Других решений пока нет …