为什么使用 DXGI 输出复制和 Direct3D 时捕获的图像数据全为零?

问题描述 投票:0回答:1

我正在开发一个应用程序,使用 DXGI 输出复制和 C# 中的 Direct3D 从桌面捕获屏幕截图。我的应用程序初始化 DXGI、创建 Direct3D 设备并复制输出。它捕获帧,但捕获的图像数据全为零。下面是我的应用程序的完整代码,旨在将屏幕截图保存到 PNG 文件:

using Silk.NET.Core.Native;
using Silk.NET.Direct3D11;
using Silk.NET.DXGI;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace TestApp;

public class Program
{
    public const int DXGI_ERROR_NOT_FOUND = unchecked((int)0x887A0002);

    public unsafe static void Main()
    {
        Console.WriteLine("Initializing DXGI...");

        using var dxgi = DXGI.GetApi();
        using var factory = dxgi.CreateDXGIFactory1<IDXGIFactory1>();

        Console.WriteLine("DXGI Factory created.");

        Console.WriteLine("Enumerating adapters and finding active outputs...");

        uint adapterIndex = 0;
        IDXGIAdapter1* adapter = null;
        IDXGIOutput* activeOutput = null;

        var foundActiveOutput = false;

        while (!foundActiveOutput && factory.EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND)
        {
            AdapterDesc1 desc;
            adapter->GetDesc1(&desc);

            var adapterDescription = Marshal.PtrToStringUni((nint)desc.Description);

            Console.WriteLine($"Adapter {adapterIndex}: {adapterDescription}, VendorId: {desc.VendorId}, DeviceId: {desc.DeviceId}, SubSysId: {desc.SubSysId}, Revision: {desc.Revision}");

            using var adapterCom = new ComPtr<IDXGIAdapter1>(adapter);
            uint outputIndex = 0;
            IDXGIOutput* output = null;

            while (!foundActiveOutput && adapter->EnumOutputs(outputIndex, &output) != DXGI_ERROR_NOT_FOUND)
            {
                using var outputCom = new ComPtr<IDXGIOutput>(output);

                OutputDesc outputDesc;
                output->GetDesc(&outputDesc);

                var outputName = Marshal.PtrToStringUni((nint)outputDesc.DeviceName);

                Console.WriteLine($"Output {outputIndex} on Adapter {adapterIndex}: {outputName}, Attached to Desktop: {(bool)outputDesc.AttachedToDesktop}, Rotation: {outputDesc.Rotation}");

                if (outputDesc.AttachedToDesktop)
                {
                    Console.WriteLine($"Active output found on adapter {adapterIndex}, resolution: {outputDesc.DesktopCoordinates.Max.X - outputDesc.DesktopCoordinates.Min.X} x {outputDesc.DesktopCoordinates.Max.Y - outputDesc.DesktopCoordinates.Min.Y}");

                    activeOutput = output;
                    foundActiveOutput = true;
                }

                outputIndex++;
            }

            adapterIndex++;
        }

        if (!foundActiveOutput)
        {
            Console.WriteLine("No active output found.");

            return;
        }

        using var activeOutputCom = new ComPtr<IDXGIOutput>(activeOutput);

        OutputDesc activeDesc;
        activeOutput->GetDesc(&activeDesc);

        Console.WriteLine($"Selected Output: {Marshal.PtrToStringUni((nint)activeDesc.DeviceName)}, Total Size: {activeDesc.DesktopCoordinates.Max.X - activeDesc.DesktopCoordinates.Min.X} x {activeDesc.DesktopCoordinates.Max.Y - activeDesc.DesktopCoordinates.Min.Y}");

        Console.WriteLine("Creating Direct3D device...");

        D3DFeatureLevel[] featureLevels =
        [
            D3DFeatureLevel.Level121,
            D3DFeatureLevel.Level120,
            D3DFeatureLevel.Level111,
            D3DFeatureLevel.Level110,
            D3DFeatureLevel.Level101,
            D3DFeatureLevel.Level100,
            D3DFeatureLevel.Level93,
            D3DFeatureLevel.Level92,
            D3DFeatureLevel.Level91
        ];

        ID3D11Device* pDevice = null;
        ID3D11DeviceContext* pImmediateContext = null;
        D3DFeatureLevel chosenFeatureLevel;

        var hr = -1;

        foreach (var level in featureLevels)
        {
            hr = D3D11.GetApi().CreateDevice((IDXGIAdapter*)adapter, D3DDriverType.Unknown, IntPtr.Zero, (uint)CreateDeviceFlag.BgraSupport, &level, 1, D3D11.SdkVersion, &pDevice, &chosenFeatureLevel, &pImmediateContext);

            if (hr == 0)
            {
                Console.WriteLine($"Successfully created device with feature level {chosenFeatureLevel}");
                break;
            }
        }

        if (hr != 0)
        {
            Console.WriteLine($"Failed to create device: HR = {hr:X}");

            return;
        }

        var textureDesc = new Texture2DDesc
        {
            Width = (uint)(activeDesc.DesktopCoordinates.Max.X - activeDesc.DesktopCoordinates.Min.X),
            Height = (uint)(activeDesc.DesktopCoordinates.Max.Y - activeDesc.DesktopCoordinates.Min.Y),
            MipLevels = 1,
            ArraySize = 1,
            Format = Format.FormatR8G8B8A8Unorm,
            SampleDesc = new SampleDesc
            {
                Count = 1,
                Quality = 0
            },
            Usage = Usage.Staging,
            BindFlags = 0,
            CPUAccessFlags = (uint)CpuAccessFlag.Read,
            MiscFlags = 0
        };

        ID3D11Texture2D* stagingTexture = null;

        pDevice->CreateTexture2D(&textureDesc, null, &stagingTexture);

        Console.WriteLine("Staging texture created with dimensions: " + textureDesc.Width + "x" + textureDesc.Height + ", Format: " + textureDesc.Format);

        IDXGIOutput1* output1 = (IDXGIOutput1*)activeOutput;
        IDXGIOutputDuplication* duplicatedOutput;

        hr = output1->DuplicateOutput((IUnknown*)pDevice, &duplicatedOutput);

        if (hr < 0)
        {
            Console.WriteLine($"Failed to duplicate output, HR = {hr:X}");

            return;
        }

        Console.WriteLine("Output duplicated successfully.");

        OutduplFrameInfo frameInfo;
        IDXGIResource* desktopResource = null;

        hr = duplicatedOutput->AcquireNextFrame(1000, &frameInfo, &desktopResource);

        if (hr < 0)
        {
            Console.WriteLine($"Failed to acquire next frame, HR = {hr:X}, Status: {frameInfo.LastPresentTime}, TotalFrames: {frameInfo.TotalMetadataBufferSize}");

            return;
        }

        Console.WriteLine($"Next frame acquired, Last Present Time: {frameInfo.LastPresentTime}, Total Metadata Buffer Size: {frameInfo.TotalMetadataBufferSize}");

        ID3D11Texture2D* desktopTexture;

        hr = desktopResource->QueryInterface(SilkMarshal.GuidPtrOf<ID3D11Texture2D>(), (void**)&desktopTexture);

        if (hr < 0)
        {
            Console.WriteLine($"Failed to query interface for ID3D11Texture2D, HR = {hr:X}");

            return;
        }

        Console.WriteLine("ID3D11Texture2D interface obtained.");

        pImmediateContext->CopyResource((ID3D11Resource*)stagingTexture, (ID3D11Resource*)desktopTexture);

        Console.WriteLine("Resources copied.");

        MappedSubresource mappedResource;
        hr = pImmediateContext->Map((ID3D11Resource*)stagingTexture, 0, Map.Read, 0, &mappedResource);

        if (hr >= 0)
        {
            Console.WriteLine("Staging texture mapped successfully.");

            var dataPtr = (byte*)mappedResource.PData;
            var stride = mappedResource.RowPitch;

            Console.WriteLine($"Data pointer: {(long)dataPtr}, Stride: {stride}");

            var allZero = true;

            for (var i = 0; i < 100; i++) 
            {
                if (dataPtr[i] != 0)
                {
                    allZero = false;
                    break;
                }
            }

            Console.WriteLine(allZero ? "Data is all zeros." : "Data contains non-zero values.");

            using (var bitmap = new Bitmap((int)textureDesc.Width, (int)textureDesc.Height, (int)stride, PixelFormat.Format32bppArgb, (nint)dataPtr))
            {
                var filePath = @"C:\Users\vitaly\Desktop\screenshot.png";
                bitmap.Save(filePath, ImageFormat.Png);

                Console.WriteLine($"Screenshot saved to {filePath}.");
            }

            pImmediateContext->Unmap((ID3D11Resource*)stagingTexture, 0);
        }
        else
        {
            Console.WriteLine($"Failed to map staging texture, HR = {hr:X}");
        }

        Console.WriteLine("Releasing resources.");

        if (pDevice != null)
        {
            pDevice->Release();
        }

        if (pImmediateContext != null)
        {
            pImmediateContext->Release();
        }

        Console.WriteLine("Resources released.");
    }
}

输出显示一切都正确初始化,并且该帧据称是使用 DXGI 输出复制 API 捕获的。但是,当我映射暂存纹理并检查数据时,所有字节均为零。尽管纹理描述和复制似乎设置正确,但还是会发生这种情况。

控制台输出表示找到设备并输出,创建和执行过程中没有报错:

Initializing DXGI...
DXGI Factory created.
Enumerating adapters and finding active outputs...
Adapter 0: Intel(R) HD Graphics 5500, VendorId: 32902, DeviceId: 5654, SubSysId: 443355203, Revision: 9
Output 0 on Adapter 0: \\.\DISPLAY1, Attached to Desktop: True, Rotation: ModeRotationIdentity
Active output found on adapter 0, resolution: 1366 x 768
Selected Output: \\.\DISPLAY1, Total Size: 1366 x 768
Creating Direct3D device...
Successfully created device with feature level D3DFeatureLevel111
Staging texture created with dimensions: 1366x768, Format: FormatR8G8B8A8Unorm
Output duplicated successfully.
Next frame acquired, Last Present Time: 0, Total Metadata Buffer Size: 0
ID3D11Texture2D interface obtained.
Resources copied.
Staging texture mapped successfully.
Data pointer: 2005097365504, Stride: 5504
Data is all zeros.
Screenshot saved to C:\Users\vitaly\Desktop\screenshot.png.
Releasing resources.
Resources released.
c# directx directx-11 dxgi
1个回答
0
投票

