TextureRenderer performance in OS X

Hello All,

Recently I’ve been playing with the TextureRenderer object (btw, it is great), but performance in OS X is simply terrible. I am mostly using it to write Java2D text animations into textures for OpenGL, but the difference in performance between OS X and XP is huge. For you to get a sense of the kinds of things I am trying to do, go here:

http://matadata.com/everyword_applet.html

That example is plain Java2D.

Imagine a lower resolution version of that turned into a texture. In XP, a 512x512 box of animated text flies by, but in OS X it crawls. I’ve tried using PBO’s, but the difference in performance is similar. I’ve tried to benchmark the code, and it seems that in OS X the text rendering (in Java) takes forever when used in conjunction with JOGL. Basically, rendering text in Graphics2D slows to a crawl in GL. Is there any particular reason why? My hunch is that this has to do with the way the VM in OS X renders in Java; perhaps Java2D is rendering in GL and there is some fighting over the resources.
,
I am testing in a MacBook Pro, so XP and OS X configs are the same (Radeon X1600, Core Duo 2 2.16).

Any thoughts?

thanks.

c.

It’s hard for me to make a guess. I’ve seen drastic performance differences on OS X between various graphics cards. For example, JOGL’s GLJPanel actually performs pretty well on a Mac Mini with Intel Integrated graphics, but I’ve seen it perform terribly on other OS X machines with far superior graphics cards. I suspect the bottleneck may involve how efficiently the Java implementation on your machine can render to a BufferedImage.

If you can boil things down to a small test case illustrating the performance difference between Windows and OS X on the same hardware and/or between two different OS X machines with different graphics hardware, I may be able to help you file a bug report with Apple.

Note that if you’re using JOGL’s TextRenderer there are currently performance problems rendering a lot of dynamic text. John Burkey formerly from Apple is working on a glyph-by-glyph caching implementation which will solve these issues.

Sorry for jumping in, but I’d be curious to know how you create the BufferedImage? What method did you use, and what format did you specify (if applicable).

The reason I’m asking is because I’ve noticed mediocre performance with drawing RGBA type BIs, at least in Java 5 (order of magnitude slower than BGRA). I know this isn’t quite what you’re doing but maybe the underlying problem (swizzling) is the same?

Ok, here is a test case. Let me know if you see something funny in the code… I am no expert.

This test case shows (at least on my machine) that it is not only text rendering that is slow. It is actually anything that is rendered using Java2D. As soon as it dips into any Graphics calls, things slow down considerably.

The program will create a TextureRenderer object. You can manipulate the size of the texture by altering the texW and texH fields. In the display method the TextureRenderer will get the Graphics2D object, clear the g2 background to blue, draw 200 white, randomly position circles in it and then use the resulting texture in a GL QUAD. You should see a blue quad with white dots in it, on a black background. In XP, as it should, the white dots flicker very fast. On OS X with the same config (ATI X1600), things are decidedly on the slow side.

thanks


package test;

import static javax.media.opengl.GL.GL_COLOR_BUFFER_BIT;
import static javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT;
import static javax.media.opengl.GL.GL_MODELVIEW;
import static javax.media.opengl.GL.GL_PROJECTION;

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

import com.sun.opengl.util.j2d.TextureRenderer;
import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureCoords;

/**
 * Testing Graphics2D and TextureRenderer
 * @author cmendoza
 *
 */
public class TestCase extends GLCanvas implements GLEventListener {

	protected GL gl;
	protected GLU glu = new GLU();
	protected int width, height;
	protected String name;
	TextureRenderer tr;
	Texture texture;
	int texW, texH;
	private Object thisTime;
	private long lastTime;

	public TestCase(int width, int height, String name) {
		super();

		this.setSize(width, height);
		
		this.width = width;
		this.height = height;
		this.name = name;
		this.addGLEventListener(this);
		
		//this is the size of the texture rendered with Java2D
		texH = texW = 512;

		tr = new TextureRenderer(texW, texH, false, false);

	}

