Better Living Through Blitting

Our cooking/coding/kick-ass little group, Freshly Coded, has a graphics wizard: Nick. He is the magic behind all the nice UI and crazy-fast graphics you’ll see over on our joints project page. Normally I stick to developer-driven testing, refactoring / architecture, and regular coding but this time I’m going to try doing some graphics coding myself … everyone cross their fingers.

It may be easier to forget what you know about WPF and just start “fresh” with the Windows 8 Xaml classes. We still have WriteableBitmap (there’s no InteropBitmap) but its usage is a bit strange. Further, it’s not immediately obvious how you can write to the buffer. Finally, here are our starting conditions:

  • Each pixel on the screen is represented by 32 bits (or 4 bytes) in BGRA format.
  • Each cell is a 5x5 pixel square.
  • The entire game is a grid of 96x96 cell.

Probably the best place to begin is with CellMapDisplay. It needs to change from using a Canvas and Rectangles to a WriteableBitmap that’s connected to an on-screen Image. Along with the WriteableBitmap, we need a buffer to store the pixels we're going to display and some helper methods to draw the cells themselves.

public class CellMapDisplay
{
    private readonly WriteableBitmap bitmap;
    private readonly uint numberCellsAcross;
    private readonly uint numberCellsDown;
    private readonly int cellSize;

    public byte[] cells;
    private int bytesPerCellLine;

    public CellMapDisplay(Image image, uint numberCellsAcross, 
                          uint numberCellsDown, int cellSize)
    {
        bitmap = new WriteableBitmap(
                              (int)numberCellsAcross * cellSize, 
                              (int)numberCellsDown * cellSize);
        image.Source = bitmap;

        this.numberCellsAcross = numberCellsAcross;
        this.numberCellsDown = numberCellsDown;
        this.cellSize = cellSize;

        bytesPerCellLine = (int)numberCellsAcross * cellSize * 
                                cellSize * 4;

        cells = new byte[bytesPerCellLine * numberCellsDown];

        for (int x = 0; x < cells.Length; x += 4)
        {
            cells[x] = 0;
            cells[x + 1] = 0;
            cells[x + 2] = 0;
            cells[x + 3] = 0xff;
        }
    }

    public void DrawCell(uint x, uint y, bool on)
    {
        byte value = (byte)(on ? 1 : 0);

        var lineLeft = bytesPerCellLine * y + (x * cellSize * 4);

        for (int celly = 0; celly < cellSize; celly++)
        {
            for (int cellx = 0; cellx < cellSize; cellx++)
            {
                var pixel = lineLeft + (cellx * 4);

                cells[pixel] = value;
                cells[pixel + 1] = value;
                cells[pixel + 2] = value;
                cells[pixel + 3] = 0xFF;
            }

            lineLeft += (int)numberCellsAcross * cellSize * 4;
        }
    }

    public async void UpdateScreen()
    {
        using (var stream = bitmap.PixelBuffer.AsStream())
        {
            await stream.WriteAsync(cells, 0, cells.Length);
        }

        bitmap.Invalidate();
    }
}

In case you're trying this and wonder why bitmap.PixelBuffer.AsStream() doesn't seem to compile, the extension method AsStream() resides in System.Runtime.InteropServices.WindowsRuntime — something that isn't immediately obvious in the documentation.

The changes to CellMap (call UpdateScreen at the start of NextGeneration) and MainPage (pass the Image rather than the Canvas) are straightforward and we are ready to run again.

And ... wow!

image

This is an order of magnitude faster.

What’s even funnier (?) is now the drawing is so fast that it’s the same order of magnitude as updating our generation counter.

image

I think we’re in a pretty good state to start exploring Windows 8-specific code so I’ll wrap up this section (for now). Next time, we tackle Live Tiles.

(Code so far)


comments powered by Disqus