Posted by: Morten Nobel-Jørgensen | February 23, 2010

Real time mandelbrot in Java – Part 2 (JOGL)


Mandelbrot rendering

In this blog, I’ll show how to improve the rendering speed of the mandelbrot fractal using the GPU.

Improving rendering speed using GPU

You can do several tricks to improve the renderings speed, such as decreasing the size of the Mandelbrot image, minimizing the maximum number of iterations or use parallel programming to run multiple calculations on multiple CPU cores in parallel.

I have chosen to speed up the calculations using a GPU. Highend GPU have up to 512 floating point cores (such as GeForce GTX 480), where highend computers only have 8 cores available (such as in the Mac Pro). This means that calculating the Mandelbrot on the GPU gives an enormous performance boost.

To unleash to power of the GPU, I have chosen to used OpenGL’s shader (GLSL) using the Java binding for OpenGL (JOGL). The GLSL language is very close to C and is quite easy to learn. The fragment shader for Mandelbrot (the shader that calculates the color of each pixel) can be seen here:

uniform float mandel_x;
uniform float mandel_y;
uniform float mandel_width;
uniform float mandel_height;
uniform float mandel_iterations;

float calculateMandelbrotIterations(float x, float y) {
	float xx = 0.0;
	float yy = 0.0;
	float iter = 0.0;
	while (xx * xx + yy * yy <= 4.0 && iter<mandel_iterations) {
		float temp = xx*xx - yy*yy + x;
 		yy = 2.0*xx*yy + y;
		xx = temp;
 		iter ++;
 	}
 	return iter;
}

const vec3 blue = vec3(0.0,0.0,1.0);
const vec3 white = vec3(1.0,1.0,1.0);
const vec3 yellow = vec3(1.0,1.0,0.0);
const vec3 red = vec3(1.0,0.0,0.0);
const float colorResolution = 16.0; // how many iterations the first color band should use (2nd use the double amount)

vec3 getColorByIndex(float index){
	float i = mod(index,4.0);
	if (i<0.5){
 		return blue;
 	}
 	if (i<1.5){
 		return white;
 	}
 	if (i<2.5){
 		return yellow;
 	}
 	return red;
}

vec4 getColor(float iterations) {
	if (iterations==mandel_iterations){
		return vec4(0.0,0.0,0.0,1.0);
 	}
 	float colorIndex = 0.0;
 	float iterationsFloat = iterations;
 	float colorRes = colorResolution;
 	while (iterationsFloat>colorRes){
 		iterationsFloat -= colorRes;
 		colorRes = colorRes*2.0;
 		colorIndex ++;
 	}
 	float fraction = iterationsFloat/colorRes;
 	vec3 from = getColorByIndex(colorIndex);
 	vec3 to = getColorByIndex(colorIndex+1.0);
 	vec3 res = mix(from,to,fraction);
 	return vec4(res.x,res.y,res.z,1.0);
}

void main()
{
 	float x = mandel_x+gl_TexCoord[0].x*mandel_width;
 	float y = mandel_y+gl_TexCoord[0].y*mandel_height;
 	float iterations = calculateMandelbrotIterations(x,y);
 	gl_FragColor = getColor(iterations);
}

You might wonder – isn’t OpenGL meant for rendering 3D stuff only? The answer is yes. My Mandelbrot is actually in 3D, to make it look like the Java2D rendering I use an orthogonal camera (a 3D camera without perspective), and only draw a single Quad that fills the full view volume. Each vertex in the quad is assigned texture coordinates (0.0 – 1.0), these coordinates are interpolated by the graphics pipeline, so when the rendering a pixel (aka fragment) the fragment shader knows the right coordinate. The mandelbrot settings (current view and iterations) are send to the shader when updated. A simplified version of the display function looks like this:

