# Sixty fractals per second

### Sixty fractals per second

Rate This

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;`

`    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!

• wow, good stuff ... I always hear about people who got their start programming by typing out code from magazines.  Then I hear said people lamenting the fact that programming is so complex these days that it would never fit in a magazine for the next generation of programmers to learn from.

This however, could easily be printed in a magazine :-D

• 2006-12-13 &dagger; 2006-12-13 Microsoft Robotic Studio 1.0 ???????? XNA Game Studio Express 1.0 &uarr;Microsoft Robotic Studio 1.0 &dagger; ?????????????????????????? ?????????????????????????? http://msdn.microsoft.com/robotics/downl...

• Looking back In the beginning there are darkness..., damn stop , too far back Looking back to before...

• Very nice example, and easy to play with. Here is an example of where the shader uses the iteration counts to create a grayscale image.

float4 PixelShader(float2 texCoord : TEXCOORD0) : COLOR0

{

float2 c = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan;

float2 v = 0;

int n = 0;

for (n = 0; n < Iterations && dot(v,v)<4; n++)

{

v = float2(v.x * v.x - v.y * v.y, v.x * v.y * 2) + c;

}

return (dot(v, v) > 4) ? (float)n / (float)Iterations : 0;

}

• Any ideas why I get this error?

System.InvalidOperationException

Both a valid vertex shader and  pixel shader (or valid effect) must be set on the device before draw operations may be performed.

Running on XP btw, haven't tried 360

• Hunter767,

it seems like your graphics card doesn't support 3.0 pixel shaders. I had the exact same problem, and it took me a while to figure it out (the exception message is not very helpful)..

Try to change the "compile ps_3_0" directive (last line in the fx file) to ps_2_a or even ps_2_0. You'll also have to decrease the number of iterations (first line of the fx file). The maximum number of iterations on my machine is 32, and even then it's painfully slow - time to get a new video card I guess..

• Having received the same System.InvalidOperationException error, I'm slightly puzzled as my graphic card is pixel shader 3.0 capable.  To double check this, I've interrogated the MaxPixelShaderProfile GraphicsDeviceCapability of my graphics adapter which correctly returns PS_3_0.

Having said this, if I drop to PS_2_a and 64 iterations (as suggested by mamue) it runs up without error.

As a result, and since I’m new to this type of development work, I wondered if anyone could provide any wisdom as to why the Pixel Shader 3 setting isn't playing ball.

• Well this was an odd experience. Search google for XNA fractals, get an example working, toggle back to the webpage you were reading to see if there is anything else interesting here and realise that you're my best friend's (Ben) brother! Surreal is probably the best word for it.

Thanks for a great article though.

• ResouceUsage.none?

I take it by the date this is for older versions of xna. I imagine TextureUsage replaces resouce usage?

I changed it to TextureUsage but all I get is a black screen sadly.

• Yes, ResourceUsage should be changed to TextureUsage in more recent versions of the XNA Framework.

I haven't tried this code recently, but I would expect it to still work. Are you running this on Xbox or PC? (if you're on PC, you may have to change the shader model to ps_2_0).

• Hi !

Here my version of the "Orbit Trap" Mandebrot (with color!).

You have to modify the .cs to make it work (i added Iterations as parameters instead of a #define).

fractals.s3.amazonaws.com/XNAMandelbrot2.jpg

---------------------

float2 Pan;

float Zoom;

float Aspect;

int Iterations;

float4 PixelShader(float4 texCoord : TEXCOORD0) : COLOR0

{

float2 c = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan;

float2 v = 0;

float4 result;

int it = 0;

float minV = 8;

float minVx = 8;

float minVy = 8;

result.r = 0; result.g = 0; result.b = 0; result.a = 0;

while((it < Iterations) && (dot(v, v) < 32))

{

v = float2(v.x * v.x - v.y * v.y, v.x * v.y * 2) + c;

if(length(v) < minV) { minV = length(v); }

if(abs(v.x) < minVx) { minVx = abs(v.x); }

if(abs(v.y) < minVy) { minVy = abs(v.y); }

it = it +1;

}

if(it != Iterations)

{

//result = minVx + minVy;

result.r = (minVx);

result.g = (minVy);

result.b = (minV);

} else {

result.r = (minVx);

result.g = (minVy);

result.b = (minV);

}

return result;

}

technique

{

pass

{

}

}

• How would you convert the part in the Draw() method to XNA 4.0? I'm currently stuck.

Tried looping over the CurrentTechnique for passes and apply them accordingly, nothing happens though.

• Shawn great blog, keep it up!

*Zolomon*: I made a version for XNA 4.0: see www.trancetrance.com/.../mandel , have a look at the code.

• since the above link is dead.

For the FX: mandelbrotEffect = Content.Load<Effect>("Mandelbrot");

//Content/Mandelbrot.fx

#define Iterations 52

float2 Pan;

float Zoom;

float Aspect;

float4x4 MatrixTransform;

void SpriteVertexShader(inout float4 color    : COLOR0,

inout float2 texCoord : TEXCOORD0,

inout float4 position : SV_Position)

{    position = mul(position, MatrixTransform); }

float4 Pixel_Shader(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

{

}

}

and for the draw

protected override void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.Black);

Matrix projection = Matrix.CreateOrthographicOffCenter(0, spriteBatch.GraphicsDevice.Viewport.Width, spriteBatch.GraphicsDevice.Viewport.Height, 0, 0, 1);

Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);

mandelbrotEffect.Parameters["MatrixTransform"].SetValue(halfPixelOffset * projection);

float aspectRatio = (float)GraphicsDevice.Viewport.Height / (float)GraphicsDevice.Viewport.Width;

mandelbrotEffect.Parameters["Pan"].SetValue(pan);

mandelbrotEffect.Parameters["Zoom"].SetValue(zoom);

mandelbrotEffect.Parameters["Aspect"].SetValue(aspectRatio);

spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);    // immediate required

mandelbrotEffect.CurrentTechnique.Passes[0].Apply();

spriteBatch.Draw(dummyTexture, Vector2.Zero, Color.Black);

spriteBatch.End();

base.Draw(gameTime);

}

Hope it saves someone some time.

• [loop] for (int n = 0; n < Iterations; n++)

{

v = float2(v.x * v.x - v.y * v.y, v.x * v.y * 2) + c;

}

allows us to declare an iteration value up to 254.

Page 1 of 1 (15 items)