2

I have WinForms application. I made an user control, which draws a map from coordinates of ca 10k lines. Actualy, not all lines are straight ones, but when the map is zoomed out fully - Bezier curves are irrelevant and are replaced with straight lines.

When the map is zoomed, I have smaller number of lines and curves, so the drawing is fast enough (below 15ms). But when it's zoomed out fully - I need to draw all lines (because all fit into viewport). This is painfully slow. On my very fast machine it takes about 1000ms, so on slower machines it would be an overkill.

Is there a simple way to speed up the drawing? I use Graphics object for drawing and I set Graphics.Scale property to my map fit into my control. Does this slow things down? I use Graphics.TranslateTransform() to ensure the whole map is visible. Both scale and translate is set only once in OnPaint() event handler.

Then there is a loop which draws ca 10k lines. And I just see them drawing on the screen.

Maybe WPF container would help?

Well, I could probably simplify the map to merge some lines, but I wonder if it's worth the effort. It would complicate the code greatly, would introduce much more calculations, use extra memory and I don't know if at the end of the day it would be considerably faster.

BTW, I tested that processing of all lines (converting from one structure to another with some aditional calculations) takes ca 10ms on my machine. So - the drawing alone costs 100x more time.

EDIT: Now here's the new problem. I've turned double buffering on with:

SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);

Here's my messy OnPaint() handler:

protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    if (Splines == null) return;

    var pens = new[] {
        new Pen(TrackColor),
        new Pen(TrackColor),
        new Pen(RoadColor),
        new Pen(RiverColor),
        new Pen(CrossColor)
    };

    var b = Splines.Bounds;
    Graphics g = e.Graphics;

    g.PageScale = _CurrentScale;
    g.TranslateTransform(-b.Left, -b.Top);
    int i = 0;
    foreach (var s in Splines) {
        g.DrawLine(pens[s.T], s.A, s.D);
        if (++i > 100) break;
        //if (s.L) g.DrawLine(pens[s.T], s.A, s.D);
        //else g.DrawBezier(pens[s.T], s.A, s.B, s.C, s.D);
    }

    foreach (var p in pens) p.Dispose();
}

Take my word the code works, if I only remove OptimizedDoubleBuffer from styles. When double buffering is on the handler executes properly, each DrawLine is executed with correct params. But the graphics is not displayed. CPU usage during resizing is next to zero. Like all DrawLine calls were ignored. What's happening here?

Harry
  • 4,524
  • 4
  • 42
  • 81
  • OK, I found scaling and translating doesn't work with double buffer. So I scaled and translated all points on OnPaint loop, now it works "real time". No visible lag on scaling, nor CPU usage above 2%. So maybe it could be done faster, but since it works fast enough now I'm not sure. If I'm missing something - please let me know. – Harry Jan 11 '15 at 22:45
  • Calling 10000 times *g.DrawLine* is causing a huge overhead. Try to add the lines in a path and draw the path once. See if it is faster. Or maybe do the scale-translate of the points manually and draw them normally. – γηράσκω δ' αεί πολλά διδασκόμε Jan 12 '15 at 02:04
  • You should re-use the `Pen`s instead of recreating them every time. – SLaks Jan 12 '15 at 04:12

4 Answers4

2

In a related post I've seen recently but can't find, the OP claimed to have seen a large speed-up when switching his control to use double-buffering. Apparently there's a substantial hit for drawing stuff to the screen.

Another thing you could try is decimating the point lists in the lines you draw when zoomed out. Instead of doing the decimation each frame, you could do it only once each time the zoom is changed.