// get memory address of uniform shader variables
int mandel_x = gl.glGetUniformLocation(shaderprogram, "mandel_x");
int mandel_y = gl.glGetUniformLocation(shaderprogram, "mandel_y");
int mandel_width = gl.glGetUniformLocation(shaderprogram, "mandel_width");
int mandel_height = gl.glGetUniformLocation(shaderprogram, "mandel_height");
int mandel_iterations = gl.glGetUniformLocation(shaderprogram, "mandel_iterations");
// set uniform shader variables
gl.glUniform1f(mandel_x, settings.getX());
gl.glUniform1f(mandel_y, settings.getY());
gl.glUniform1f(mandel_width, settings.getWidth());
gl.glUniform1f(mandel_height, settings.getHeight());
gl.glUniform1f(mandel_iterations, settings.getIterations());

// Reset the current matrix to the "identity"
gl.glLoadIdentity();

// Draw A Quad
gl.glBegin(GL.GL_QUADS);
{
	gl.glTexCoord2f(0.0f, 0.0f);
	gl.glVertex3f(0.0f, 1.0f, 1.0f);
	gl.glTexCoord2f(1.0f, 0.0f);
	gl.glVertex3f(1.0f, 1.0f, 1.0f);
	gl.glTexCoord2f(1.0f, 1.0f);
	gl.glVertex3f(1.0f, 0.0f, 1.0f);
	gl.glTexCoord2f(0.0f, 1.0f);
	gl.glVertex3f(0.0f, 0.0f, 1.0f);
 }
 // Done Drawing The Quad
 gl.glEnd();

Running the program

Simply launch the webstart application by clicking on the launch button below, and when the application is running, click the JOGL radiobutton. If your graphics card has GLSL shader and OpenGL support, you should see mandelbrot-fractal rendered blazing fast. On my machine it is around 10 times faster that the Java2D rendering!

Edit 23 okt. 2011: Since JOGL is no longer maintained at the java.net, there is currently no webstart demo available.

 

Full source code is available here:
http://www.nobel-joergensen.com/java/projects/mandelbrot_jogl/javamandelbrot_src.zip

Useful links:
JOGL (Java Bindings for OpenGL)
OpenGL Shading Language (3rd Edition)

Advertisement

Responses

  1. OpenGL is able to render pure 2D aswell :), and if you want to you can even just output raw pixel data (you want to do this), instead of what your doing, outputting raw pixel data to the frame buffer will speed things up, maybe you even what to just set it all up, and let a pixelshader handle it all? – and just render nothing, but one full plane, with a light source on, that happens to calculate mandelbrot using a shader 🙂

    • Hi Emil

      Thanks for your suggesting. However I’m not totally convinced that your idea gives a major performance boost. Currently I’m only rendering one quad – so it is really not the 3D rendering that takes time. Most of the workload in the current program happens in the fragment shader (also known as the pixel shader), that calculates the mandelbrot pixel (once per pixel on the scene) – and this computation needs to be done no matter what method used.
      If I’m wrong and you manage to create a faster mandelbrot program, please let me know 🙂

  2. Emil is wrong, using raw input data would be noticeably slower. Thank you for this nice example.

  3. Great example thank you very much.

  4. Do you have a version suitable for OpenGLES2?
    I tried updating the code and got it to compile but then got run-time errors.
    I was running on a Raspberry Pi. ~0.7fps in Java2D

    • I don’t have a version for OpenGL ES 2. If you need help with OpenGL ES 2, I suggest you post a question on StackOverflow and put a link in here (these blog comments are not good at finding problems).

  5. Great example, I did some code changes this evening to get it to run on Jogamp 2.3.2 and use a microsecond accuracy timing. Running the demo on my desktop (which uses Win 10 and a GeForce GTX 1050 card), I was staggered to notice the difference that Java2D was around 20FPS, and using shaders it went up to 20,000FPS (1,000 times the speed), using 128 iterations and movement speed 10.

    I’m a bit of a newbie to OpenGL, got the books and going through them, but wanted an example to help me along (as I had already previous made a mandelbrot renderer in Java2D, so was familiar with the concept). Thanks for this example !! Shaders are obviously the way to go if you need to get some serious grunt out of your machine when coding in Java and OpenGL.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Categories

%d bloggers like this: