locked
WPF performance problem regarding AddDirtyRect on WriteableBitmap

    Question

  • Hello,

    Thanks for your help regarding the line drawing in the thread "Improve line drawing performance". Actually, now the line drawing consumes virtually no CPU time at all.

    However, the application still takes approx 35% CPU for a window size of 1200*900 on my computer. I have destilled a small program (see below) demonstrating the problem. In this program I have removed the line drawing completely, now only filling a modest 10 by 10 pixel square 50 times a second, and marking the same area as dirty.

    If the entire window of 1200*900 pixel is marked as dirty instead of the 10*10 square, the CPU percentage is almost the same. If the call to AddDirtyRect is omitted, the program goes down to 0%.

    A funny effect is the the program takes approx 0% CPU if the window is instead small or medium sized (this goes for windows up to a little more than half the screen on my computer). For larger windows (e.g. 1200*900) something strange happens and the CPU usage abruptly goes up to approx. 35%.

    I am using a Dell Precision 370 Pentium 4, 3GHz with an NVIDIA Quadro NVS 280 graphics card IRQ 16 BIOS 4.34.20.79.08, but the same effect can be seen on several different computer configurations.

    Any suggestions?

    Best regards,
    G


    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Timers;
    using System.Drawing;
    using System.Drawing.Drawing2D;

    namespace WpfTest
    {
        public partial class Window1 : Window
        {
            static Window1 instance;
            public const int DrawTimerPeriod = 20; // ms
            public System.Drawing.Pen LinePen   { get; set; }
            public System.Drawing.Brush BackGroundBrush  { get; set; }
            int controlHeight;
            int controlWidth;
            System.Drawing.Graphics bitmapGraphics;
            WriteableBitmap drawingBitmap = null;
            Timer drawTimer = new Timer(DrawTimerPeriod);
            private delegate void DummyDelegate();

            public Window1()
            {
                InitializeComponent();
                instance = this;
                LinePen = new System.Drawing.Pen(System.Drawing.Color.Lime, 1.0f);
                BackGroundBrush = System.Drawing.Brushes.Black;

                // Setup the timer
                drawTimer.Elapsed += delegate
                {
                    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                        (DummyDelegate)delegate { TimerEvent(); });
                };
                drawTimer.Start();
            }

            private void UserControl_Loaded(object sender, RoutedEventArgs e)
            {
                UserControl_SizeChanged(sender, null);
            }

            private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                System.Windows.Media.Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
                double dpi_x = m.M11 * 96.0;
                double dpi_y = m.M22 * 96.0;
                controlWidth = (int)this.ActualWidth;
                controlHeight = (int)this.ActualHeight;
                drawingBitmap = new WriteableBitmap(controlWidth, controlHeight, dpi_x, dpi_y, PixelFormats.Pbgra32, null);
                bitmapImage.Source = drawingBitmap;
                System.Drawing.Bitmap b = new Bitmap(controlWidth, controlHeight, controlWidth * 4,
                    System.Drawing.Imaging.PixelFormat.Format32bppPArgb, drawingBitmap.BackBuffer);
                bitmapGraphics = System.Drawing.Graphics.FromImage(b);
                bitmapGraphics.SmoothingMode = SmoothingMode.HighQuality;
                bitmapGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
                bitmapGraphics.CompositingMode = CompositingMode.SourceOver;
                bitmapGraphics.CompositingQuality = CompositingQuality.HighQuality;

                // Fill the background
                drawingBitmap.Lock();
                bitmapGraphics.FillRectangle(BackGroundBrush, 0, 0, controlWidth, controlHeight);
                bitmapGraphics.Flush(System.Drawing.Drawing2D.FlushIntention.Flush);
                drawingBitmap.AddDirtyRect(new Int32Rect(0, 0, controlWidth, controlHeight));
                drawingBitmap.Unlock();
            }

            public void RenderStuff()
            {
                if (drawingBitmap != null)
                {
                    drawingBitmap.Lock();
                    bitmapGraphics.FillRectangle(BackGroundBrush, 10, 10, 20, 20);
                    drawingBitmap.AddDirtyRect(new Int32Rect(10, 10, 20, 20));
                    //drawingBitmap.AddDirtyRect(new Int32Rect(0, 0, 1199, 899));
                    drawingBitmap.Unlock();
                }
            }

            public static void TimerEvent()
            {
                instance.RenderStuff();
            }
        }
    }


    <Window x:Class="WpfTest.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="900" Width="1200" Background="Black" WindowStyle="None"
        Loaded="UserControl_Loaded" SnapsToDevicePixels="True" SizeChanged="UserControl_SizeChanged">
        <Grid>
            <Image Name="bitmapImage" SnapsToDevicePixels="True" />
        </Grid>
    </Window>

    Wednesday, July 01, 2009 2:58 PM

