Saving Surface to Bitmap and optimizing DirectX screen capture in C#
Solution 1
If you don't want to use SlimDX library you can also try
public Bitmap GimmeBitmap(Surface s)
{
GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Bmp, s);
return new Bitmap(gs);
}
and try the same for .png - I did not test performance but it have to be faster than using disc temporary file :)
and as for 1st question - try to only once create surface and then on every screenshot only put into it device's buffer data and create the bitmap
d.GetFrontBufferData(0, s);
return new Bitmap(SurfaceLoader.SaveToStream(ImageFileFormat.Bmp, s));
this should save you some time :)
Solution 2
If performance really is an issue, you should consider writing your code in C++
instead. Therefor you dont need an external library but can directly access the backend-buffer of your video card via Windows-API + DirectX.
Accessing the backend(-video)-buffer is a lot faster than reading from the frontend-buffer.
To optimize performance (which also awnsers your question 1) use multithreading (see TPL or threading depending on your needs).
Here is an inside of how to do it in C++ CodeProject examples in C++. From my personal experience, DirectX was by far the fastest.
These steps
1. reading backend-buffer into a bitmap to process the data
2. spawning new thread to repeat step 1 while previous thread is still busy
take about 10-40ms
(together) - implemented in C++
(on NVIDIA GeForce GTX 970M) and depending on the current workload of the hardware
Possible middle course
If you want to stick with C# but also need the performance, writing a C++
-dll for .NET (see .NET Programming with C++/CLI (Visual C++)) which reads the video buffer and returns the data to your C#
-Code will do the trick.
Related videos on Youtube
Alex
Updated on February 13, 2022Comments
-
Alex over 2 years
after a whole day of testing I came up with this code, which captures current screen using DirectX (SlimDX) and saves it into a file:
Device d; public DxScreenCapture() { PresentParameters present_params = new PresentParameters(); present_params.Windowed = true; present_params.SwapEffect = SwapEffect.Discard; d = new Device(new Direct3D(), 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, present_params); } public Surface CaptureScreen() { Surface s = Surface.CreateOffscreenPlain(d, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch); d.GetFrontBufferData(0, s); return s; }
Then I do the following:
DxScreenCapture sc = new DxScreenCapture();
..code here
private void button1_Click(object sender, EventArgs e) { Stopwatch stopwatch = new Stopwatch(); // Begin timing stopwatch.Start(); Surface s = sc.CaptureScreen(); Surface.ToFile(s, @"c:\temp\test.png", ImageFileFormat.Png); s.Dispose(); stopwatch.Stop(); textBox1.Text = ("Elapsed:" + stopwatch.Elapsed.TotalMilliseconds); }
The results are:
0. when I don't save surface: avg. elapsed time: 80-90ms
1. when I also save Surface to BMP file: format: ImageFileFormat.Bmp , avg. elapsed time: 120ms, file size: 7mb
2. when I also save Surface to PNG file: format: ImageFileFormat.Png , avg. elapsed time: 800ms, file size: 300kb
The questions are:
1. Is it possible to optimise current image capture? According to this article - Directx screen capture should be faster than GDI. For me, GDI usually takes 20ms to get a "Bitmap", whereas it takes 80ms to get "Surfare" using DX (both without saving).
http://www.codeproject.com/Articles/274461/Very-fast-screen-capture-using-DirectX-in-Csharp
2a. How to save Surface to PNG image format faster? When I save surface to 7mb BMP file it takes almost 6 times less time, than when I save the same surface to 300kb PNG file..
2b. Is it possible to save Surface directly to Bitmap so I don't have to create temporary files?
So I don't have to do following: Surface -> image file; image file open -> bitmap;, but instead: Surface -> bitmap
that's all for now. I'll gladly accept any tips, thanks!
Edit:
Just solved 2b by doing:
Bitmap bitmap = new Bitmap(SlimDX.Direct3D9.Surface.ToStream(s, SlimDX.Direct3D9.ImageFileFormat.Bmp));
Edit2:
Surface.ToFile(s, @"C:\temp\test.bmp", ImageFileFormat.Bmp); Bitmap bitmap = new Bitmap(@"C:\temp\test.bmp");
is faster than:
Bitmap bitmap = new Bitmap(SlimDX.Direct3D9.Surface.ToStream(s, SlimDX.Direct3D9.ImageFileFormat.Bmp));
by 100 ms!!! Yeah, I couldn't believe my eyes too ;) I don't like the idea of temporary file creation, but a 50% performance increase (100-200ms instead of 200-300+) is a very good thing.
-
Admin about 12 yearsI think png can compress faster than 800 ms. Try if it is faster if you first write to a memorystream.
-
-
Alex about 12 yearsSlimDX doesn't work that way.. I get tons of errors with "SurfaceLoader" (The name 'SurfaceLoader' does not exist in the current context). I think that I has something to do with "Managed DirectX" library, which is not supported in .net 4 so I can't add the reference. It's like C# VisualStudion doesn't know what "SurfaceLoader" and "GraphicsStream" are..
-
Alex about 12 yearsP.S. I've already installed DX SDK but unfortunattely there is no "Managed DX" reference in VS2010. After some digging I found out that we no longder(June 2010 SDK+) can use DX, all that's left is SlimDX (wrapper) or XNA (used mainly for game dev).
-
Runaurufu about 12 yearsSurfaceLoader is not in SlimDX but in native DirectX (Microsoft.DirectX.Direct3D.SurfaceLoader) and it works on .NET4... And if you cannot find dll for managed code go look at "C:\Windows\Microsoft.NET\DirectX for Managed Code".