	public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2) {
		// TODO Auto-generated method stub

	}

	public void init(GLAutoDrawable drawable) {
		GL gl = drawable.getGL();


		gl.glEnable(GL.GL_BLEND); 
		//gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE_MINUS_SRC_ALPHA); 
		gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE_MINUS_SRC_ALPHA); 
		gl.glEnable(GL.GL_SMOOTH);
		gl.glEnable(GL.GL_POINT_SMOOTH); 
		gl.glHint(GL.GL_POINT_SMOOTH_HINT, GL.GL_NICEST); 
		gl.glEnable(GL.GL_LINE_SMOOTH);
		gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST);
		gl.glEnable(GL.GL_POLYGON_SMOOTH);
		gl.glHint(GL.GL_POLYGON_SMOOTH_HINT, GL.GL_NICEST);

		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glDepthFunc(GL.GL_LESS);
		gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
		
		gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
		gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
		gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
		
		gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE);
		
		gl.glClearColor(0, 0, 0, 1);

		texture = tr.getTexture();
	}


	public void reshape(GLAutoDrawable drawable,
			int x, int y, int width, int height) {
		gl = drawable.getGL();

		gl.glViewport(0, 0, width, height);
		gl.glMatrixMode(GL_PROJECTION);
		gl.glLoadIdentity();
		
			double aspectRatio = (double)width / (double)height;
			glu.gluPerspective(45.0, aspectRatio, 1f, 400.0);
		
		gl.glMatrixMode(GL_MODELVIEW);
		gl.glLoadIdentity();
	}


	public void display(GLAutoDrawable drawable) {
		
		thisTime = System.currentTimeMillis() - lastTime;
		lastTime = System.currentTimeMillis();
		
		System.out.println("Frame time: " + thisTime );
		gl = drawable.getGL();
		gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		gl.glMatrixMode(GL_MODELVIEW);
		gl.glLoadIdentity();
		gl.glTranslatef(0f, 0f, -5f);

		Graphics2D g2 = tr.createGraphics();
		g2.setColor(Color.WHITE);
		g2.setBackground(Color.blue);
		g2.clearRect(0, 0, texW, texH);
		
		
		//to test Graphics2D I am drawing N circles randomly in the Graphics object
		//this particular config gives me about 600ms per frame in OS X, but it is much quicker in XP (realtime in terms of animation)
		for(int i = 0; i < 200; i++) { //try increasing the number of iterations to get a more dramatic result...
			g2.fillOval(randomRange(0, texW), randomRange(0, texH), 20, 20);
		}
		
		g2.dispose();
		
		tr.markDirty(0, 0, texW, texH);
		texture = tr.getTexture();
		texture.enable();
		texture.bind();

		TextureCoords coords = texture.getImageTexCoords();


		float w = 1.5f;
		//yeah, whatever...
		float h = (float)texH / (float)texW;
		h = h * w;

		gl.glColor4f(1, 0, 0, 1);

		gl.glBegin(GL.GL_QUADS);
		gl.glTexCoord2f(coords.left(), coords.bottom());
		gl.glVertex3f(-w, -h, 1);
		gl.glTexCoord2f(coords.right(), coords.bottom());
		gl.glVertex3f(w, -h, 1);
		gl.glTexCoord2f(coords.right(), coords.top());
		gl.glVertex3f(w, h, 1);
		gl.glTexCoord2f(coords.left(), coords.top());
		gl.glVertex3f(-w, h, 1);
		gl.glEnd();

//		texture.disable();

	}

	public int randomRange(int low, int high) {
		if(low > high){
			int temp = low;
			low = high;
			high = temp;
		}

		int ran = (int)(Math.random() * (high - low /*+1*/) + low);
		//System.out.println("high was " + high + " low was " + low + " rand was " + ran);
		return ran;
	}	
	
	public void launch(){

		Frame frame = new Frame(name);
		frame.add(this);
		
		frame.setSize(width, height);
		frame.setLocationRelativeTo(null);

		final com.sun.opengl.util.Animator animator =
			new com.sun.opengl.util.Animator(this);
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				// Run this on another thread than the AWT event queue to
				// make sure the call to Animator.stop() completes before
				// exiting
				new Thread(new Runnable() {
					public void run() {
						animator.stop();
						System.exit(0);
					}
				}).start();
			}
		});
		frame.setVisible(true);
		animator.start();
	}

	public static void main(String[] args){
		new TestCase(1024, 768, "test").launch();
	}
}


Oh, and as far as the BGR vs. RGB:
I was using TextureRenderer, so the BI is created for me. I believe it is implemented as TYPE_RGB.

In any case, the time difference is gigantic, much too large to be just a swizzling issue. We are talking an order of magnitude +. And I tried doing it manually, experimenting with the different pixel types, but no luck.

That’s what I thought, but re-read my earlier reply, it was an order of magnitude slower. However, if I understand you correctly, you’ve also tried manually creating a BI using various formats - same poor performance?

I dunno but you might get better performance if you draw into a “compatible” (Component.createImage()) image and blit that into the BI owned by the TextureRenderer. This would obviously incur an overhead (the extra blit) but might be offset by the faster image format.

Yeah, I tried manually, and unless I am doing something wrong, I get the same terrible performance. And I must be doing it sort of right, since in XP it flies. I tried different pixel format combos in OS X, but performance is still terrible.

Can anyone confirm this, or is it just me and my poor coding?