Рендеринг скриншота Windows с растровым изображением в виде DirectX

Я делаю успехи в разработке приложения DirectX для "3D-рабочего стола", которое должно отображать текущее содержимое окна рабочего стола (например, "Калькулятор") в виде 2D-текстуры на прямоугольной поверхности в DirectX (11). Я ооочень близок, но действительно борюсь со скриншотом BMP -> Texture2D step. У меня действительно есть скриншот->HBITMAP и DDSFile-> визуализированная текстура, успешно работающая, но я не могу завершить скриншот-> визуализированная текстура.

Пока у меня есть бит "захватить окно как скриншот":

RECT user_window_rectangle;
HWND user_window = FindWindow(NULL, TEXT("Calculator"));
GetClientRect(user_window, &user_window_rectangle);
HDC hdcScreen = GetDC(NULL);
HDC hdc = CreateCompatibleDC(hdcScreen);
UINT screenshot_width = user_window_rectangle.right - user_window_rectangle.left;
UINT screenshot_height = user_window_rectangle.bottom - user_window_rectangle.top;
hbmp = CreateCompatibleBitmap(hdcScreen, screenshot_width, screenshot_height);
SelectObject(hdc, hbmp);
PrintWindow(user_window, hdc, PW_CLIENTONLY);

На данный момент у меня есть битмап окна, на который ссылается HBITMAP hbmp.

Также работает мой код для визуализации файла DDS в виде текстуры на прямоугольнике DirectX /3D:

ID3D11Device *dev;
ID3D11DeviceContext *dev_context;
...
dev_context->PSSetShaderResources(0, 1, &shader_resource_view);
dev_context->PSSetSamplers(0, 1, &tex_sampler_state);
...
DirectX::TexMetadata tex_metadata;
DirectX::ScratchImage image;

hr = LoadFromDDSFile(L"Earth.dds", DirectX::DDS_FLAGS_NONE, &tex_metadata, image);
hr = CreateShaderResourceView(dev, image.GetImages(), image.GetImageCount(), tex_metadata, &shader_resource_view);

Пиксельный шейдер это:

Texture2D ObjTexture
SamplerState ObjSamplerState
float4 PShader(float4 pos : SV_POSITION, float4 color : COLOR, float2 tex : TEXCOORD) : SV_TARGET\
{
    return ObjTexture.Sample( ObjSamplerState, tex );
}

Состояние сэмплера (по умолчанию линейное):

D3D11_SAMPLER_DESC sampler_desc;
ZeroMemory(&sampler_desc, sizeof(sampler_desc));
sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.MinLOD = 0;
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;

hr = dev->CreateSamplerState(&sampler_desc, &tex_sampler_state);

Вопрос: как заменить бит LoadFromDDSFile на какой-нибудь эквивалент, который берет HBITMAP из снимка экрана Windows и в конечном итоге выводит его на графическую карту как ObjTexture?

Ниже мой лучший снимок моста от скриншота HBITMAP hbmp к ресурсу шейдера screenshot_texture, но он дает нарушение доступа к памяти со стороны графического драйвера (я думаю, из-за моих data.pSysmem = &bmp.bmBits, но на самом деле не знаю):

GetObject(hbmp, sizeof(BITMAP), (LPSTR)&bmp)

D3D11_TEXTURE2D_DESC screenshot_desc = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R8G8B8A8_UNORM, bmp.bmWidth, bmp.bmHeight, 1,
    1,
    D3D11_BIND_SHADER_RESOURCE
    );

int bytes_per_pixel = 4;

D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(D3D11_SUBRESOURCE_DATA));
data.pSysMem = &bmp.bmBits; //pixel buffer
data.SysMemPitch = bytes_per_pixel * bmp.bmWidth;// line size in byte
data.SysMemSlicePitch = bytes_per_pixel * bmp.bmWidth * bmp.bmHeight;// total buffer size in byte

hr = dev->CreateTexture2D(
    &screenshot_desc, //texture format
    &data,          // pixel buffer use to fill the texture
    &screenshot_texture  // created texture
    );

:::::::::::::::::::::::::РЕШЕНИЕ::::::::::::::::::::::::::::::::::::::::::

Основная проблема заключалась в попытке использовать & bmp.bmBits напрямую, так как пиксельный буфер вызывал конфликты памяти в графическом драйвере - это было решено с помощью 'malloc' для выделения блока памяти соответствующего размера для хранения пиксельных данных. Спасибо Чаку Уолбурну за помощь в том, чтобы разобраться в темноте и выяснить, как на самом деле хранятся данные пикселей (по умолчанию они были 32 бит / пиксель). Это все еще возможно / вероятно, часть кода полагается на удачу, чтобы правильно прочитать данные пикселей, но это было улучшено с помощью ввода Чака.

