Brushes.White slows graphics demo down
Asked Answered
H

2

8

Below is a (very naive) implementation of Conway's Game of Life in WPF. It's just a demo...

xaml:

<Window x:Class="wpf_conway_life_2013_05_19.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="500">
    <Grid>
        <Canvas Name="canvas"
            Width="auto"
            Height="auto" 
            HorizontalAlignment="Stretch"  
            VerticalAlignment="Stretch">
        </Canvas>
    </Grid>
</Window>

code behind:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace wpf_conway_life_2013_05_19
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var random = new Random();

            var data = new int[100, 100];

            var dataB = new int[100, 100];

            Func<int, int, int> at = (x, y) =>
                {
                    if (x < 0) x = 100 + x;
                    if (x >= 100) x = x % 100;
                    if (y < 0) y = 100 + y;
                    if (y >= 100) y = y % 100;

                    return data[x, y];
                };

            for (var x = 0; x < 100; x++)
                for (var y = 0; y < 100; y++)
                    data[x, y] = random.Next(2);

            var rectangles = new Rectangle[100, 100];

            for (var x = 0; x < 100; x++)
                for (var y = 0; y < 100; y++)
                {
                    rectangles[x, y] = new Rectangle();

                    canvas.Children.Add(rectangles[x, y]);
                }

            canvas.SizeChanged += (s, e) =>
                {
                    for (var x = 0; x < 100; x++)
                    {
                        for (var y = 0; y < 100; y++)
                        {
                            rectangles[x, y].Width = canvas.ActualWidth / 100;
                            rectangles[x, y].Height = canvas.ActualHeight / 100;

                            Canvas.SetLeft(rectangles[x, y], (canvas.ActualWidth / 100) * x);
                            Canvas.SetTop(rectangles[x, y], (canvas.ActualHeight / 100) * y);
                        }
                    }
                };

            Action macroStep = () =>
                {
                    dataB = new int[100, 100];

                    for (var x = 0; x < 100; x++)
                    {
                        for (var y = 0; y < 100; y++)
                        {
                            var neighbors = 0;

                            for (var i = -1; i <= 1; i++)
                                for (var j = -1; j <= 1; j++)
                                    if (i == 0 && j == 0)
                                        continue;
                                    else
                                        neighbors += at(x + i, y + j);

                            dataB[x, y] = data[x, y];

                            if (neighbors < 2) dataB[x, y] = 0;
                            if (neighbors == 3) dataB[x, y] = 1;
                            if (neighbors > 3) dataB[x, y] = 0;

                            rectangles[x, y].Fill = dataB[x, y] == 0 ? new SolidColorBrush(new Color()) : Brushes.Black;
                        }
                    }

                    data = dataB;
                };

            var timer = new DispatcherTimer();

            timer.Tick += (s, e) => macroStep();

            timer.Start();
        }
    }
}

Here's what it looks like:

enter image description here

If I replace new SolidColorBrush(new Color()) with Brushes.White the program runs much more slowly. Why?

I'm testing on Windows 7 64-bit using 2010 Express.

Hut answered 20/5, 2013 at 4:10 Comment(10)
To narrow down whether it's a rendering or a retrieval issue, if you keep a cached reference to the brush outside your loop and use that reference in place of Brushes.White, is the speed affected? System brushes are cached in a Dictionary<uint, SolidColorBrush>, which has to be locked each time an item is retrieved.Krafftebing
Do you get the same behavior with Brushes.Transparent?Richardo
@SimonMcKenzie Good suggestion. However, I tried it and it makes no difference. (By the way, congratulations on MapSnap; very cool WP7 app!)Hut
Thanks! You'll have to give it a go ;) Although I have no idea, I wonder if WPF parallelises some rendering, which could potentially be compromised if all items share the same brush. If you create the new brush instance outside the loop, is the performance the same?Krafftebing
@Richardo Brushes.Transparent is also slow. Funny thing is, it doesn't start out as slow as Brushes.White but after about 5 iterations, it becomes much slower.Hut
By the way, I assume this is just for fun, but for true performance you'd do much better using a WriteableBitmap...Krafftebing
@SimonMcKenzie I also would have guessed that storing a brush in a variable outside the loop would be the most efficient. But after testing various combinations of setting up the brushes, it seems the fastest is to create a new brush and color, i.e. new SolidColorBrush(new Color() {...}). It's very bizarre.Hut
@Richardo Thanks for the suggestion. It's bizarre but allocating a new SolidColorBrush each time seems to be the fastest approach. Would still love to know why though.Hut
Try creating new SolidColorBrush just once and Freeze it.Innes
@Innes Thanks for the suggestion. I gave that a shot but new SolidColorBrush(...) is still faster.Hut
F
1

Because new Color() has alpha value of zero, which means WPF doesn't have to render it because it's fully transparent - on the other hand White color's alpha is 255, which means it is completely solid white color which have to be rendered.

Fuzz answered 20/5, 2013 at 19:13 Comment(1)
Good suggestion Jaska. But if that were the case, then this would be just as slow: new SolidColorBrush(new Color() { R = 255, G = 255, B = 255, A = 255 }). However, it's not.Hut
S
0

There is nothing special about using Brushes.White.

If you define your own local brush outside the macroStep event handler, and then freeze it, it will behave exactly identical to using Brushes.White. If you don't freeze it first, it will behave far, far worse.

The best performance is to create your brush once at the beginning of each call to macroStep, before the loop, and then freeze it. It is a significant slowdown if you create a new brush inside the innermost loop.

Also, if you increase the interval on the timer for the badly behaving code, it will actually fix the performance issue. My guess is that there's some kind of resource cleanup that would occur on a background thread after it finishes rendering each time, that's tied to the internals of the brush, but it's starved from being able to do its cleanup because you're turning right around and using the brush in the next iteration. To demonstrate this, I created a pool of brushes, and use a different brush each time:

SolidColorBrush[] brushes = new SolidColorBrush[2];
for (int i = 0; i < brushes.Length; i++)
{
    var brush = new SolidColorBrush(new Color());
    brush.Freeze();
    brushes[i] = brush;
}
int brushIx = 0;

Action macroStep = () =>
{
    dataB = new int[100, 100];
    var brush = brushes[brushIx++ % brushes.Length];
...
    rectangles[x, y].Fill = dataB[x, y] == 0
        ? brush
        : Brushes.Black;
    data = dataB;
};

If you set the number of brushes to 1, this will give the same behavior as using Brushes.White. But if you set it to 2 or more, you'll get the performance you expect.

Start answered 4/6, 2013 at 2:49 Comment(2)
Thanks for the suggestion Bryce. Did you test this code on your system, comparing the different variations? On my system, creating a new brush in the inner loop is still faster than creating and freezing a brush, either outside or inside macroStep. It's very bizarre.Hut
@dharmatech, I've just tested all three cases - your original with "new SolidColorBrush(new Color())", the suggested by Bryce - with arrays of freezed Brushes and again you original with just Brushes.White. And on my machine the fastest was the second one - with arrays of Brushes (no surprise here). The slowest was the last one - with Brushes.White. And case with original "new SolidColorBrush(new Color())" was somewhere in the middle. It may be worth to add that difference between the winner and the second one though it was noticeable, but not as much as between the second and the third one.Alleras

© 2022 - 2024 — McMap. All rights reserved.