我正在开发一个应用程序,可以使用DXGI输出复制和C#中的Direct 3D从桌面捕获屏幕截图.我的应用程序初始化DXGI,创建Direct 3D设备,并复制输出.它捕获帧,但捕获的图像数据全为零.以下是我的应用程序的完整代码,该应用程序旨在将屏幕截图保存到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.

推荐答案

当您对Direction(D3 D、DXGI、Direct 2D等)进行编程时,您必须首先做什么是启用DirectX debug layer.完成后,当在调试模式下运行代码时,您将在调试输出中看到这一点:

D3 D11错误:ID 3D 11设备上下文::CopyResource:无法调用 当每个资源的字节数不相同或处于 至少可相互触发,除非一种格式被压缩 (DXGI_FORMAT_R9G9B9E5_SHAREDEXP,或DXGI_FORMAT_BC[1,2,3,4,5,6,7]_*) 源格式与dest类似,根据:BC[1| 4] ~= R16 G16 B16 A16| R32 G32,BC[2| 3| 5| 6| 7] ~= R32 G32 B32 A32,R9 G9 B9 E5_SHAREDEXP ~= R32.[资源_操纵错误#284: COPYRESource_INvalIDSource]

这是宝贵的信息.在不确定纹理是否具有相同格式的情况下,您不应该将纹理(desktopTexture)复制到另一个纹理(stagingTexture),事实上它们并非如此.

所以,改变这个:

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

进入这个:

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

现在错误已经消失了,但是.您的资源中仍然有所有零.

原因是DXGI Output Duplication is not a screenshot API,它是一个API,当新的桌面框架可用时,它会acquires(又名复制),所以它确实是在"经过的一些时间"内有意义的事情.例如,当屏幕上没有任何东西移动时,不会获取任何帧(并且您将获得超时,这是预期的).

因此,在您的代码中,看到它工作的最简单方法是循环运行获取代码,或者只是在AcquireNextFrame调用之前添加一个计时器.

Thread.Sleep(200); // wait a bit here
hr = duplicatedOutput->AcquireNextFrame(1000, &frameInfo, &desktopResource);

PS:代码缺少Release个调用(desktopResource、desktopTexture等)

Csharp相关问答推荐

更新数据库中的对象失败,原因是:Microsoft. EntityFrame Core. GbUpdateConcurrencyResponse'

ASP.NET核心结果.文件vs结果.流

. NET 8使用COM向VB6公开

通过EFCore上传大量数据.

如何在NodaTime中为Instant添加一年?

Thad.Sept()vs Task.Delay().Wait()

为什么在使用动态obj+类obj时会调用串联?

在具有不同属性名称的两个类之间创建关系

在IAsyncEnumerable上先调用,然后跳过(1)可以吗?

.NET并发词典交换值

在扩展方法中,IEnumerable<;T>;不会转换为IEumerable<;T&>

如何在发布NuGet包之前设置命名空间?

如何在特定环境中运行dotnet测试?

在.NET8中如何反序列化为私有字段?

在同一个捕获中可以有多种类型的异常吗?

我是否应该注销全局异常处理程序

无法向Unity注册Microsoft Logger

如何读取TagHelper属性的文本值?

除非首先访问使用的终结点,否则本地API上的终结点不起作用

测试单个对象是否与Func<;T,bool>;匹配