如何从仅GPU访问的IDXGISurface中提取像素数据?
问题:无法从Direct3D11捕获帧的图面获取CPU访问权限
需求与问题概述
需要实现屏幕(或窗口)捕获并转换为数字图像格式,计划步骤如下:
- 设置
Direct3D11CaptureFramePool并订阅FrameArrived事件 - 在事件委托中获取捕获帧的像素数据访问权限
- 将图像数据传入Windows Imaging Component(WIC)进行编码
问题出在第二步:捕获帧的图面无法通过IDXGISurface::Map获取CPU读取权限,调用返回E_INVALIDARG,调试层提示:
DXGI ERROR: IDXGISurface::Map: This object was not created with CPUAccess flags that allow CPU access. [ MISCELLANEOUS ERROR #42: ]
事件委托的错误实现代码:
void on_frame_arrived(Direct3D11CaptureFramePool const& frame_pool, winrt::Windows::Foundation::IInspectable const&) { if (auto const frame = frame_pool.TryGetNextFrame()) { if (auto const surface = frame.Surface()) { if (auto const interop = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>()) { com_ptr<IDXGISurface> dxgi_surface { nullptr }; check_hresult(interop->GetInterface(IID_PPV_ARGS(&dxgi_surface))); DXGI_MAPPED_RECT info = {}; // Fails with `E_INVALIDARG` check_hresult(dxgi_surface->Map(&info, DXGI_MAP_READ)); } } } }
解决方案:通过暂存纹理实现GPU到CPU的像素复制
捕获的帧图面是GPU专属资源,未启用CPU访问权限,因此需要通过Direct3D11的资源复制机制,将像素数据转移到带CPU访问权限的暂存纹理中,步骤如下:
- 保留D3D设备和即时上下文的全局引用(用于后续资源创建与复制)
- 将捕获到的DXGI图面转换为
ID3D11Texture2D - 创建匹配格式与大小的暂存纹理,设置
D3D11_USAGE_STAGING和D3D11_CPU_ACCESS_READ标志 - 使用
CopyResource将GPU帧纹理复制到暂存纹理 - 映射暂存纹理获取CPU可访问的像素数据
修改后的完整代码
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Graphics.Capture.h> #include <winrt/Windows.Graphics.DirectX.Direct3D11.h> #include <winrt/Windows.Graphics.DirectX.h> #include <Windows.Graphics.Capture.Interop.h> #include <windows.graphics.capture.h> #include <windows.graphics.directx.direct3d11.interop.h> #include <Windows.h> #include <d3d11.h> #include <dxgi.h> #include <cstdint> #include <stdio.h> #include <string> using namespace winrt; using namespace winrt::Windows::Graphics::Capture; using namespace winrt::Windows::Graphics::DirectX; using namespace winrt::Windows::Graphics::DirectX::Direct3D11; // 全局保存D3D设备和上下文,用于资源操作 com_ptr<ID3D11Device> g_d3d_device; com_ptr<ID3D11DeviceContext> g_d3d_context; void on_frame_arrived(Direct3D11CaptureFramePool const& frame_pool, winrt::Windows::Foundation::IInspectable const&) { wprintf(L"Frame arrived.\n"); if (auto const frame = frame_pool.TryGetNextFrame()) { if (auto const surface = frame.Surface()) { if (auto const interop = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>()) { com_ptr<IDXGISurface> dxgi_surface { nullptr }; check_hresult(interop->GetInterface(IID_PPV_ARGS(&dxgi_surface))); // 将DXGI图面转换为D3D11纹理 com_ptr<ID3D11Texture2D> src_texture; check_hresult(dxgi_surface.as(src_texture.put())); // 获取源纹理的描述信息 D3D11_TEXTURE2D_DESC src_desc{}; src_texture->GetDesc(&src_desc); // 创建带CPU读取权限的暂存纹理 D3D11_TEXTURE2D_DESC dst_desc = src_desc; dst_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; dst_desc.Usage = D3D11_USAGE_STAGING; // 暂存资源,用于GPU->CPU数据传输 dst_desc.BindFlags = 0; dst_desc.MiscFlags = 0; com_ptr<ID3D11Texture2D> dst_texture; check_hresult(g_d3d_device->CreateTexture2D(&dst_desc, nullptr, dst_texture.put())); // 从GPU帧纹理复制到暂存纹理 g_d3d_context->CopyResource(dst_texture.get(), src_texture.get()); // 映射暂存纹理,获取CPU可访问的像素数据 D3D11_MAPPED_SUBRESOURCE mapped{}; check_hresult(g_d3d_context->Map(dst_texture.get(), 0, D3D11_MAP_READ, 0, &mapped)); // 这里可以访问mapped.pData中的像素数据,mapped.RowPitch是每行字节数 wprintf(L"Successfully mapped texture, row pitch: %u\n", mapped.RowPitch); // 示例:读取第一个像素的BGRA值 uint32_t* first_pixel = static_cast<uint32_t*>(mapped.pData); wprintf(L"First pixel (BGRA): 0x%08X\n", *first_pixel); // 解除映射,释放资源 g_d3d_context->Unmap(dst_texture.get(), 0); } } } } int wmain(int argc, wchar_t const* argv[]) { init_apartment(apartment_type::single_threaded); // 验证输入参数 if (argc != 2) { wprintf(L"Usage: %s <HWND>\n", argv[0]); return 1; } auto const target = reinterpret_cast<HWND>(static_cast<intptr_t>(std::stoi(argv[1], nullptr, 0))); // 创建GraphicsCaptureItem auto interop = get_activation_factory<GraphicsCaptureItem, IGraphicsCaptureItemInterop>(); ::ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* capture_item_abi { nullptr }; check_hresult(interop->CreateForWindow(target, IID_PPV_ARGS(&capture_item_abi))); GraphicsCaptureItem const capture_item { capture_item_abi, take_ownership_from_abi }; // 创建D3D11设备,并获取设备上下文 check_hresult(::D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG, nullptr, 0, D3D11_SDK_VERSION, g_d3d_device.put(), nullptr, g_d3d_context.put())); auto dxgi_device = g_d3d_device.as<IDXGIDevice>(); // 创建WinRT兼容的IDirect3DDevice com_ptr<IInspectable> d3d_device_interop { nullptr }; check_hresult(::CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.get(), d3d_device_interop.put())); auto d3d_device = d3d_device_interop.as<IDirect3DDevice>(); // 创建捕获帧池和会话 auto const pool = Direct3D11CaptureFramePool::Create(d3d_device, DirectXPixelFormat::B8G8R8A8UIntNormalized, 1, capture_item.Size()); auto const session = pool.CreateCaptureSession(capture_item); [[maybe_unused]] auto const event_guard = pool.FrameArrived(auto_revoke, &on_frame_arrived); // 开始捕获 session.StartCapture(); // 等待用户停止捕获 ::MessageBoxW(nullptr, L"Click OK to stop capturing", L"Capturing...", MB_OK); }
关键说明
- 暂存纹理:
D3D11_USAGE_STAGING类型的资源专门用于GPU与CPU之间的数据传输,必须设置对应CPU访问标志 - 资源复制:
CopyResource只能在同一D3D设备的资源之间执行,因此必须确保捕获帧的纹理与暂存纹理属于同一设备 - 性能优化:如果需要持续捕获,建议预先创建匹配大小的暂存纹理,避免每次帧到来时重复创建;可监听
Direct3D11CaptureFramePool的SizeChanged事件,在目标窗口大小变化时重新创建纹理
内容的提问来源于stack exchange,提问作者IInspectable
相关产品推荐
相关产品推荐

