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

时间:2014-11-26 13:49:38

标签: windows directx textures screenshot direct3d

我正在开发一个3D桌面' directx app需要在directx(11)的矩形表面上显示桌面窗口的当前内容(例如" Calculator")作为2D纹理。我很近,但真的很难用屏幕截图BMP - > Texture2D步骤。我有截图 - > 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 );
}

samplerstate(默认为线性)是:

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
    );

::::::::::::::::::::::::: SOLUTION :::::::::::::::::: ::::::::::::::::::::::::

主要问题是尝试直接使用& bmp.bmBits作为像素缓冲区导致图形驱动程序内存冲突 - 这通过使用' malloc'分配适当大小的存储器块来存储像素数据。感谢 Chuck Walbourn 帮助我在黑暗中探索如何实际存储像素数据(默认情况下它实际上是32位/像素)。仍有可能/可能有些代码依靠运气来正确读取像素数据,但它已经通过Chuck的输入得到了改进。

我的基本技术是;

  • FindWindow 以在桌面上获取客户端窗口
  • CreateCompatibleBitmap SelectObject PrintWindow 以获取HBITMAP到快照
  • malloc 为(byte *)像素缓冲区分配正确的空间量
  • GetDIBits 从HBITMAP填充(byte *)像素缓冲区
  • 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 个答案:

答案 0 :(得分:1)

主要问题是尝试直接使用& 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将纹理映射到图形像素着色器
  7. 因此,使用代码截取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 :(得分:0)

你在这里做了很多假设,BITMAP返回的实际上是32位RGBA形式。它可能根本不是那种格式,并且在任何情况下,如果假设每个像素为4个字节,则需要验证bmPlanes的内容为1和bmBitsPixel为32。您应该阅读有关BMP格式的更多信息。

BMP使用BGRA订单,因此DXGI_FORMAT_B8G8R8A8_UNORMbmBitsPixel时,您可以使用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

如果DXGI_FORMAT_B8G8R8X8_UNORM为24,则没有与之相当的DXGI格式。您必须将数据复制为32位格式,例如bmBitsPixel

如果DXGI_FORMAT_B5G5R5A1_UNORM为15或16,则可以在具有Direct3D 11.1的系统上使用bmBitsPixel,但请记住,根据驱动程序,并不总是支持16位DXGI格式。否则,您必须将此数据转换为其他内容。

对于{{1}}值1,2,4或8,您必须转换它们,因为没有相同的DXGI纹理格式。