adv12
  • 8,443
  • 2
  • 24
  • 48
  • When I set `DoubleBuffered` property of my `UserControl` to true, it doesn't paint at all. OnPaint is called properly, but no line is visible. Why is that? – Harry Jan 11 '15 at 21:44
  • Are you using the Graphics object passed into the Paint event handler to do the drawing? – adv12 Jan 11 '15 at 21:46
  • Of course I use `Graphics` from `PaintEventArgs`. It works fine (but slow) when `DoubleBuffered` is false. With double buffering all I got is background. Do I have to "commit" the drawing somehow? – Harry Jan 11 '15 at 21:49
  • I wouldn't think so. I'm stumped. – adv12 Jan 11 '15 at 21:50
  • Ok - I almost got it - when I removed scaling and translation it draws, but I obviously need scaling. What's interesting, with double buffer and no scaling it's super fast. – Harry Jan 11 '15 at 22:18
  • 1
    @Harry: Try applying the scaling yourself to the raw data (outside the paint handler for perf). – SLaks Jan 12 '15 at 04:13
  • 1
    Yes, I end up scaling data myself, but I don't see how scaling outside `OnPaint` would help - the handler is called only when visible area or control size is changed - it's exactly the case when map data should be rescaled. We wouldn't need repainting otherwise. At least since we use `OptimizedDoubleBuffer`. I forgot to tell I don't animate the control beside scaling and moving. So even if I add "intertia" animation for map navigation - every frame would need rescaling (or translating). So scaling inside `OnPaint` is OK. It could get slower on rotation, but it's rarely used with maps. – Harry Jan 12 '15 at 22:09
2

Try double buffering as a possible solution or try to reduce the number of lines. Only testing will give you an answer for your application.

Winforms Double Buffering

Double buffering with Panel

Community
  • 1
  • 1
Emond
  • 50,210
  • 11
  • 84
  • 115
  • 1
    Reducing the number of lines would be quite tough, because I would have to find lines which are connected to other lines and merge them. If I just leave out some lines the resulting map look would be unpredictible, because the lines are not ordered continuously. I could order them first (I have sorting algorithm in my code) - but it's quite time consuming operation, since it involves huge amount of comparisons and data moving. I pursue double buffering, but it just doesn't work with this option. – Harry Jan 11 '15 at 22:10
  • @Harry - Of course, it is up to you to decide/figure out what the best way is for your application. We do not have your code so we can only guess. It might be that you are expecting/asking too much from WinForms. – Emond Jan 12 '15 at 09:04
  • That's what I thought first. I considered using WPF drawing, but since I tested the code with over 10k lines - i'm surprised how fast WinForms can draw. At least when I use only simple 2D shapes and transformations. The code itself displays a vector map of railway tracks. So it's a simple case. It's very fast with `OptimizedDoubleBuffer`. – Harry Jan 13 '15 at 06:06
1

The feasibility of this really depends on if you're using anti-aliasing, if the thing can rotate, if the thickness has to be very accurate, etc.

However you can always draw all the lines into a bitmap, then simply redraw the bitmap unless the map data itself has actually changed. Of course then you get into having different bitmaps for different zoom levels, hiding and showing them, multiple bitmaps in a grid for the high details etc.

It's definitely not ideal, but if you really do need to draw thousands of lines on a 20ms refresh though.. it might be your only real option.

Octopoid
  • 3,507
  • 5
  • 22
  • 43
0

Or you could use lower level of drawing, outside GDI+. one such example is SlimDX. This wrapper allows you to create a directX device write from your windows controls and forms. Once DirectX is in action, the speed can increase up to several times.

2ndly, when drawing on win panel even with DoubleBuffered enabled, you always have to Invalidate the panel which asks the Environment to call the OnPaint event which actual draws using the system provided Graphics object. This invalidation usually requires a timer with fire rate more than 30 to five you a feeling of smooth playback. Now, when the load increases, the subsequent timer event is delayed since everything is happening under a single thread. And the timer must Yield the thread for around 25ms after every fire (windows OS limitation). Cross Thread access ia not allowed, using which a System.Threading.Timer could have prevent this jitter.

See this link for an example where I have tried to transfer my existing GDI code to DirectX. The code uses a lot of graphics attributes which i have incorporated in the wrapper which can draw on both GDI and DirectX.

https://drive.google.com/file/d/1DsoQl62x2YeZIKFxf252OTH4HCyEorsO/view?usp=drivesdk

Umar Hassan
  • 192
  • 3
  • 11