WPF, WinForms: draw Bitmap c> 15000 FPS. Hardcore Tricks Part 1

Immediate clarification: Bitmap 200x100 on a computer with fast memory and i7 3930K on 1366. But, this is an honest System.Drawing.Bitmap.
Introductory: an oscilloscope type application. Link to the finished project with a frontend at the end of the article.
How to quickly draw it on the screen? WriteableBitmap is good, fast, and it is the best solution for WP, WinRT, WPF. But WinForms, .Net 2.0, Win2K are also worried about the boring old-time encoder-encoder (yes, in some state agencies there is still a warm tube Win2K).
Next, I drew attention to DirectX, especially since we had a useful control for D3DImage for WPF. I tried many engines, but none of them gave a convenient elegant way to draw GDI + Bitmap from memory. Some worked only with the DX10-11. The closest to the target was SlimDX . In any case, the front end for the control was ugly. All of these engines ... to put it mildly redundant, for my simple task.

But there is a solution.
And, to my pleasure, it turned out to be quite simple and universal, just as it should, it will work even on Win2K and .Net 2.0.
When I was young, and I still seemed to have a 5-inch drive, I used BitBlt and SetDIBitsToDevice. Then, with the transition to .Net, I still used them and Win32 GDI BITMAP, because I used the old ones, then everything was forgotten. But suddenly, now I needed a non-standard control with pixel-by-pixel graphics, plus a quick rendering. That's how I got into a little dead end.
GDI + Bitmap is damn convenient with its gradients, antialiasing, and alpha. Very tasty pictures are obtained. It is not difficult to prepare the desired Bitmap in memory, and even do so quickly if the cache used on most of the image, but quickly display them on the screen, there is no obvious way.

I had to recall the not obvious:
[DllImport("gdi32")]
extern static int SetDIBitsToDevice(HandleRef hDC, int xDest, int yDest, int dwWidth, int dwHeight, int XSrc, int YSrc, int uStartScan, int cScanLines, ref int lpvBits, ref BITMAPINFO lpbmi, uint fuColorUse);

And the key method in the end turned out like this:
public void Paint(HandleRef hRef, Bitmap bitmap)
{
	if (bitmap.Width != _width || bitmap.Height != _height)
		Realloc(bitmap.Width, bitmap.Height);
	//_gcHandle = GCHandle.Alloc(pArray, GCHandleType.Pinned);
	BitmapData BD = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), 
									ImageLockMode.ReadOnly, 
									PixelFormat.Format32bppArgb);
	Marshal.Copy(BD.Scan0, _pArray, 0, _width * _height);
	SetDIBitsToDevice(hRef, 0, 0, _width, _height, 0, 0, 0, _height, ref _pArray[0], ref _BI, 0);
	bitmap.UnlockBits(BD);
	//if (gcHandle.IsAllocated)
	//	_gcHandle.Free();
}

Regarding commented out lines. In general, they should be uncommented to make the life of the GC easier, but for the sake of hardcore FPS, if the size of _pArray has not changed, GCHandle is kicked once in Realloc (). Although ... when we have 15,000, plus or minus a couple of hundred FPS no longer play a role, hehe. If you uncomment in Paint () - do not forget to comment out the pin in Realloc ().
So, at the cost of only 100 lines of code (the full code in the attached project below), we solved the FPS problem for System.Drawing.Bitmap, and no monstrous GPU engines and frameworks. The anger of Microsoft evangelists is possible “You can’t do this, this is against accepted programming practices,” but what can you do.
The entire frontend for the desired control is elegantly reduced to several lines:
RazorPainter RP = new RazorPainter();
graphics = Control1.CreateGraphics();
hDCRef = new HandleRef(graphics, graphics.GetHdc());
public void Render()
{
    RP.Paint(hDCRef, BMP);
}
RP.Dispose();
graphics.Dispose();

And now the cookies ! One of the reasons for switching to the "dark side" of GDI32. The fact is that with this approach we are absolutely indifferent to UI Thread and Invoke it. For the sake of record FPS, we boldly create a separate full-fledged Thread and it is cruel in it:
renderthread = new Thread(() =>
{
	while (true)
		Render();
});
renderthread.Start();

There is still a small detail. Since the OS is not aware of our hooliganism with memory, then in the window WndProc it is useless and pointless, but stubbornly wipes our control Background Color. We will save the OS from unnecessary torment (and slightly increase the FPS) in this way:
public RazorBackend()
{
	InitializeComponent();
	SetStyle(ControlStyles.DoubleBuffer, false);
	SetStyle(ControlStyles.UserPaint, true);
	SetStyle(ControlStyles.AllPaintingInWmPaint, true);
	SetStyle(ControlStyles.Opaque, true);
}

In general, of course, FPS will be different for everyone, and the matter is not in the video card at all - here the frequency of the bus, processor, memory, their bandwidth are grand. In general, almost quantum effects for an ordinary user.

So, I achieved 15600 FPS, the application takes ~ 30MB of memory, but the utilization of the 8% processor did not please me at all. To put it mildly, this is a lot for the 3930K. And then a video driver “cried out” in my head: “Master, I have epilepsy!” And a monitor: “But I can only do 60Hz!” Of course, we don’t need such an FPS, and the correct rendering cycle will be something like this:
rendertimer = new DispatcherTimer();
rendertimer.Interval = TimeSpan.FromMilliseconds(15); /* ~60 FPS on my PC */
rendertimer.Tick += (o, args) => Render();
rendertimer.Start();

Well, or in another way, to your taste. Disposal of the processor goes in the region of zero + error.
Next WPF. Everything is complicated and simple at the same time. WPF "controls" are not actually controls (otherwise we couldn’t twist and flatten them), and they don’t have DC. Everything is solved by hosting WindowsForms control in WPF using WindowsFormsHost. In the attached project, WPF is an example of use, but it can easily be converted to pure WindowsForms, since the front-end is simple as a boot.

The Bitmap rendering cycle in a demo project consists of only one line:
GFX.Clear((drawred = !drawred) ? System.Drawing.Color.Red : System.Drawing.Color.Blue);

Of course, the FPS rendering cycle for the most part depends on the complexity of drawing the original Bitmap in memory, and simply clearing its Graphics in a demo project is a wildly fast block operation. But I tested the approach and speed.

Use your health if you know what you are doing and why. I uploaded the source code and build on CodePlex under the MIT license:
http://razorgdipainter.codeplex.com/
(I apologize in advance for the frivolous design of the project on CodePlex, if interested, I’ll complete the normal OpenSource)

UPD : alexanderzaytsevTruly signaled that my version of WPF implementation may not be ideal. I tried to make it simple and understandable. The key point is only in the RazorPainter.cs file, and the demo project is not an exemplary pattern for its use, it only demonstrates the possibility in WPF and shows FPS.
Judging by the resonance, probably I should make a real OpenSource framework out of this. I already write an article about WinForms control.
UPD2 : There was a continuation of the post: http://habrahabr.ru/post/164885/ . Upgrading sorts and binaries on CodePlex to v. 0.6 beta

Also popular now: