Direct2D / GDI+ and slow Windows forms drawing - What can be done?
Asked Answered
C

5

6

I'm working a lot with Visual Studio 2008, .NET C# 2.0-3.5 and Windows Forms and I have noticed, like many before me, that GDI+ is extremely slow in drawing Controls. Note that I do not deal with images (JPG, GIF etc) very much. Images are only as icons in certain places. This is actually Controls/Forms/etc that are slow to draw.

The issue is that you can see Controls being drawn and it can take several seconds for a seemingly easy set of Controls to be drawn. Ie, it's lagging and horrible.

I have made tests where I just put a number of Labels (40-50) on a form, hitting F5 to run and have to wait for them to be drawn. Again, lag and not a very nice experience.

So, then there is WPF that might address this problem, but I/we are not ready to move to WPF. So I'm looking around for workarounds or fixes and I stumbled upon Direct2D, and when reading on that some other libraries.

I'm puzzled and thus have these questions:

  1. Firstly, what I want is a fairly neat and simple way to just replace GDI+ with something faster and hardware accelerated approach. Is it possible to do that without going over to WPF and without having to rewrite all my Windows Forms code?

    Whenever I read anything on Direct2D I see long blocks of usually horrible C++ code, telling me on how to manually write code to for drawing. I do not want that.

  2. While reading on the net, I stumbled upon SlimDX, but I cannot figure out how to use it (and I admit, I haven't tried very much as of writing). Let's say I already have a GUI-application (Windows Forms, standard C# code) - can I somehow use SlimDX (or something like it) to just "replace" GDI+ without too much rewriting?

My problem is I cannot find any examples or such telling me if it is possible to use SlimDX, Direct2D or other similar things in my already-created Windows Forms software, and if it is possible - how to do it.

Edit 1

I have made some tests in my real app, and isolated one of the slow things to this:

When I add text to some Labels in a UserControl, the Controls resize themselves to fit the text. For example, the containing GroupControl adapts a bit to the size of the text that was just added to the .Text-property of the Labels.

There are about 10 Label controls. The first time the labels are updated, and thus sizes are changed, the whole process takes about 500 ms. The second time the labels are updated, and no size changes, it takes about 0 ms.

Edit 2

Found one of the slow-downs. Then adding a String to the Text-property it is slow if the text that is being added differs in string length from the text that was there before the update.

I'm using DevExpress libraries, and the LabelControls can be set to AutoSizeMode. If I set that to "None" then the lag will go away when adding text that differs in length from the previous text. I guess this problem will be the same for the normal Label-control as it also has a AutoSize = true/false setting.

However, it's a "workaround" but still proves my point - it's really slow when resizing.

Chalcography answered 17/9, 2010 at 12:2 Comment(3)
Ted, can you post some trivial Winforms sample project that demonstrates your performance issue?Smallsword
Cant really see how to post files here. But what I tried was to create 1 Form, on that form I place 1 TabControl with 3 pages. Each page I will with Label, TextBox and ComboBox (200 Controls per tabPage). Then I just run it, click/switch between pages and there you can see how it lags. The same thing in normal apps, just worse usually =(Chalcography
I'm using DevExpress libraries - then the first test to try, is to use standard built-in Windows Controls, to see if they are faster. IMHO, the fact that you were using a third-party library of controls should have been in your first paragraph! [I've now added the appropriate devexpress tag, to hopefully make that more obvious.]Tanbark
N
3

Many of the posters above come with good points. I've created a 3D CAD application in GDI+ myself, and found it plenty fast enough if it's implemented correctly. Using Controls, however, immediately strikes me as a very awkward way to do things. A Control is a fairly big object and there are numerous reasons to make your own in this case.

I'd advise you to look into a retained mode drawing system. It's easy to implement and would cover your situation in most cases. You'd have to create the drawing logic yourself, but that's just fun and would give you more flexibility :)

Nigeria answered 18/3, 2012 at 13:58 Comment(4)
Seems that guy moved on from technology in 2014: bobpowelldotnet.blogspot.co.ukFulkerson
Sadly, Bob Powell died a few years ago. His site was pretty much the best resource on GDI+ programming out there, and unfortunately I don't think there's a complete copy of his archive anywhere.Nigeria
That is sad on several levels.Fulkerson
Thankfully the Internet Archive has some backups of his work from before it was taken over by soulless internet vampires: web.archive.org/web/20120826121342/http://www.bobpowell.net/…Fulkerson
A
1

On your first question, I had to use GDI to do some image processing stuff which was taking ages under GDI+. This was 4-5 years ago and working with GDI using managed C# was a pain - not sure how much it has changed now. There are many good and fast functions such as BitBlt which are very fast in drawing but you need to be very careful with releasing resources (handles) and memory. I also had another issue and that was saving the result as JPEG and it is non-existent in GDI so I had to use CxImage to read the HBitmap and then save it.

All in all, GDI is very fast and robust. If there better abstractions in DirectX, probably you are better off using them.

Amortize answered 17/9, 2010 at 12:24 Comment(1)
Thx. Well, I dont deal with pictures very much. Its the drawing of normal Windows Forms controls, bringin up Forms and suchs that is slow. GDI is fast? I read everywhere that it is not?Chalcography
O
1

I'm looking at some of the same issues. I am writing an application that needs to be able to render 2d graphics very efficiently since some users could have 10 - 50 windows open simultaneously. One thing to consider that no one else talked about here is the fact that direct2d can only be used on computers with Vista with service pack 2 and up. Also, according to this link:

http://www.tomshardware.com/news/msft-windows-xp-windows-7-market-share-win7,13876.html

38.5% of all Windows users were still using XP as of Nov, 2011. So, if selling the app to a significant amount of users still running XP is a concern (or your marketbase is to 3rd world countries that are mainly using XP), then you should either go with:

  1. Direct2d for newer operating systems and GDI+ for XP systems.

  2. XNA - which is compatible with XP and can also be used with newer operating systems. See this link: http://msdn.microsoft.com/en-us/library/bb203925.aspx

  3. SlimDX - mentioned in the first answer. Supports XP as well as newer operating systems. See: http://slimdx.org/ and http://slimdx.org/features.php

  4. OpenTK if you care about compatibility between Windows, Linux, Max, etc.

You should also be aware that there was a bug with GDI+ that caused it to suffer very poor performance when it was initially released. See the following link why some developers claim that Microsoft broke the gui for Windows7 for apps using GDI+:

http://www.windows7taskforce.com/view/3607

or do a web search from your favorite search engine with this string: "gdi+ bug slow on windows 7".

Outrange answered 17/2, 2012 at 1:27 Comment(0)
S
0

We use SlimDX in our C# app.... But we're actually doing 3D. We wrote our own 2D lib to be able to do simple 2D drawing. SlimDX is just a lightweight wrapper around DirectX. So you'll get all of the pro's and cons of DirectX. Like that it's your problem to emulate the videocard if its not present.

If you want something for drawing to offscreen bitmaps, I'd go for WPF, as it is well integrated with C#, works mostly everywhere, and accellerated when there's hardware available. You can copy the output to a bitmap and use that in regular GDI/Winforms. But it will only be faster than GDI+ if you do fairly complex stuff (lots of filters, mixing textures etc...).

Edit:

In response to the comments, I built a little sample form. There is a seconds long wait when switching the first time, but after that it's responsive. Slower than I'd expect, but by all means usable. Would like Ted to comment if this is about the performance he is seeing in his app.

public class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        // *** EDIT ***
        this.tabPage1.SuspendLayout();
        this.tabPage2.SuspendLayout();
        this.tabControl1.SuspendLayout();
        this.SuspendLayout();

        FillTab(tabPage1, Color.White);
        FillTab(tabPage2, Color.Yellow);

        // *** EDIT ***
        this.tabPage1.ResumeLayout(false);
        this.tabPage2.ResumeLayout(false);
        this.tabControl1.ResumeLayout(false);
        this.ResumeLayout(false);
    }

    private static void FillTab(TabPage tabPage, Color color)
    {
        for (int i = 0; i < 200; ++ i)
        {
            int left = (i % 5) * 320;
            int topOffset = (i / 5) * 23;
            tabPage.Controls.Add(new Label { Left = left, Top = topOffset, Width = 100, Text = "Label" });
            tabPage.Controls.Add(new TextBox() { BackColor = color, Left = left + 100, Top = topOffset, Width = 100, Text = tabPage.Text });
            tabPage.Controls.Add(new ComboBox { BackColor = color, Left = left + 200, Top = topOffset, Width = 100 });
        }

    }

    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.tabControl1 = new System.Windows.Forms.TabControl();
        this.tabPage1 = new System.Windows.Forms.TabPage();
        this.tabPage2 = new System.Windows.Forms.TabPage();
        this.tabControl1.SuspendLayout();
        this.SuspendLayout();
        // 
        // tabControl1
        // 
        this.tabControl1.Controls.Add(this.tabPage1);
        this.tabControl1.Controls.Add(this.tabPage2);
        this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
        this.tabControl1.Location = new System.Drawing.Point(0, 0);
        this.tabControl1.Name = "tabControl1";
        this.tabControl1.SelectedIndex = 0;
        this.tabControl1.Size = new System.Drawing.Size(292, 266);
        this.tabControl1.TabIndex = 0;
        // 
        // tabPage1
        // 
        this.tabPage1.Location = new System.Drawing.Point(4, 22);
        this.tabPage1.Name = "tabPage1";
        this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
        this.tabPage1.Size = new System.Drawing.Size(284, 240);
        this.tabPage1.TabIndex = 0;
        this.tabPage1.Text = "tabPage1";
        this.tabPage1.UseVisualStyleBackColor = true;
        // 
        // tabPage2
        // 
        this.tabPage2.Location = new System.Drawing.Point(4, 22);
        this.tabPage2.Name = "tabPage2";
        this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
        this.tabPage2.Size = new System.Drawing.Size(245, 217);
        this.tabPage2.TabIndex = 1;
        this.tabPage2.Text = "tabPage2";
        this.tabPage2.UseVisualStyleBackColor = true;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(292, 266);
        this.Controls.Add(this.tabControl1);
        this.Name = "Form1";
        this.Text = "Form1";
        this.tabControl1.ResumeLayout(false);
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.TabControl tabControl1;
    private System.Windows.Forms.TabPage tabPage1;
    private System.Windows.Forms.TabPage tabPage2;
}
Smallsword answered 17/9, 2010 at 12:35 Comment(18)
I dont think Im following. offscreen bitmaps? ehm, I just want the controls on my UserControls/forms to be drawn quickly and without the lag. Are u saying that GDI+ is as fast is it gets? Cause its really slow. I just tried adding about 160 TextBoxes otno an empty Form, and you can the the lag when redrawing that area...Chalcography
@Ted: In that case WPF is maybe not appropriate. Could you post some code showing what you do exaclty with those controls? 160 empty textboxes, that is 160 rectangles, should be fast in any technology.Smallsword
Hey, WPF is as I said not an option since it would require a lot of rewrite and thats not an option. The code is simple a new Windows Forms project, 1 TabControl that contains about 160-180 TextBoxes. On the next TabPage there is about 200-300 LabelControls and on the next TabPage about 200 ComboBoxes. If you then change TabPage you can see the effect clearly. thx.Chalcography
200 textboxes/comboboxes?! Are you sure you're not better off using a datagrid-type control or something? Because I question the value of having so many controls on one form.Dupaix
@Alex Thats not the point. Its just an example to show how slow it is. That was just a test project. In my normal apps we use a lot of different controls, and its is SO SLOW that it makes me wanna cry.Chalcography
Any technology is slow if you use it inappropriately. Neither WinForms nor WPF were designed for that kind of brutality. You must aggregate your controls somehow, make use of virtualization, and other similar techniques to take it down to manageable size. Of course all these frameworks bring some overhead with them, so it's unrealistic to expect them to be fast under such circumstances. Also, at one of your previous comments - a textbox is NOT a rectangle. It provides so much functionality I can't even fit in in this comment. Think undo/redo, multi-language, scrolling etc etc etcDupaix
So, what you are saying that simple Labels cannot be in a form without them being slow. This illustrated my point with an extremely slow Framework. The textbox is a very simple control. Also see my update in my question above.Chalcography
@Alex: Ted's question is about replacing the GDI painting of controls with another techology. So my point that a textbox (an empty one) is no more work than a rectangle, referred to the GDI involvement, the painting. There is for sure lots of overhead to be found here somewhere, but I doubt if GDI is the bottleneck.Smallsword
@jdv - Indeed, the actual drawing code is almost definitely not the bottleneck. One can easily test that by drawing the same things the textbox draws - I think there's a helper class somewhere in the framework that does exactly that. Draw that 200 times and I don't thing you'd have a problem regardless of the technology. Controls on the other hand are responsible for a zillion other things, most importantly layout - each control has alignments, dock settings etc etc.Dupaix
I updated with an Edit 2, explaining the problem I found. The problem is with the framework that is insanely slow when "resizing" (or whatever it does). It should be that way.Chalcography
@jdv I ran you example code, and it most definately lags. When switching between the pages it takes about.... 150-200 ms? Something like that for the page to draw...Chalcography
@Ted: If you read my edit carefully, I did not claim it was instantaneous. Given that you changed the question, is painting still an issue for you?Smallsword
@jdv Yes, the painting is still an issue even though it is a lot better when skipping half a second of wait time... =)Chalcography
@Ted: Pity. Are you talking about my sample, your sample (which I haven't seen) or your production code? And how many labels/controls you have on each page in your production code? I still have the idea I'm missing something.Smallsword
@jdv I tried your example, and I can see the "lag" when it is showing the Controls. In my example there is around 200 labels/comboboxes per TabPage... You dont see that?Chalcography
@Ted: Yes, I do see some lag (and I think I already confirmed that). It's along the lines of the 200ms you mentioned. But that's an example with 3 times more controls than what you say you need. Why haven't modified the sample such that it is about as complex as what you actually need? When I reduce the control count in my sample to 200, there is no noticable lag. So I fail to see what the problem is.Smallsword
Late reply. For the simple reason that I want to understand if GDI sucks or not. Is it so slow and lame, that it cant draw 200 controls without lagging? In my real apps, I have a lots of different controls all over the place, and it lags. And I want to get away from that, if I can.Chalcography
Proposed edit: In the form's constructor, before the calls to FillTab, I would add this.tabPage1.SuspendLayout(); this.tabPage2.SuspendLayout(); this.tabControl1.SuspendLayout(); this.SuspendLayout(); then after the calls add this.tabPage1.ResumeLayout(false); this.tabPage2.ResumeLayout(false); this.tabControl1.ResumeLayout(false); this.ResumeLayout(false);Tanbark
V
0

You could try managed directx, but they no longer support it (moved on to XNA). Honestly, unless you've got a shitty computer or a ton of controls, I don't know why it'd be lagging so bad. If you're doing some cpu intensive stuff on your main thread, move it to a separate thread. That's the only other reason I can think of that'd cause that kind of lag.

Vaillancourt answered 17/9, 2010 at 18:32 Comment(1)
I have a Intel 920 2.6 Ghz with 6 GB ram, running om Win7 64bit with LOADS of RAM available and a bored CPU. No, its not the computer =) A testproject of just addding a bunch of Labels, TextBoxes in a TabControl shows the problem. It lags very visibly when switching tabs (and the tabPages contain hunders of Labels or TextBoxes or whatever)....Chalcography

© 2022 - 2024 — McMap. All rights reserved.