Vortice.DXGI桌面区域捕获 矩形X/Y非0时返回黑位图问题
问题原因
非0偏移捕获返回黑帧是3个实现逻辑错误共同导致的:
- DXGI Desktop Duplication接口返回的桌面纹理始终是覆盖整个显示器输出的全尺寸纹理,原点固定为屏幕左上角(0,0)。原代码使用
CopyResource做全纹理拷贝,没有对目标捕获区域做裁剪,后续内存拷贝的偏移计算完全不匹配。 - 原代码内存拷贝逻辑的地址计算错误:
LockBits传入带X/Y偏移的矩形时,目标位图的扫描线起始地址、每行拷贝的步长计算没有对应偏移,当X/Y不为0时会读到显存中未初始化的黑色区域,甚至触发内存越界。 - 原代码创建的staging纹理、输出Bitmap均为全屏尺寸,做局部捕获时完全浪费资源,且
Rectangle结构体的Width/Height属性和Right/Bottom属性混用,循环边界判断错误。
修复方案
按以下步骤调整代码即可解决偏移捕获黑帧问题:
- 移除循环内硬编码的
rectangle.X = 0逻辑,单独定义捕获区域变量,明确区分全屏尺寸和目标捕获区域的X/Y/Width/Height参数。 - 把全量拷贝方法
CopyResource替换为区域拷贝方法CopySubresourceRegion,直接从DXGI返回的全屏桌面纹理中,截取指定偏移、指定大小的区域到staging纹理,staging纹理尺寸设置为和捕获区域一致即可,无需创建全屏大小的staging纹理。 - 修正内存拷贝的步长、地址计算逻辑,输出Bitmap尺寸和捕获区域尺寸完全匹配,移除多余的偏移计算,避免内存错位。
核心修复代码
public void Start() { _isCapturing = true; DXGI.CreateDXGIFactory1<IDXGIFactory1>(out var factory); if (factory == null) return; var adapter = factory.GetAdapter(0); var output = adapter.GetOutput(0); var output1 = output.QueryInterface<IDXGIOutput1>(); D3D11.D3D11CreateDevice(adapter, DriverType.Unknown, DeviceCreationFlags.None, _featureLevels, out var device); if (device == null) throw new Exception("Unable to Locate Device."); // 全屏尺寸 var desktopBounds = output.Description.DesktopCoordinates; int desktopWidth = desktopBounds.Right - desktopBounds.Left; int desktopHeight = desktopBounds.Bottom - desktopBounds.Top; // 自定义捕获区域,可自行修改X/Y/Width/Height var captureRect = new Rectangle(100, 100, 500, 500); // Staging纹理尺寸和捕获区域一致,无需创建全屏大小 var texture2dDescription = new Texture2DDescription { ArraySize = 1, BindFlags = BindFlags.None, CPUAccessFlags = CpuAccessFlags.Read, Format = Format.B8G8R8A8_UNorm, Height = captureRect.Height, MipLevels = 1, SampleDescription = { Count = 1, Quality = 0 }, Usage = ResourceUsage.Staging, Width = captureRect.Width }; Task.Factory.StartNew(() => { using var duplicatedOutput = output1.DuplicateOutput(device); while (_isCapturing) { try { Thread.Sleep(50); duplicatedOutput.AcquireNextFrame(100, out var frameInfo, out var desktopResource); if (desktopResource == null) continue; using var tempTexture = desktopResource.QueryInterface<ID3D11Texture2D>(); using var currentFrame = device.CreateTexture2D(texture2dDescription); // 用区域拷贝代替全量拷贝,指定源纹理的拷贝起始偏移 device.ImmediateContext.CopySubresourceRegion( currentFrame, 0, 0, 0, 0, tempTexture, 0, new ResourceRegion(captureRect.X, captureRect.Y, 0, captureRect.Right, captureRect.Bottom, 1)); var dataBox = device.ImmediateContext.Map(currentFrame, 0, MapMode.Read, MapFlags.None); var frame = new Bitmap(captureRect.Width, captureRect.Height, PixelFormat.Format32bppRgb); var mapDest = frame.LockBits(new Rectangle(0,0,captureRect.Width,captureRect.Height), ImageLockMode.WriteOnly, frame.PixelFormat); int bytesPerPixel = 4; int copyStride = captureRect.Width * bytesPerPixel; for (int y = 0; y < captureRect.Height; y++) { MemoryHelpers.CopyMemory( mapDest.Scan0 + y * mapDest.Stride, dataBox.DataPointer + y * dataBox.RowPitch, copyStride); } frame.UnlockBits(mapDest); device.ImmediateContext.Unmap(currentFrame, 0); ScreenRefreshed?.Invoke(this, frame); frame.Dispose(); } catch (Exception e) { if (e.HResult != Vortice.DXGI.ResultCode.WaitTimeout.Code) { Trace.TraceError(e.Message); Trace.TraceError(e.StackTrace); } } finally { duplicatedOutput.ReleaseFrame(); } } }); }
注意:如果你的显示器开启了DPI缩放,需要先把进程设为Per-Monitor DPI Aware,否则捕获的偏移坐标会和实际屏幕坐标不匹配,同样会出现捕获内容异常的问题。
内容的提问来源于stack exchange,提问作者Jon Huthsing
相关产品推荐
相关产品推荐

