The Xbox GPU is a shading monster!
I've written several Mandelbrot viewers over the years, but this is the first time I've ever been able to move around this at a rock solid 60 frames per second:
The trick to making this fast is to do all the heavy lifting on the GPU. I'm computing the fractal entirely inside my pixel shader, using this Mandelbrot.fx effect file:
#define Iterations 128
float2 Pan;
float Zoom;
float Aspect;
float4 PixelShader(float2 texCoord : TEXCOORD0) : COLOR0
{
float2 c = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan;
float2 v = 0;
for (int n = 0; n < Iterations; n++)
{
v = float2(v.x * v.x - v.y * v.y, v.x * v.y * 2) + c;
}
return (dot(v, v) > 1) ? 1 : 0;
}
technique
{
pass
{
PixelShader = compile ps_3_0 PixelShader();
}
}
Since the GPU is doing all the work, my C# code is pretty simple. Starting with the default Xbox 360 Game project, you need to add a few fields:
Effect mandelbrot;
SpriteBatch spriteBatch;
Texture2D dummyTexture;
Vector2 pan = new Vector2(0.25f, 0);
float zoom = 3;
Add this to the LoadGraphicsContent method:
mandelbrot = content.Load<Effect>("Mandelbrot");
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
int w = graphics.GraphicsDevice.Viewport.Width;
int h = graphics.GraphicsDevice.Viewport.Height;
dummyTexture = new Texture2D(graphics.GraphicsDevice, w, h, 1,
ResourceUsage.None, SurfaceFormat.Color);
I'm using a bit of a trick here. To render my fractal, I want to draw a fullscreen quad using my custom pixel shader. SpriteBatch provides an easy way to draw fullscreen quads, but it expects to be given a source texture. I don't need any source texture for my fractal, but I'm creating the dummyTexture as a trick to keep SpriteBatch happy. Yes, that's a nasty hack, and I apologise for it :-)
My Update method uses the gamepad to zoom and pan the display:
GamePadState pad = GamePad.GetState(PlayerIndex.One);
if (pad.Buttons.A == ButtonState.Pressed)
zoom /= 1.05f;
if (pad.Buttons.B == ButtonState.Pressed)
zoom *= 1.05f;
float panSensitivity = 0.01f * (float)Math.Log(zoom + 1);
pan += new Vector2(pad.ThumbSticks.Left.X, -pad.ThumbSticks.Left.Y) * panSensitivity;
And finally, my Draw method issues a single SpriteBatch call to draw a fullscreen quad, using my Mandelbrot effect to apply all that massively parallel Xbox GPU goodness to every pixel of the screen:
GraphicsDevice device = graphics.GraphicsDevice;
float aspectRatio = (float)device.Viewport.Height / (float)device.Viewport.Width;
mandelbrot.Parameters["Pan"].SetValue(pan);
mandelbrot.Parameters["Zoom"].SetValue(zoom);
mandelbrot.Parameters["Aspect"].SetValue(aspectRatio);
spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
mandelbrot.Begin();
mandelbrot.CurrentTechnique.Passes[0].Begin();
spriteBatch.Draw(dummyTexture, Vector2.Zero, Color.White);
spriteBatch.End();
mandelbrot.CurrentTechnique.Passes[0].End();
mandelbrot.End();
This is not only the fastest Mandelbrot renderer I've ever written, but probably also the least code!