diff options
author | Kenneth Russel <[email protected]> | 2007-01-13 22:21:13 +0000 |
---|---|---|
committer | Kenneth Russel <[email protected]> | 2007-01-13 22:21:13 +0000 |
commit | 97910f24754333f5934dc7c5836d101d709550d0 (patch) | |
tree | d4cfb23c18f439ee16b9270cadde1b34a207ce8f /src | |
parent | 446556d8e8bf1f7e5365bf46deb192e346aa5025 (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')
5 files changed, 450 insertions, 84 deletions
diff --git a/src/classes/com/sun/opengl/impl/packrect/BackingStoreManager.java b/src/classes/com/sun/opengl/impl/packrect/BackingStoreManager.java index ba4b0cf30..0c4636063 100755 --- a/src/classes/com/sun/opengl/impl/packrect/BackingStoreManager.java +++ b/src/classes/com/sun/opengl/impl/packrect/BackingStoreManager.java @@ -49,16 +49,37 @@ public interface BackingStoreManager { public Object allocateBackingStore(int w, int h); public void deleteBackingStore(Object backingStore); - // Notification that movement is starting + /** Notification that expansion of the backing store is about to be + done due to addition of the given rectangle. Gives the manager a + chance to do some compaction and potentially remove old entries + from the backing store, if it acts like a least-recently-used + cache. This method receives as argument the number of attempts + so far to add the given rectangle. Manager should return true if + the RectanglePacker should retry the addition (which may result + in this method being called again, with an increased attempt + number) or false if the RectanglePacker should just expand the + backing store. The caller should not call RectanglePacker.add() + in its preExpand() method. */ + public boolean preExpand(Rect cause, int attemptNumber); + + /** Notification that addition of the given Rect failed because a + maximum size was set in the RectanglePacker and the backing + store could not be expanded. */ + public void additionFailed(Rect cause, int attemptNumber); + + /** Notification that movement is starting. */ public void beginMovement(Object oldBackingStore, Object newBackingStore); - // Can the backing stores be identical? I think so, in the case of - // compacting the existing backing store as opposed to reallocating it... + /** Tells the manager to move the contents of the given rect from + the old location on the old backing store to the new location on + the new backing store. The backing stores can be identical in + the case of compacting the existing backing store instead of + reallocating it. */ public void move(Object oldBackingStore, Rect oldLocation, Object newBackingStore, Rect newLocation); - // Notification that movement is ending + /** Notification that movement is ending. */ public void endMovement(Object oldBackingStore, Object newBackingStore); } diff --git a/src/classes/com/sun/opengl/impl/packrect/Level.java b/src/classes/com/sun/opengl/impl/packrect/Level.java index e7eab9ca7..12a09cd9a 100755 --- a/src/classes/com/sun/opengl/impl/packrect/Level.java +++ b/src/classes/com/sun/opengl/impl/packrect/Level.java @@ -129,8 +129,7 @@ public class Level { freeList.add(candidate); } - if (freeList.isEmpty()) - freeList = null; + coalesceFreeList(); return true; } @@ -148,38 +147,22 @@ public class Level { // to the free list, we can just decrease the nextAddX if (rect.maxX() + 1 == nextAddX) { nextAddX -= rect.w(); - - // Now try to coalesce additional free space at the end of the - // free list - if (freeList != null) { - boolean found = true; - while (found) { - found = false; - for (Iterator iter = freeList.iterator(); iter.hasNext(); ) { - Rect cur = (Rect) iter.next(); - if (cur.maxX() + 1 == nextAddX) { - nextAddX -= cur.w(); - found = true; - freeList.remove(cur); - break; - } - } - } - if (freeList.isEmpty()) - freeList = null; + } else { + if (freeList == null) { + freeList = new ArrayList/*<Rect>*/(); } - - return true; + freeList.add(new Rect(rect.x(), rect.y(), rect.w(), height, null)); + coalesceFreeList(); } - // Else, add the space consumed by this rectangle to the free list - if (freeList == null) { - freeList = new ArrayList/*<Rect>*/(); - } - freeList.add(new Rect(rect.x(), rect.y(), rect.w(), height, null)); return true; } + /** Indicates whether this Level contains no rectangles. */ + public boolean isEmpty() { + return rects.isEmpty(); + } + /** Indicates whether this Level could satisfy an allocation request if it were compacted. */ public boolean couldAllocateIfCompacted(Rect rect) { @@ -211,6 +194,7 @@ public class Level { nextCompactionDest += cur.w(); } nextAddX = nextCompactionDest; + freeList.clear(); manager.endMovement(backingStore, backingStore); } @@ -240,4 +224,52 @@ public class Level { rects.set(i, next); } } + + private void coalesceFreeList() { + if (freeList == null) + return; + if (freeList.isEmpty()) + return; + + // Try to coalesce adjacent free blocks in the free list + Collections.sort(freeList, rectXComparator); + int i = 0; + while (i < freeList.size() - 1) { + Rect r1 = (Rect) freeList.get(i); + Rect r2 = (Rect) freeList.get(i+1); + if (r1.maxX() + 1 == r2.x()) { + // Coalesce r1 and r2 into one block + freeList.remove(i+1); + r1.setSize(r1.w() + r2.w(), r1.h()); + } else { + ++i; + } + } + // See whether the last block bumps up against the addition point + Rect last = (Rect) freeList.get(freeList.size() - 1); + if (last.maxX() + 1 == nextAddX) { + nextAddX -= last.w(); + freeList.remove(freeList.size() - 1); + } + if (freeList.isEmpty()) { + freeList = null; + } + } + + //---------------------------------------------------------------------- + // Debugging functionality + // + + public void dumpFreeSpace() { + int freeListWidth = 0; + for (Iterator iter = freeList.iterator(); iter.hasNext(); ) { + Rect cur = (Rect) iter.next(); + System.err.println(" Free rectangle at " + cur); + freeListWidth += cur.w(); + } + // Add on the remaining space at the end + System.err.println(" Remaining free space " + (width - nextAddX)); + freeListWidth += (width - nextAddX); + System.err.println(" Total free space " + freeListWidth); + } } diff --git a/src/classes/com/sun/opengl/impl/packrect/LevelSet.java b/src/classes/com/sun/opengl/impl/packrect/LevelSet.java index 561a3fe14..97a1f2e74 100755 --- a/src/classes/com/sun/opengl/impl/packrect/LevelSet.java +++ b/src/classes/com/sun/opengl/impl/packrect/LevelSet.java @@ -149,6 +149,39 @@ public class LevelSet { nextAddY += (newHeight - oldHeight); } + /** Gets the used height of the levels in this LevelSet. */ + public int getUsedHeight() { + return nextAddY; + } + + /** Sets the height of this LevelSet. It is only legal to reduce the + height to greater than or equal to the currently used height. */ + public void setHeight(int height) throws IllegalArgumentException { + if (height < getUsedHeight()) { + throw new IllegalArgumentException("May not reduce height below currently used height"); + } + h = height; + } + + /** Returns the vertical fragmentation ratio of this LevelSet. This + is defined as the ratio of the sum of the heights of all + completely empty Levels divided by the overall used height of + the LevelSet. A high vertical fragmentation ratio indicates that + it may be profitable to perform a compaction. */ + public float verticalFragmentationRatio() { + int freeHeight = 0; + int usedHeight = getUsedHeight(); + if (usedHeight == 0) + return 0.0f; + for (Iterator iter = iterator(); iter.hasNext(); ) { + Level level = (Level) iter.next(); + if (level.isEmpty()) { + freeHeight += level.h(); + } + } + return (float) freeHeight / (float) usedHeight; + } + public Iterator iterator() { return levels.iterator(); } @@ -171,4 +204,10 @@ public class LevelSet { level.updateRectangleReferences(); } } + + /** Clears out all Levels stored in this LevelSet. */ + public void clear() { + levels.clear(); + nextAddY = 0; + } } diff --git a/src/classes/com/sun/opengl/impl/packrect/RectanglePacker.java b/src/classes/com/sun/opengl/impl/packrect/RectanglePacker.java index 453f5e1e2..6057f7bb9 100755 --- a/src/classes/com/sun/opengl/impl/packrect/RectanglePacker.java +++ b/src/classes/com/sun/opengl/impl/packrect/RectanglePacker.java @@ -52,6 +52,13 @@ public class RectanglePacker { private Object backingStore; private LevelSet levels; private float EXPANSION_FACTOR = 0.5f; + private float SHRINK_FACTOR = 0.3f; + + private int initialWidth; + private int initialHeight; + + private int maxWidth = -1; + private int maxHeight = -1; static class RectHComparator implements Comparator { public int compare(Object o1, Object o2) { @@ -71,6 +78,8 @@ public class RectanglePacker { int initialHeight) { this.manager = manager; levels = new LevelSet(initialWidth, initialHeight); + this.initialWidth = initialWidth; + this.initialHeight = initialHeight; } public Object getBackingStore() { @@ -81,6 +90,16 @@ public class RectanglePacker { return backingStore; } + /** Sets up a maximum width and height for the backing store. These + are optional and if not specified the backing store will grow as + necessary. Setting up a maximum width and height introduces the + possibility that additions will fail; these are handled with the + BackingStoreManager's allocationFailed notification. */ + public void setMaxSize(int maxWidth, int maxHeight) { + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + } + /** Decides upon an (x, y) position for the given rectangle (leaving its width and height unchanged) and places it on the backing store. May provoke re-layout of other Rects already added. */ @@ -89,29 +108,89 @@ public class RectanglePacker { if (backingStore == null) backingStore = manager.allocateBackingStore(levels.w(), levels.h()); - // Try to allocate - if (levels.add(rect)) - return; + int attemptNumber = 0; + boolean tryAgain = false; + + do { + // Try to allocate + if (levels.add(rect)) + return; + + // Try to allocate with horizontal compaction + if (levels.compactAndAdd(rect, backingStore, manager)) + return; + + // Let the manager have a chance at potentially evicting some entries + tryAgain = manager.preExpand(rect, attemptNumber++); + } while (tryAgain); + + compactImpl(rect); + + // Retry the addition of the incoming rectangle + add(rect); + // Done + } + + /** Removes the given rectangle from this RectanglePacker. */ + public void remove(Rect rect) { + levels.remove(rect); + } + + /** Visits all Rects contained in this RectanglePacker. */ + public void visit(RectVisitor visitor) { + levels.visit(visitor); + } - // Try to allocate with compaction - if (levels.compactAndAdd(rect, backingStore, manager)) - return; + /** Returns the vertical fragmentation ratio of this + RectanglePacker. This is defined as the ratio of the sum of the + heights of all completely empty Levels divided by the overall + used height of the LevelSet. A high vertical fragmentation ratio + indicates that it may be profitable to perform a compaction. */ + public float verticalFragmentationRatio() { + return levels.verticalFragmentationRatio(); + } - // Have to expand. Need to figure out what direction to go. Prefer - // to expand vertically. Expand horizontally only if rectangle - // being added is too wide. FIXME: may want to consider - // rebalancing the width and height to be more equal if it turns - // out we keep expanding in the vertical direction. + /** Forces a compaction cycle, which typically results in allocating + a new backing store and copying all entries to it. */ + public void compact() { + compactImpl(null); + } + + // The "cause" rect may be null + private void compactImpl(Rect cause) { + // Have to either expand, compact or both. Need to figure out what + // direction to go. Prefer to expand vertically. Expand + // horizontally only if rectangle being added is too wide. FIXME: + // may want to consider rebalancing the width and height to be + // more equal if it turns out we keep expanding in the vertical + // direction. boolean done = false; int newWidth = levels.w(); int newHeight = levels.h(); LevelSet nextLevelSet = null; + int attemptNumber = 0; + boolean needAdditionFailureNotification = false; + while (!done) { - if (rect.w() > newWidth) { - newWidth = rect.w(); - } else { - newHeight = (int) (newHeight * (1.0f + EXPANSION_FACTOR)); + if (cause != null) { + if (cause.w() > newWidth) { + newWidth = cause.w(); + } else { + newHeight = (int) (newHeight * (1.0f + EXPANSION_FACTOR)); + } } + + // Clamp to maximum values + needAdditionFailureNotification = false; + if (maxWidth > 0 && newWidth > maxWidth) { + newWidth = maxWidth; + needAdditionFailureNotification = true; + } + if (maxHeight > 0 && newHeight > maxHeight) { + newHeight = maxHeight; + needAdditionFailureNotification = true; + } + nextLevelSet = new LevelSet(newWidth, newHeight); // Make copies of all existing rectangles @@ -138,7 +217,42 @@ public class RectanglePacker { break; } } + + if (done && cause != null) { + // Try to add the new rectangle as well + if (nextLevelSet.add(cause)) { + // We're OK + } else { + done = false; + } + } + + // Don't send addition failure notifications if we're only doing + // a compaction + if (!done && needAdditionFailureNotification && cause != null) { + manager.additionFailed(cause, attemptNumber); + } + ++attemptNumber; } + + // See whether the implicit compaction that just occurred has + // yielded excess empty space. + if (nextLevelSet.getUsedHeight() > 0 && + nextLevelSet.getUsedHeight() < nextLevelSet.h() * SHRINK_FACTOR) { + int shrunkHeight = Math.max(initialHeight, + (int) (nextLevelSet.getUsedHeight() * (1.0f + EXPANSION_FACTOR))); + if (maxHeight > 0 && shrunkHeight > maxHeight) { + shrunkHeight = maxHeight; + } + nextLevelSet.setHeight(shrunkHeight); + } + + // If we temporarily added the new rectangle to the new LevelSet, + // take it out since we don't "really" add it here but in add(), above + if (cause != null) { + nextLevelSet.remove(cause); + } + // OK, now we have a new layout and a mapping from the old to the // new locations of rectangles on the backing store. Allocate a // new backing store, move the contents over and deallocate the @@ -162,19 +276,11 @@ public class RectanglePacker { // Update to new versions of backing store and LevelSet backingStore = newBackingStore; levels = nextLevelSet; - // Retry the addition of the incoming rectangle - add(rect); - // Done } - /** Removes the given rectangle from this RectanglePacker. */ - public void remove(Rect rect) { - levels.remove(rect); - } - - /** Visits all Rects contained in this RectanglePacker. */ - public void visit(RectVisitor visitor) { - levels.visit(visitor); + /** Clears all Rects contained in this RectanglePacker. */ + public void clear() { + levels.clear(); } /** Disposes the backing store allocated by the 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) {} + } } |