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

Vortice.DXGI桌面区域捕获 矩形X/Y非0时返回黑位图问题

问题原因

非0偏移捕获返回黑帧是3个实现逻辑错误共同导致的:

  1. DXGI Desktop Duplication接口返回的桌面纹理始终是覆盖整个显示器输出的全尺寸纹理,原点固定为屏幕左上角(0,0)。原代码使用CopyResource做全纹理拷贝,没有对目标捕获区域做裁剪,后续内存拷贝的偏移计算完全不匹配。
  2. 原代码内存拷贝逻辑的地址计算错误:LockBits传入带X/Y偏移的矩形时,目标位图的扫描线起始地址、每行拷贝的步长计算没有对应偏移,当X/Y不为0时会读到显存中未初始化的黑色区域,甚至触发内存越界。
  3. 原代码创建的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:54:05