Моя основная техника была;

  • FindWindow, чтобы получить окно клиента на рабочем столе
  • CreateCompatibleBitmap и SelectObject и PrintWindow, чтобы получить HBITMAP для снимка
  • malloc для выделения правильного объема пространства для (байтового *) пиксельного буфера
  • GetDIBits для заполнения пиксельного буфера (байт *) из HBITMAP
  • CreateTexture2D для построения буфера текстуры
  • CreateShaderResourceView для сопоставления текстуры с графическим пиксельным шейдером

Итак, рабочий код для скриншота окна рабочего стола Windows и передачи его в качестве текстуры в приложение direct3d:

RECT user_window_rectangle;

HWND user_window = FindWindow(NULL, TEXT("Calculator"));    //the window can't be min
if (user_window == NULL)
{
    MessageBoxA(NULL, "Can't find Calculator", "Camvas", MB_OK);
    return;
}
GetClientRect(user_window, &user_window_rectangle);
//create
HDC hdcScreen = GetDC(NULL);
HDC hdc = CreateCompatibleDC(hdcScreen);
UINT screenshot_width = user_window_rectangle.right - user_window_rectangle.left;
UINT screenshot_height = user_window_rectangle.bottom - user_window_rectangle.top;
hbmp = CreateCompatibleBitmap(hdcScreen, screenshot_width, screenshot_height);

SelectObject(hdc, hbmp);

//Print to memory hdc
PrintWindow(user_window, hdc, PW_CLIENTONLY);

BITMAPINFOHEADER bmih;
ZeroMemory(&bmih, sizeof(BITMAPINFOHEADER));
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biWidth = screenshot_width;
bmih.biHeight = 0-screenshot_height;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0;

int bytes_per_pixel = bmih.biBitCount / 8;

BYTE *pixels = (BYTE*)malloc(bytes_per_pixel * screenshot_width * screenshot_height);

BITMAPINFO bmi = { 0 };
bmi.bmiHeader = bmih;

int row_count = GetDIBits(hdc, hbmp, 0, screenshot_height, pixels, &bmi, DIB_RGB_COLORS);

D3D11_TEXTURE2D_DESC screenshot_desc = CD3D11_TEXTURE2D_DESC(
    DXGI_FORMAT_B8G8R8A8_UNORM,     // format
    screenshot_width,               // width
    screenshot_height,              // height
    1,                              // arraySize
    1,                              // mipLevels
    D3D11_BIND_SHADER_RESOURCE,     // bindFlags
    D3D11_USAGE_DYNAMIC,            // usage
    D3D11_CPU_ACCESS_WRITE,         // cpuaccessFlags
    1,                              // sampleCount
    0,                              // sampleQuality
    0                               // miscFlags
    );

D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(D3D11_SUBRESOURCE_DATA));
data.pSysMem = pixels; // texArray; // &bmp.bmBits; //pixel buffer
data.SysMemPitch = bytes_per_pixel * screenshot_width;// line size in byte
data.SysMemSlicePitch = bytes_per_pixel * screenshot_width * screenshot_height;

hr = dev->CreateTexture2D(
    &screenshot_desc, //texture format
    &data,          // pixel buffer use to fill the texture
    &screenshot_texture  // created texture
    );

D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
srvDesc.Format = screenshot_desc.Format;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MostDetailedMip = screenshot_desc.MipLevels;

dev->CreateShaderResourceView(screenshot_texture, NULL, &shader_resource_view);

2 ответа

Решение

Основная проблема заключалась в попытке использовать &bmp.bmBits напрямую, так как пиксельный буфер вызывал конфликты памяти в графическом драйвере - это было решено с помощью 'malloc' для выделения блока памяти соответствующего размера для хранения пиксельных данных. Спасибо Чаку Уолбурну за помощь в том, чтобы разобраться в темноте и выяснить, как на самом деле хранятся данные пикселей (по умолчанию они были 32 бит / пиксель). Это все еще возможно / вероятно, часть кода полагается на удачу, чтобы правильно прочитать данные пикселей, но это было улучшено с помощью ввода Чака.

Моя основная техника была;

  1. FindWindow, чтобы получить окно клиента на рабочем столе
  2. CreateCompatibleBitmap и SelectObject и PrintWindow, чтобы получить HBITMAP для снимка
  3. malloc для выделения правильного объема пространства для (байтового *) пиксельного буфера
  4. GetDIBits для заполнения пиксельного буфера (байт *) из HBITMAP
  5. CreateTexture2D для построения буфера текстуры
  6. CreateShaderResourceView для сопоставления текстуры с графическим пиксельным шейдером