All replies

  • One quick thing to try:  use http://msdn.microsoft.com/en-us/library/system.windows.media.bitmapscalingmode.aspx to set RenderOptions.BitmapScalingMode="Linear" or even NearestNeighbor on your Image element.

    -Adam Smith [MSFT]
    Wednesday, July 01, 2009 6:44 PM
  • Thanks for your reply. Unfortunately it did not change anything. I tried Linear, LowQuality and NearestNeighbor and also changed some of the bitmapGraphics parameters as follows:

           private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                ...
                bitmapGraphics.SmoothingMode = SmoothingMode.None;
                bitmapGraphics.InterpolationMode = InterpolationMode.Low;
                bitmapGraphics.CompositingMode = CompositingMode.SourceCopy;
                bitmapGraphics.CompositingQuality = CompositingQuality.HighSpeed;

                // Set the bitmap scaling mode for the image to render faster.
                RenderOptions.SetBitmapScalingMode(bitmapImage, BitmapScalingMode.Linear);
                ...

    Cheers,
    G

    Thursday, July 02, 2009 8:35 AM
  • Can you run a CPU profiler on your app and see where the time is going?
    -Adam Smith [MS]
    Thursday, July 02, 2009 6:23 PM
  • OK, here are the results from the VS2008 profiler:

    Functions Causing Most Work

    [wpfgfx_v0300.dll]           94.09%        (10,825 samples)
    [mscorwks.dll]                 6.01%
    [mscoree.dll]                   5.52
    CLRStubOrUnknownAddress       5.40%
    WptTest.App.Main()                   5.39%

    Functions Doing Most Individual Work

    [wpfgfx_v0300.dll]           93.91%        (10,804 samples)
    [mscorwks.dll]                 6.01%
    [gdiplus.dll]                     1.37%
    CLRStubOrUnknownAddress     0.26%
    [ntdll.dll]                         0.23%

    Perhaps the painting should be done in some other way?

    Regards,
    G
    Friday, July 03, 2009 12:17 PM
  • I'll try to take a look at this later.

    Is this XP or Vista? Do you see any difference between the two?
    Tuesday, July 07, 2009 10:06 PM
  • I am using XP. I currently do not have Vista. I will find a Vista-installation and get back to you with the results after the summer holidays.

    We (a medium sized company) are in the process of selecting a platform and technology for a new generation of PC-based products. We are really interrested in finding out if MS VS2008, C# and WPF is a working combination or not for our products. It all boils down to the possibility to have a small portion of a large window updated frequently (like 50Hz) without consuming too much CPU power. Thats why we made the technology evaluation program listed above.

    The target systems will be XP and Windows 7

    Tuesday, July 14, 2009 12:55 PM
  • Thanks for reporting this as we do have a performance bug! 

    WriteableBitmap is implemented with two buffers. When you add a dirty rect, we copy that region from the back buffer to the front buffer and then the front buffer is supposed to update that same region to video memory but there's a bug and we're copying the entire front buffer to video memory. That's why a 10 x 10 update on a larger WB is taking longer.

    InteropBitmap only has one buffer so it saves the back to front copy, but there's still the "front" to video copy. If you had large dirty rects it will be faster but with small dirty rects it shouldn't be much faster.

    Performance should be better on Win7 than XP too.
    • Proposed as answer by Gustav Vasa Friday, December 18, 2009 1:49 PM
    Thursday, July 16, 2009 12:12 AM
  • Will this be fixed? Is there a workaround? Can we use some other way to frequently update a small portion of a large window (sometimes on quite slow computers) in WPF? Or should we use DirectX?

    Any help regarding which technology you can recommend would be very much appreciated.

    Thanks,
    G.

    Friday, July 17, 2009 12:02 PM
  • Yes, it will be fixed in .NET 4.0, our next release. The fix should make Beta 2 in fact. I don't know if or when there will be a 3.5 SP2 or what would be in it.

    There is no workaround, sorry.

    "Update a small portion of a large window" -- Does the entire window need to be a WriteableBitmap? The performance issue is the size of the WB. If this line drawing area is smaller part of the window then have a smaller WB and you might be good. If you do need to have a huge, high performance, writeable area inside of WPF, then D3DImage is also an option but you'll need to write some D3D code of course. Or you could consider tiling the big WB into multiple, smaller WBs.

    By "Or should we use DirectX?" do you mean forget WPF entirely and go DirectX + Win32? That's a big decision that depends upon a lot of things. First off, D3D isn't always faster than WPF. You can write slow D3D Code :). Well written D3D code will be faster but it comes at the price of having to write a ton more code since you're close to the metal. There's more tradeoffs than that but I'm sure you know what I mean.



    Tuesday, July 21, 2009 1:12 AM
  • Jordan,

    Did this fix to WriteableBitmap make it to Beta 2 ?

    I´m having smiliar problem. I am drawing live data on the screen and it gets very slow when maximizing the window.

    Thanks
    GH

    Thursday, December 10, 2009 12:22 PM
  • Yes, the fix made Beta 2. Can you describe your scenario more?
    Thursday, December 17, 2009 8:17 PM
  • Thank you for fixing the performance bug. I have tested the performance difference between VS2008 .NET 3.5 and VS2010 .NET 4.0 using the test code i submitted when starting this thread some time ago (see above). This time, Windows 7 was used. Using the small dirty rect (10 by 10 pixels) I get a clearly significant difference in CPU usage, as follows:

    3.5, large window (but not maximized): App 40% (dwm 20%)
    4.0, large window (but not maximized): App 4% (dwm 2%)

    3.5, maximized: App 10% (dwm 20%)
    4.0, maximized: App 4% (dwm 2%)

    However, using a dirty rect covering the entire window yields somewhat different results. In this case I get slightly lower CPU usage from the app using 3.5 than 4.0 in the case of a maximized window, although the difference is not much bigger than my estimation of the measurement precision (*). According to my measurements it looks like the maximized window actually performs better in 3.5 than in 4.0 as far as the application is concerned, but when taking the dwm load into account, 4.0 still performs better than 3.5.

    3.5, large window (but not maximized): App 40% (dwm 20%)
    4.0, large window (but not maximized): App 15% (dwm 2%)

    3.5, maximized: App 8% (dwm 20%)
    4.0, maximized: App 15% (dwm 5%) 

    As you can see the difference that can be seen for 3.5 between the maximized and the normal window is absent in .NET 4.0.


    (*) Method:
    These tests were not very advanced. I have used estimated mean values from the CPU usage table in the task manager. As far as I could tell from the CPU usage, the computer essentially did no other stuff during the tests. The mouse was not moved during the tests. With "large window" I mean a window covering virtually the entire screen, i.e. it was only marginally smaller than the maximized window. The taskbar was placed on a different screen (a two screen computer was used - but all tests were performed on screen one only and no windows crossed the screen boundery).

    Regards,
    G.
    • Proposed as answer by Gustav Vasa Friday, December 18, 2009 1:49 PM
    Friday, December 18, 2009 1:22 PM
  • The InteropBitmap´s use currenty only the member function "Invalidate" to indicate that the bitmap need to be re-read or updated.

    The disadvantage is, on the other hand, that the entire bitmap will be updated.

    The WriteableBitmap´s have the function "AddDirtyRect".

    Is also any similar function such as InvalidateRect for InteropBitmap´s planned for the next versions of .NET so that one can invalidate only a small portion if the screen?
    Wednesday, February 24, 2010 10:21 AM
  • Sunday, July 04, 2010 1:50 PM
  • Is this bug confirmed to be fixed in .Net 4?  I'm still seeing it in a simple test app using Win 7 x64 / .Net 4 Client Profile.

    Using Perforator, I found that the whole bitmap is getting invalidated no matter how big of a dirty rect I add.

    Sunday, August 08, 2010 7:53 PM
  • I can confirm that I'm also seeing the entire bitmap become invalidated in .NET 4 when calling WritePixels() even on a small area of the bitmap. Was this really not fixed?
    Monday, April 18, 2011 2:22 PM