summaryrefslogtreecommitdiffstats
path: root/src/classes/com/sun/opengl/util/j2d
diff options
context:
space:
mode:
authorKenneth Russel <[email protected]>2007-01-13 22:21:13 +0000
committerKenneth Russel <[email protected]>2007-01-13 22:21:13 +0000
commit97910f24754333f5934dc7c5836d101d709550d0 (patch)
treed4cfb23c18f439ee16b9270cadde1b34a207ce8f /src/classes/com/sun/opengl/util/j2d
parent446556d8e8bf1f7e5365bf46deb192e346aa5025 (diff)
Robustness improvements to TextRenderer and underlying RectanglePacker.
Added ability to specify a maximum size to which the RectanglePacker can expand. Added preExpand method to BackingStoreManager to support early eviction of unused entries instead of always expanding the backing store, and additionFailed method used when the backing store can not expand further. Added more robust free list coalescing to Level. Added support for shrinking of backing store and for eager compaction when there is a lot of vertical dead space. Added TextFlow demo which shows how to do dynamic layout of text and which acts as a stress test for the TextRenderer. git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/svn-server-sync/jogl/trunk@1083 232f8b59-042b-4e1e-8c03-345bb8c30851
Diffstat (limited to 'src/classes/com/sun/opengl/util/j2d')
-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) {}
+ }
}