Итак, рабочий код для скриншота окна рабочего стола Windows и передачи его в качестве текстуры в приложение direct3d:

RECT user_window_rectangle;

HWND user_window = FindWindow(NULL, TEXT("Calculator"));    //the window can't be min
if (user_window == NULL)
{
    MessageBoxA(NULL, "Can't find Calculator", "Camvas", MB_OK);
    return;
}
GetClientRect(user_window, &user_window_rectangle);
//create
HDC hdcScreen = GetDC(NULL);
HDC hdc = CreateCompatibleDC(hdcScreen);
UINT screenshot_width = user_window_rectangle.right - user_window_rectangle.left;
UINT screenshot_height = user_window_rectangle.bottom - user_window_rectangle.top;
hbmp = CreateCompatibleBitmap(hdcScreen, screenshot_width, screenshot_height);

SelectObject(hdc, hbmp);

//Print to memory hdc
PrintWindow(user_window, hdc, PW_CLIENTONLY);

BITMAPINFOHEADER bmih;
ZeroMemory(&bmih, sizeof(BITMAPINFOHEADER));
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biWidth = screenshot_width;
bmih.biHeight = 0-screenshot_height;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0;

int bytes_per_pixel = bmih.biBitCount / 8;

BYTE *pixels = (BYTE*)malloc(bytes_per_pixel * screenshot_width * screenshot_height);

BITMAPINFO bmi = { 0 };
bmi.bmiHeader = bmih;

int row_count = GetDIBits(hdc, hbmp, 0, screenshot_height, pixels, &bmi, DIB_RGB_COLORS);

D3D11_TEXTURE2D_DESC screenshot_desc = CD3D11_TEXTURE2D_DESC(
    DXGI_FORMAT_B8G8R8A8_UNORM,     // format
    screenshot_width,               // width
    screenshot_height,              // height
    1,                              // arraySize
    1,                              // mipLevels
    D3D11_BIND_SHADER_RESOURCE,     // bindFlags
    D3D11_USAGE_DYNAMIC,            // usage
    D3D11_CPU_ACCESS_WRITE,         // cpuaccessFlags
    1,                              // sampleCount
    0,                              // sampleQuality
    0                               // miscFlags
    );

D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(D3D11_SUBRESOURCE_DATA));
data.pSysMem = pixels; // texArray; // &bmp.bmBits; //pixel buffer
data.SysMemPitch = bytes_per_pixel * screenshot_width;// line size in byte
data.SysMemSlicePitch = bytes_per_pixel * screenshot_width * screenshot_height;

hr = dev->CreateTexture2D(
    &screenshot_desc, //texture format
    &data,          // pixel buffer use to fill the texture
    &screenshot_texture  // created texture
    );

D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
srvDesc.Format = screenshot_desc.Format;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MostDetailedMip = screenshot_desc.MipLevels;

dev->CreateShaderResourceView(screenshot_texture, NULL, &shader_resource_view);

Здесь вы делаете много предположений, что BITMAP возвращается в 32-битной форме RGBA. Скорее всего, не в этом формате, и в любом случае вам необходимо проверить содержание bmPlanes быть 1 и bmBitsPixel быть 32, если вы предполагаете, что это 4 байта на пиксель. Вы должны прочитать больше о формате BMP.

BMP использует порядок BGRA, так что вы можете использовать DXGI_FORMAT_B8G8R8A8_UNORM для случая bmBitsPixel быть 32.

Во-вторых, вам нужно извлечь шаг из bmWidthBytes и не bmWidth,

data.pSysMem = &bmp.bmBits; //pixel buffer
data.SysMemPitch = bmp.bmWidthBytes;// line size in byte
data.SysMemSlicePitch = bmp.bmWidthBytes * bmp.bmHeight;// total buffer size in byte

Если bmBitsPixel 24, нет формата DXGI, эквивалентного этому. Вы должны скопировать данные в 32-битный формат, такой как DXGI_FORMAT_B8G8R8X8_UNORM,

Если bmBitsPixel 15 или 16, вы можете использовать DXGI_FORMAT_B5G5R5A1_UNORM в системе с Direct3D 11.1, но помните, что 16-битные форматы DXGI не всегда поддерживаются в зависимости от драйвера. В противном случае вам придется преобразовать эти данные во что-то другое.

За bmBitsPixel Значения 1, 2, 4 или 8 необходимо преобразовать, поскольку отсутствуют форматы текстур DXGI, которые являются эквивалентными.

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