使用C#+DirectX11截图仅得到空Span的问题排查
使用Silk.NET.Direct3D11实现DX11截图时ReadOnlySpan为空的问题
我使用Silk.NET.Direct3D11库在C#中通过DirectX11实现截图功能,执行过程无报错,但最终得到的ReadOnlySpan<byte>为空。我尝试过创建中间Texture2D,先将desktopTexture复制到中间Texture2D,再复制到stagingResource的方案,问题仍未解决,相关代码如下:
//.... ID3D11Device* device=null; ID3D11DeviceContext* context=null; D3DFeatureLevel featureLevel=D3DFeatureLevel.Level101; D3DFeatureLevel[] featureLevels = [ D3DFeatureLevel.Level111, D3DFeatureLevel.Level110, D3DFeatureLevel.Level101,D3DFeatureLevel.Level100 ]; fixed (D3DFeatureLevel* pFeatureLevels = &featureLevels[0]) { D3D11 d3D11 = new D3D11(new DefaultNativeContext("d3d11")); if (d3D11.CreateDevice((IDXGIAdapter*)adapter1, D3DDriverType.Unknown, IntPtr.Zero, (uint)CreateDeviceFlag.None,pFeatureLevels,(uint)featureLevels.Length, D3D11.SdkVersion, ref device, &featureLevel, ref context)!=0){/*..*/} } ID3D11DeviceContext* immediateContext = null; device->GetImmediateContext(ref immediateContext); IDXGIOutputDuplication *outputDuplication = null; if (output5.DuplicateOutput((IUnknown*)device, ref outputDuplication)!=0){/*..*/} OutduplFrameInfo outduplFrameInfo = new OutduplFrameInfo(); IDXGIResource* desktopResource = null; if (outputDuplication->AcquireNextFrame(1000, &outduplFrameInfo, &desktopResource)!=0){/*..*/} if (desktopResource->QueryInterface<ID3D11Resource>(out var desktopTexture)!=0){/*..*/} Texture2DDesc stagingTextureDesc = new() { CPUAccessFlags = (uint)CpuAccessFlag.Read, BindFlags = (uint)(BindFlag.None), Format = Format.FormatB8G8R8A8Unorm, Width = (uint)desc.DesktopCoordinates.Size.X, Height = (uint)desc.DesktopCoordinates.Size.Y, MiscFlags = (uint)ResourceMiscFlag.None, MipLevels = 1, ArraySize = 1, SampleDesc = { Count = 1, Quality = 0 }, Usage = Usage.Staging }; ID3D11Texture2D* stagingTexture = null; if (device->CreateTexture2D(&stagingTextureDesc,null,ref stagingTexture )!=0){/*..*/} stagingTexture->QueryInterface<ID3D11Resource>(out var stagingResource); immediateContext->CopyResource(stagingResource, desktopTexture); MappedSubresource mappedSubresource=new MappedSubresource(); if (immediateContext->Map(stagingResource, 0, Map.Read, 0, &mappedSubresource)!=0){/*..*/} var span = new ReadOnlySpan<byte>(mappedSubresource.PData, (int)mappedSubresource.DepthPitch);
错误点及修复方案
1. 纹理格式不匹配导致复制失败
桌面复制得到的desktopTexture格式不一定是FormatB8G8R8A8Unorm,硬编码staging纹理格式会导致CopyResource静默失败(无报错但无数据复制)。
修复:
先获取桌面纹理的实际格式,再创建对应格式的staging纹理:
// 获取桌面纹理的描述 Texture2DDesc desktopDesc; ((ID3D11Texture2D*)desktopTexture)->GetDesc(&desktopDesc); // 基于桌面纹理格式创建staging纹理 Texture2DDesc stagingTextureDesc = new() { CPUAccessFlags = (uint)CpuAccessFlag.Read, BindFlags = 0, Format = desktopDesc.Format, // 使用桌面纹理的原生格式 Width = desktopDesc.Width, Height = desktopDesc.Height, MiscFlags = 0, MipLevels = 1, ArraySize = 1, SampleDesc = { Count = 1, Quality = 0 }, Usage = Usage.Staging };
2. Span长度计算错误
MappedSubresource.DepthPitch是深度纹理每个切片的字节数,对于普通2D纹理,应该用RowPitch(每行字节数)乘以高度得到总数据长度。直接用DepthPitch会导致Span长度错误,甚至为空。
修复:
计算正确的总字节数:
// 总字节数 = 每行字节数 × 纹理高度 int totalBytes = (int)(mappedSubresource.RowPitch * desktopDesc.Height); var span = new ReadOnlySpan<byte>(mappedSubresource.PData, totalBytes);
3. 未同步GPU异步操作
CopyResource是GPU异步操作,若在GPU完成复制前调用Map,会读取到未初始化的内存,表现为空数据。
修复:
添加GPU同步确保复制完成:
immediateContext->CopyResource(stagingResource, desktopTexture); // 创建栅栏同步GPU操作 ID3D11Fence* fence = null; device->CreateFence(0, (uint)FenceFlag.None, typeof(ID3D11Fence).GUID, (void**)&fence); immediateContext->Signal(fence, 1); // 等待GPU完成复制 fence->SetEventOnCompletion(1, IntPtr.Zero); // 释放栅栏资源 fence->Release();
4. 未正确释放COM资源(潜在问题)
未释放desktopResource、stagingTexture等COM资源可能导致后续操作异常,操作完成后需手动释放:
// 在读取数据完成后执行 immediateContext->Unmap(stagingResource, 0); desktopResource->Release(); desktopTexture->Release(); stagingTexture->Release(); stagingResource->Release(); outputDuplication->ReleaseFrame();
内容的提问来源于stack exchange,提问作者MakesYT
相关产品推荐
相关产品推荐

