将Windows截图位图渲染为DirectX纹理

8
我正在开发一个“3D桌面”DirectX应用程序,需要在DirectX(11)上将桌面窗口(例如“计算器”)的当前内容显示为2D纹理,放置在矩形表面上。我已经接近成功,但是在截图BMP-> Texture2D步骤中遇到了困难。 我已经成功地实现了截图-> HBITMAP和DDS文件->渲染纹理,但无法完成截图->渲染纹理的步骤。
到目前为止,我已经完成了“捕获窗口截图”的部分。
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引用的窗口位图。

我的代码也能够在DirectX/3D矩形上呈现DDS文件作为纹理:

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位,使其从Windows截屏中获取HBITMAP,并将其转换为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'来分配一个适当大小的内存块来存储像素数据来解决。感谢Chuck Walbourn帮助我摸索出像素数据实际上是如何存储的(默认情况下实际上是32位/像素)。仍然有可能/很可能一些代码依赖于幸运来正确读取像素数据,但是随着Chuck的输入,它已经得到了改进。

我的基本技术是;

  • FindWindow以获取桌面上的客户端窗口
  • CreateCompatibleBitmapSelectObjectPrintWindow以获取HBITMAP以进行快照
  • malloc以分配正确数量的空间给(byte*)像素缓冲区
  • GetDIBits将(byte*)像素缓冲区从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个回答

2
主要问题是尝试直接使用&bmp.bmBits作为像素缓冲区会导致图形驱动程序内存冲突 - 通过使用“malloc”来分配适当大小的内存块来存储像素数据解决了这个问题。感谢Chuck Walbourn帮助我摸索出像素数据实际存储的方式(默认情况下实际上是32位/像素)。仍然可能/很可能一些代码依赖于幸运才能正确读取像素数据,但Chuck的输入已经改进了它。
我的基本技术是:
  1. 使用FindWindow在桌面上获取客户端窗口
  2. 使用CreateCompatibleBitmap和SelectObject和PrintWindow获取HBITMAP以进行快照
  3. 使用malloc为(byte*)像素缓冲区分配正确的空间
  4. 使用GetDIBits从HBITMAP填充(byte*)像素缓冲区
  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);

1
你有没有任何想法如何在DirectX 9中实现它? - alDiablo

2
你在这里做了许多假设,即返回的位图实际上是32位RGBA格式。它很可能根本不是那种格式,而且无论如何,您需要验证bmPlanes的内容为1,bmBitsPixel为32,如果您假定每像素占用4个字节。您应该阅读更多关于BMP格式的信息。
BMP使用BGRA顺序,因此如果bmBitsPixel为32,则可以使用DXGI_FORMAT_B8G8R8A8_UNORM
其次,您需要从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,则可以在具有 Direct3D 11.1 的系统上使用 DXGI_FORMAT_B5G5R5A1_UNORM,但请记住,取决于驱动程序,不一定始终支持16位 DXGI 格式。否则,您必须将此数据转换为其他格式。
对于 bmBitsPixel 值为 1、2、4 或 8,您必须进行转换,因为没有等效的 DXGI 纹理格式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接