summaryrefslogtreecommitdiffstats
path: root/src/classes/com/sun/opengl/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/classes/com/sun/opengl/util')
-rwxr-xr-xsrc/classes/com/sun/opengl/util/j2d/TextRenderer.java220
1 files changed, 194 insertions, 26 deletions
diff --git a/src/classes/com/sun/opengl/util/j2d/TextRenderer.java b/src/classes/com/sun/opengl/util/j2d/TextRenderer.java
index 027f857fe..7f47c011a 100755
--- a/src/classes/com/sun/opengl/util/j2d/TextRenderer.java
+++ b/src/classes/com/sun/opengl/util/j2d/TextRenderer.java
@@ -54,6 +54,13 @@ import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.impl.packrect.*;
+// For debugging purposes
+import java.awt.EventQueue;
+import java.awt.Frame;
+import java.awt.event.*;
+import com.sun.opengl.impl.*;
+import com.sun.opengl.util.*;
+
/** Renders bitmapped Java 2D text into an OpenGL window with high
performance, full Unicode support, and a simple API. Performs
appropriate caching of text rendering results in an OpenGL texture
@@ -94,11 +101,14 @@ import com.sun.opengl.impl.packrect.*;
*/
public class TextRenderer {
+ private static final boolean DEBUG = Debug.debug("TextRenderer");
+
private Font font;
private boolean antialiased;
private boolean useFractionalMetrics;
private RectanglePacker packer;
+ private boolean haveMaxSize;
private TextureRenderer cachedBackingStore;
private Graphics2D cachedGraphics;
private FontRenderContext cachedFontRenderContext;
@@ -116,8 +126,11 @@ public class TextRenderer {
// Every certain number of render cycles, flush the strings which
// haven't been used recently
- private static final int CYCLES_PER_FLUSH = 200;
+ private static final int CYCLES_PER_FLUSH = 100;
private int numRenderCycles;
+ // The amount of vertical dead space on the backing store before we
+ // force a compaction
+ private static final float MAX_VERTICAL_FRAGMENTATION = 0.7f;
// Current text color
private float r = 1.0f;
@@ -148,6 +161,9 @@ public class TextRenderer {
void clearUsed() { used = false; }
}
+ // Debugging purposes only
+ private boolean debugged;
+
/** Creates a new TextRenderer with the given font, using no
antialiasing or fractional metrics. Equivalent to
<code>TextRenderer(font, false, false)</code>.
@@ -214,6 +230,23 @@ public class TextRenderer {
return normalize(gv.getPixelBounds(frc, 0, 0));
}
+ /** Returns the Font this renderer is using. */
+ public Font getFont() {
+ return font;
+ }
+
+ /** Returns a FontRenderContext which can be used for external
+ text-related size computations. This object should be considered
+ transient and may become invalidated between {@link
+ #beginRendering beginRendering} / {@link #endRendering
+ endRendering} pairs. */
+ public FontRenderContext getFontRenderContext() {
+ if (cachedFontRenderContext == null) {
+ cachedFontRenderContext = getGraphics2D().getFontRenderContext();
+ }
+ return cachedFontRenderContext;
+ }
+
/** Begins rendering with this {@link TextRenderer TextRenderer}
into the current OpenGL drawable, pushing the projection and
modelview matrices and some state bits and setting up a
@@ -229,8 +262,22 @@ public class TextRenderer {
@throws GLException If an OpenGL context is not current when this method is called
*/
public void beginRendering(int width, int height) throws GLException {
+ if (DEBUG && !debugged) {
+ debug();
+ }
+
getBackingStore().beginOrthoRendering(width, height);
GL gl = GLU.getCurrentGL();
+
+ if (!haveMaxSize) {
+ // Query OpenGL for the maximum texture size and set it in the
+ // RectanglePacker to keep it from expanding too large
+ int[] sz = new int[1];
+ gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, sz, 0);
+ packer.setMaxSize(sz[0], sz[0]);
+ haveMaxSize = true;
+ }
+
// Change texture environment mode to MODULATE
gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
// Change text color to last saved
@@ -344,24 +391,10 @@ public class TextRenderer {
getBackingStore().endOrthoRendering();
if (++numRenderCycles >= CYCLES_PER_FLUSH) {
numRenderCycles = 0;
- final List/*<Rect>*/ deadRects = new ArrayList/*<Rect>*/();
- // Iterate through the contents of the backing store, removing
- // text strings that haven't been used recently
- packer.visit(new RectVisitor() {
- public void visit(Rect rect) {
- TextData data = (TextData) rect.getUserData();
- if (data.used()) {
- data.clearUsed();
- } else {
- deadRects.add(rect);
- }
- }
- });
- for (Iterator iter = deadRects.iterator(); iter.hasNext(); ) {
- Rect r = (Rect) iter.next();
- packer.remove(r);
- stringLocations.remove(((TextData) r.getUserData()).string());
+ if (DEBUG) {
+ System.err.println("Clearing unused entries in endRendering()");
}
+ clearUnusedEntries();
}
}
@@ -421,13 +454,6 @@ public class TextRenderer {
return cachedGraphics;
}
- private FontRenderContext getFontRenderContext() {
- if (cachedFontRenderContext == null) {
- cachedFontRenderContext = getGraphics2D().getFontRenderContext();
- }
- return cachedFontRenderContext;
- }
-
private int getSpaceWidth() {
if (spaceWidth < 0) {
Graphics2D g = getGraphics2D();
@@ -470,7 +496,48 @@ public class TextRenderer {
}
}
- static class Manager implements BackingStoreManager {
+ private void clearUnusedEntries() {
+ final List/*<Rect>*/ deadRects = new ArrayList/*<Rect>*/();
+ // Iterate through the contents of the backing store, removing
+ // text strings that haven't been used recently
+ packer.visit(new RectVisitor() {
+ public void visit(Rect rect) {
+ TextData data = (TextData) rect.getUserData();
+ if (data.used()) {
+ data.clearUsed();
+ } else {
+ deadRects.add(rect);
+ }
+ }
+ });
+ for (Iterator iter = deadRects.iterator(); iter.hasNext(); ) {
+ Rect r = (Rect) iter.next();
+ packer.remove(r);
+ stringLocations.remove(((TextData) r.getUserData()).string());
+
+ if (DEBUG) {
+ Graphics2D g = getGraphics2D();
+ g.setColor(TRANSPARENT_BLACK);
+ g.fillRect(r.x(), r.y(), r.w(), r.h());
+ g.setColor(Color.WHITE);
+ }
+ }
+
+ // If we removed dead rectangles this cycle, try to do a compaction
+ float frag = packer.verticalFragmentationRatio();
+ if (!deadRects.isEmpty() && frag > MAX_VERTICAL_FRAGMENTATION) {
+ if (DEBUG) {
+ System.err.println("Compacting TextRenderer backing store due to vertical fragmentation " + frag);
+ }
+ packer.compact();
+ }
+
+ if (DEBUG) {
+ getBackingStore().sync(0, 0, getBackingStore().getWidth(), getBackingStore().getHeight());
+ }
+ }
+
+ class Manager implements BackingStoreManager {
private Graphics2D g;
public Object allocateBackingStore(int w, int h) {
@@ -479,6 +546,9 @@ public class TextRenderer {
// store (i.e., non-default Paint, foreground color, etc.), but
// for now, let's just be more efficient
TextureRenderer renderer = TextureRenderer.createAlphaOnlyRenderer(w, h);
+ if (DEBUG) {
+ System.err.println(" TextRenderer allocating backing store " + w + " x " + h);
+ }
return renderer;
}
@@ -486,6 +556,43 @@ public class TextRenderer {
((TextureRenderer) backingStore).dispose();
}
+ public boolean preExpand(Rect cause, int attemptNumber) {
+ // Only try this one time; clear out potentially obsolete entries
+
+ // NOTE: this heuristic and the fact that it clears the used bit
+ // of all entries seems to cause cycling of entries in some
+ // situations, where the backing store becomes small compared to
+ // the amount of text on the screen (see the TextFlow demo) and
+ // the entries continually cycle in and out of the backing
+ // store, decreasing performance. If we added a little age
+ // information to the entries, and only cleared out entries
+ // above a certain age, this behavior would be eliminated.
+ // However, it seems the system usually stabilizes itself, so
+ // for now we'll just keep things simple. Note that if we don't
+ // clear the used bit here, the backing store tends to increase
+ // very quickly to its maximum size, at least with the TextFlow
+ // demo when the text is being continually re-laid out.
+ if (attemptNumber == 0) {
+ if (DEBUG) {
+ System.err.println("Clearing unused entries in preExpand(): attempt number " + attemptNumber);
+ }
+ clearUnusedEntries();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void additionFailed(Rect cause, int attemptNumber) {
+ // Heavy hammer -- might consider doing something different
+ packer.clear();
+ stringLocations.clear();
+
+ if (DEBUG) {
+ System.err.println(" *** Cleared all text because addition failed ***");
+ }
+ }
+
public void beginMovement(Object oldBackingStore, Object newBackingStore) {
TextureRenderer newRenderer = (TextureRenderer) newBackingStore;
g = newRenderer.createGraphics();
@@ -523,4 +630,65 @@ public class TextRenderer {
newRenderer.sync(0, 0, newRenderer.getWidth(), newRenderer.getHeight());
}
}
+
+ //----------------------------------------------------------------------
+ // Debugging functionality
+ //
+
+ private void debug() {
+ Frame dbgFrame = new Frame("TextRenderer Debug Output");
+ GLCanvas dbgCanvas = new GLCanvas(new GLCapabilities(), null, GLContext.getCurrent(), null);
+ dbgCanvas.addGLEventListener(new DebugListener(dbgFrame));
+ dbgFrame.add(dbgCanvas);
+ final FPSAnimator anim = new FPSAnimator(dbgCanvas, 10);
+ dbgFrame.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() {
+ anim.stop();
+ }
+ }).start();
+ }
+ });
+ dbgFrame.setSize(256, 256);
+ dbgFrame.setVisible(true);
+ anim.start();
+ debugged = true;
+ }
+
+ class DebugListener implements GLEventListener {
+ private GLU glu = new GLU();
+ private Frame frame;
+
+ DebugListener(Frame frame) {
+ this.frame = frame;
+ }
+
+ public void display(GLAutoDrawable drawable) {
+ GL gl = drawable.getGL();
+ gl.glClear(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT);
+ TextureRenderer rend = getBackingStore();
+ final int w = rend.getWidth();
+ final int h = rend.getHeight();
+ rend.beginOrthoRendering(w, h);
+ rend.drawOrthoRect(0, 0);
+ rend.endOrthoRendering();
+ if (frame.getWidth() != w ||
+ frame.getHeight() != h) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ frame.setSize(w, h);
+ }
+ });
+ }
+ }
+
+ // Unused methods
+ public void init(GLAutoDrawable drawable) {}
+ public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {}
+ public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
+ }
}