You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从仅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访问权限的暂存纹理中,步骤如下:

  1. 保留D3D设备和即时上下文的全局引用(用于后续资源创建与复制)
  2. 将捕获到的DXGI图面转换为ID3D11Texture2D
  3. 创建匹配格式与大小的暂存纹理,设置D3D11_USAGE_STAGING和D3D11_CPU_ACCESS_READ标志
  4. 使用CopyResource将GPU帧纹理复制到暂存纹理
  5. 映射暂存纹理获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:01:01