📜 ⬆️ ⬇️

WPF, WinForms: draw Bitmap with> 15000 FPS. Hardcore tricks Part 1

Immediately clarification: Bitmap 200x100 on a computer with fast memory and i7 3930K at 1366. But, this is an honest System.Drawing.Bitmap.
Introductory: 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 Starper coder (yes, in some government agencies there is still a warm Win2K lamp ).
Next, I drew attention to DirectX, the more we have for WPF appeared useful D3DImage control. I tried many engines, but none of them gave a convenient, elegant way to draw GDI + Bitmap from memory. Some worked with DX10-11 only. Closest to the goal was SlimDX . In any case, the frontend for control turned out to be ugly. All these engines ... are, to say the least, redundant, for my simple task.

But there is a solution.
And, to my satisfaction, it turned out to be quite simple and universal, exactly as it should, it will work even on Win2K and .Net 2.0.
When I was young and I thought I still had 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 old practices, then everything was forgotten. But suddenly, now I needed a non-standard control with pixel-by-pixel graphics, plus a fast drawing. That's how I got into a little dead end.
GDI + Bitmap is pretty darn comfortable with its gradients, anti-aliasing, and alpha. Very tasty pictures are obtained. It is easy to prepare the necessary Bitmap in memory, and even to do it quickly if you cache most of the image, but quickly display them on the screen there is no obvious way.

I had to remember 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 ended up with 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 the commented lines. In general, they should be uncommented to make life easier for the GC, but for the sake of hardcore FPS, if the _pArray size has not changed, I get kicked with GCHandle once in Realloc (). Although ... when we have 15,000, plus or minus a couple of hundreds of FPS do not play the role, hehe. If uncommenting in Paint () - do not forget to comment out the pin in Realloc ().
So, with the price of only 100 lines of code (the code is completely 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 that, it’s against accepted programming practices,” but what can you do.
The whole 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 cookies ! One of the reasons for the transition 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 high-grade Thread and it is cruel in it:
 renderthread = new Thread(() => { while (true) Render(); }); renderthread.Start(); 

There is still a little detail. Since the OS is not aware of our hooliganism with memory, in the window WndProc it is useless and pointless, but obstinately wipes our Background Color control. We will relieve the OS of 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 at all in a video card - here the frequency of the bus, processor, memory, their throughput is pianous. In general, almost quantum effects for the ordinary user.

So, I achieved 15600 FPS, the application takes ~ 30MB of memory, but 8% of processor utilization did not please me at all. To put it mildly, this is a lot for 3930K. And here in my head the video driver “vozvopil”: “Master, I think I have epilepsy!”, And the monitor: “And in general I only know how to 60Hz!”. Of course, we do not 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. Utilization of the processor goes around zero + error.
Next WPF. Everything is complicated and simple at the same time. WPF "controls" are not actually controls (otherwise we would not be able to turn and flatten them), and they do not have DC. Everything is decided by hosting Windows Forms controls in WPF using Windows FormsHost. In the attached project, WPF is an example of use, but is easily converted into pure Windows Forms, since the front end is simple as a boot.
')
The Bitmap rendering cycle in a demo project consists of just one line:
 GFX.Clear((drawred = !drawred) ? System.Drawing.Color.Red : System.Drawing.Color.Blue); 

Of course, the FPS rendering cycle mostly depends on the complexity of drawing the initial 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 on health, if you understand what you are doing and why. Sources and build, laid out on CodePlex under MIT license:
http://razorgdipainter.codeplex.com/
(I apologize in advance for the frivolous design of the project on CodePlex, if you're interested, I will complete it to normal OpenSource)

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

Source: https://habr.com/ru/post/164705/


All Articles