在编写 DirectX(D3D、DXGI、Direct2D 等)时必须首先执行的操作是启用 DirectX 调试层。完成后,当在调试模式下运行代码时,您将在调试输出中看到这一点:

D3D11 错误:ID3D11DeviceContext::CopyResource:无法调用 当每个资源的格式不相同或位于时复制资源 彼此之间的可转换性最低,除非压缩了一种格式 (DXGI_FORMAT_R9G9B9E5_SHAREDEXP,或DXGI_FORMAT_BC[1,2,3,4,5,6,7]_*) 源格式与目标格式类似,根据: BC[1|4] ~= R16G16B16A16|R32G32,BC[2|3|5|6|7] ~= R32G32B32A32,R9G9B9E5_SHAREDEXP 〜= R32。 [资源操作错误#284: 复制资源_无效源]

这是宝贵的信息。在不确定纹理 (

desktopTexture
) 是否具有相同格式的情况下,您永远不应该将它们复制到另一个 (
stagingTexture
),而事实上它们并不具有相同的格式。

所以,改变这个:

var textureDesc = new Texture2DDesc
{
    ...
    Format = Format.FormatR8G8B8A8Unorm,
    ...
};

进入这个:

var textureDesc = new Texture2DDesc
{
    ...
    Format = Format.FormatB8G8R8A8Unorm,
    ...
};

现在错误消失了,但资源中仍然全为零。原因是DXGI输出复制不是一个屏幕截图API,它是一个API,当一个新的桌面框架可用时,获取(又名复制)一个新的桌面框架,所以它在“一段时间内”确实是有意义的。例如,当屏幕上没有任何移动时,不会获取任何帧(并且您将收到超时,这是预期的)。

因此,在代码中查看其工作的最简单方法是在循环中运行获取代码,或者只是在

AcquireNextFrame
调用之前添加一个计时器。

Thread.Sleep(200); // wait a bit here
hr = duplicatedOutput->AcquireNextFrame(1000, &frameInfo, &desktopResource);
© www.soinside.com 2019 - 2024. All rights reserved.