使用DirectX GetFrontBufferData

时间:2017-08-02 11:31:10

标签: c# .net directx

我正在尝试在我的电脑上创建所有屏幕的屏幕截图。在过去,我一直在使用GDI方法,但由于性能问题,我正在尝试DirectX方式。

我可以在没有问题的情况下拍摄单个屏幕的屏幕截图,代码如下:

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using System.Drawing;    

class Capture : Form
{
    private Device device;
    private Surface surface;

    public Capture()
    {
        PresentParameters p = new PresentParameters();
        p.Windowed = true;
        p.SwapEffect = SwapEffect.Discard;
        device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
        surface = device.CreateOffscreenPlainSurface(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8B8G8R8, Pool.Scratch);
    }

    public Bitmap Frame()
    {
        GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
        return new Bitmap(gs);
    }
}

(让我们忽略从内存中删除这个问题的位图)

使用该代码,我可以截取主屏幕截图。将Device构造函数的第一个参数更改为不同的数字对应于不同的屏幕。如果我有3个屏幕并且我将2作为参数传递,我会得到第三个屏幕的屏幕截图。

我遇到的问题是如何处理捕获所有屏幕。我想出了以下内容:

class CaptureScreen : Form
{
    private int index;
    private Screen screen;
    private Device device;
    private Surface surface;
    public Rectangle ScreenBounds { get { return screen.Bounds; } }
    public Device Device { get { return device; } }

    public CaptureScreen(int index, Screen screen, PresentParameters p)
    {
        this.screen = screen; this.index = index;

        device = new Device(index, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
        surface = device.CreateOffscreenPlainSurface(screen.Bounds.Width, screen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch);
    }

    public Bitmap Frame()
    {
        device.GetFrontBufferData(0, surface);
        GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
        return new Bitmap(gs);
    }
}

class CaptureDirectX : Form
{
    private CaptureScreen[] screens;
    private int width = 0;
    private int height = 0;

    public CaptureDirectX()
    {
        PresentParameters p = new PresentParameters();
        p.Windowed = true;
        p.SwapEffect = SwapEffect.Discard;
        screens = new CaptureScreen[Screen.AllScreens.Length];
        for (int i = 0; i < Screen.AllScreens.Length; i++)
        {
            screens[i] = new CaptureScreen(i, Screen.AllScreens[i], p);
            //reset previous devices
            if (i > 0)
            {
                for(int j = 0; j < i; j++)
                {
                    screens[j].Device.Reset(p);
                }
            }
            width += Screen.AllScreens[i].Bounds.Width;
            if (Screen.AllScreens[i].Bounds.Height > height)
            {
                height = Screen.AllScreens[i].Bounds.Height;
            }
        }
    }

    public Bitmap Frame()
    {
        Bitmap result = new Bitmap(width, height);
        using (var g = Graphics.FromImage(result))
        {
            for (int i = 0; i < screens.Length; i++)
            {
                Bitmap frame = screens[i].Frame();
                g.DrawImage(frame, screens[i].Bounds);
            }
        }
        return result;
    }
}

如您所见,我迭代可用的屏幕并在单独的类中创建多个设备和表面。但是调用Frame()类的CaptureDirectX会引发以下错误:

  

Microsoft.DirectX.Direct3D.dll中出现“Microsoft.DirectX.Direct3D.InvalidCallException”类型的未处理异常

device.GetFrontBufferData(0, surface);

我一直在研究这一点,但没有取得很大的成功。我不确定问题是什么。 我找到了一个link,提供了一个解决方案,即重置Device对象。但正如您在上面的代码中所看到的,我一直在尝试重置所有以前创建的Device对象,遗憾的是没有成功。

所以我的问题是:

  • 我试图通过这种方法实现的目标是什么(即GetFrontBufferData)?
  • 我做错了什么?我错过了什么?
  • 您是否在高速捕获屏幕时看到任何性能问题,例如30 fps? (捕获目标为30fps的单个屏幕给我的速率约为25-30fps,而GDI方法则有时下降到15fps)

仅供参考,它是一个WPF应用程序,即.NET 4.5

编辑:我应该提到我知道IDXGI_DesktopDuplication但遗憾的是它不符合我的要求。据我所知,该API只能从Windows 8开始提供,但我试图从我的客户那里获得一个可以从Windows 7开始运行的解决方案。

1 个答案:

答案 0 :(得分:0)

嗯,最终解决方案完全不同。 System.Windows.Forms.Screen班级与DirectX班级没有很好的配合。为什么?因为索引不匹配。 AllScreens中的第一个对象不一定必须是Device instatiation中的索引0。

现在通常这不是一个问题,除非你有一个&#34;奇怪的&#34;像我一样监控设置。在桌子上我有3个屏幕,一个垂直(1200,1920),一个水平(1920,1200)和另一个水平笔记本电脑屏幕(1920,1080)。

我的情况发生了什么:AllScreens中的第一个对象是左侧的垂直监视器。我尝试为索引0,1200宽度和1920高度创建一个设备。索引0对应于我的主监视器,即中间的水平监视器。因此,我实际上已经离开屏幕界限了我的实例。 instatiation不会抛出异常,稍后我会尝试读取前缓冲区数据。 Bam,例外,因为我试图拍摄1920x1200的1920x1200的屏幕截图。

可悲的是,即使在我开始工作之后,表现并不好。所有3个监视器的单帧大约需要300到500毫秒。即使只有一台显示器,执行时间也就像100毫秒。对我的用例来说不够好。 没有Backbuffer工作,它只会产生黑色图像。

我回到GDI方法并通过仅在每个Frame()调用上更新位图的特定块来增强它。您想捕获1920x1200区域,该区域被切割成480x300矩形。