diff options
author | Kenneth Russel <[email protected]> | 2007-01-04 07:20:30 +0000 |
---|---|---|
committer | Kenneth Russel <[email protected]> | 2007-01-04 07:20:30 +0000 |
commit | e4daa5d745f3d084e0c7180a88597b2582131b63 (patch) | |
tree | 3e5b35a4b3ec92a74abaa11228fabca32e11eae0 /src | |
parent | 10c76681d1507fa527b59cb28bcde5d1c6a4fe18 (diff) |
With extensive help from Phil Race and Chris Campbell from the Java 2D
team, added a TextRenderer class which enables high-performance
rendering of bitmapped Java 2D fonts, with full Unicode support, into
arbitrary OpenGL drawables using a simple API. Builds on top of the
TextureRenderer class and its associated functionality; added
createAlphaOnlyRenderer() and other methods to enable the TextRenderer.
Caches rendering results on a string-by-string basis in an OpenGL
texture using a fully automatic least-recently-used algorithm for good
efficiency when rendering the same string or strings multiple times.
Uses a rectangle packing algorithm, currently housed in
com.sun.opengl.impl.packrect, for managing the positions of cached
strings on a larger OpenGL texture to avoid OpenGL pipeline state
changes. Added a TestTextRenderer demo which simply adds a moving text
string and frames-per-second counter to the Gears demo; more
sophisticated examples to come. (Some commented-out debugging code is
being temporarily left in the new demo, to be removed in the next
checkin, in order to have it in the version history.)
git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/svn-server-sync/jogl/trunk@1067 232f8b59-042b-4e1e-8c03-345bb8c30851
Diffstat (limited to 'src')
9 files changed, 1476 insertions, 5 deletions
diff --git a/src/classes/com/sun/opengl/impl/packrect/BackingStoreManager.java b/src/classes/com/sun/opengl/impl/packrect/BackingStoreManager.java new file mode 100755 index 000000000..ba4b0cf30 --- /dev/null +++ b/src/classes/com/sun/opengl/impl/packrect/BackingStoreManager.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.impl.packrect; + +/** This interface must be implemented by the end user and is called + in response to events like addition of rectangles into the + RectanglePacker. It is used both when a full re-layout must be + done as well as when the data in the backing store must be copied + to a new one. */ + +public interface BackingStoreManager { + public Object allocateBackingStore(int w, int h); + public void deleteBackingStore(Object backingStore); + + // 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... + public void move(Object oldBackingStore, + Rect oldLocation, + Object newBackingStore, + Rect newLocation); + + // 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 new file mode 100755 index 000000000..e7eab9ca7 --- /dev/null +++ b/src/classes/com/sun/opengl/impl/packrect/Level.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.impl.packrect; + +import java.util.*; + +public class Level { + private int width; + private int height; + private int yPos; + private LevelSet holder; + + private List/*<Rect>*/ rects = new ArrayList/*<Rect>*/(); + private List/*<Rect>*/ freeList; + private int nextAddX; + + static class RectXComparator implements Comparator { + public int compare(Object o1, Object o2) { + Rect r1 = (Rect) o1; + Rect r2 = (Rect) o2; + return r1.x() - r2.x(); + } + + public boolean equals(Object obj) { + return this == obj; + } + } + private static final Comparator rectXComparator = new RectXComparator(); + + public Level(int width, int height, int yPos, LevelSet holder) { + this.width = width; + this.height = height; + this.yPos = yPos; + this.holder = holder; + } + + public int w() { return width; } + public int h() { return height; } + public int yPos() { return yPos; } + + /** Tries to add the given rectangle to this level only allowing + non-disruptive changes like trivial expansion of the last level + in the RectanglePacker and allocation from the free list. More + disruptive changes like compaction of the level must be + requested explicitly. */ + public boolean add(Rect rect) { + if (rect.h() > height) { + // See whether it's worth trying to expand vertically + if (nextAddX + rect.w() > width) { + return false; + } + + // See whether we're the last level and can expand + if (!holder.canExpand(this, rect.h())) { + return false; + } + + // Trivially expand and try the allocation + holder.expand(this, height, rect.h()); + height = rect.h(); + } + + // See whether we can add at the end + if (nextAddX + rect.w() <= width) { + rect.setPosition(nextAddX, yPos); + rects.add(rect); + nextAddX += rect.w(); + return true; + } + + // See whether we can add from the free list + if (freeList != null) { + Rect candidate = null; + for (Iterator iter = freeList.iterator(); iter.hasNext(); ) { + Rect cur = (Rect) iter.next(); + if (cur.canContain(rect)) { + candidate = cur; + break; + } + } + + if (candidate != null) { + // Remove the candidate from the free list + freeList.remove(candidate); + // Set up and add the real rect + rect.setPosition(candidate.x(), candidate.y()); + rects.add(rect); + // Re-add any remaining free space + if (candidate.w() > rect.w()) { + candidate.setPosition(candidate.x() + rect.w(), candidate.y()); + candidate.setSize(candidate.w() - rect.w(), height); + freeList.add(candidate); + } + + if (freeList.isEmpty()) + freeList = null; + + return true; + } + } + + return false; + } + + /** Removes the given Rect from this Level. */ + public boolean remove(Rect rect) { + if (!rects.remove(rect)) + return false; + + // If this is the rightmost rectangle, instead of adding its space + // 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; + } + + return true; + } + + // 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 could satisfy an allocation request + if it were compacted. */ + public boolean couldAllocateIfCompacted(Rect rect) { + if (rect.h() > height) + return false; + if (freeList == null) + return false; + int freeListWidth = 0; + for (Iterator iter = freeList.iterator(); iter.hasNext(); ) { + Rect cur = (Rect) iter.next(); + freeListWidth += cur.w(); + } + // Add on the remaining space at the end + freeListWidth += (width - nextAddX); + return (freeListWidth >= rect.w()); + } + + public void compact(Object backingStore, BackingStoreManager manager) { + Collections.sort(rects, rectXComparator); + int nextCompactionDest = 0; + manager.beginMovement(backingStore, backingStore); + for (Iterator iter = rects.iterator(); iter.hasNext(); ) { + Rect cur = (Rect) iter.next(); + if (cur.x() != nextCompactionDest) { + manager.move(backingStore, cur, + backingStore, new Rect(nextCompactionDest, cur.y(), cur.w(), cur.h(), null)); + cur.setPosition(nextCompactionDest, cur.y()); + } + nextCompactionDest += cur.w(); + } + nextAddX = nextCompactionDest; + manager.endMovement(backingStore, backingStore); + } + + public Iterator iterator() { + return rects.iterator(); + } + + /** Visits all Rects contained in this Level. */ + public void visit(RectVisitor visitor) { + for (Iterator iter = rects.iterator(); iter.hasNext(); ) { + Rect rect = (Rect) iter.next(); + visitor.visit(rect); + } + } + + /** Updates the references to the Rect objects in this Level with + the "next locations" of those Rects. This is actually used to + update the new Rects in a newly laid-out LevelSet with the + original Rects. */ + public void updateRectangleReferences() { + for (int i = 0; i < rects.size(); i++) { + Rect cur = (Rect) rects.get(i); + Rect next = cur.getNextLocation(); + next.setPosition(cur.x(), cur.y()); + if (cur.w() != next.w() || cur.h() != next.h()) + throw new RuntimeException("Unexpected disparity in rectangle sizes during updateRectangleReferences"); + rects.set(i, next); + } + } +} diff --git a/src/classes/com/sun/opengl/impl/packrect/LevelSet.java b/src/classes/com/sun/opengl/impl/packrect/LevelSet.java new file mode 100755 index 000000000..561a3fe14 --- /dev/null +++ b/src/classes/com/sun/opengl/impl/packrect/LevelSet.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.impl.packrect; + +import java.util.*; + +/** Manages a list of Levels; this is the core data structure + contained within the RectanglePacker and encompasses the storage + algorithm for the contained Rects. */ + +public class LevelSet { + // Maintained in sorted order by increasing Y coordinate + private List/*<Level>*/ levels = new ArrayList/*<Level>*/(); + private int nextAddY; + private int w; + private int h; + + /** A LevelSet manages all of the backing store for a region of a + specified width and height. */ + public LevelSet(int w, int h) { + this.w = w; + this.h = h; + } + + public int w() { return w; } + public int h() { return h; } + + /** Returns true if the given rectangle was successfully added to + the LevelSet given its current dimensions, false if not. Caller + is responsible for performing compaction, expansion, etc. as a + consequence. */ + public boolean add(Rect rect) { + if (rect.w() > w) + return false; + + // Go in reverse order through the levels seeing whether we can + // trivially satisfy the allocation request + for (int i = levels.size() - 1; i >= 0; --i) { + Level level = (Level) levels.get(i); + if (level.add(rect)) + return true; + } + + // See whether compaction could satisfy this allocation. This + // increases the computational complexity of the addition process, + // but prevents us from expanding unnecessarily. + for (int i = levels.size() - 1; i >= 0; --i) { + Level level = (Level) levels.get(i); + if (level.couldAllocateIfCompacted(rect)) + return false; + } + + // OK, we need to either add a new Level or expand the backing + // store. Try to add a new Level. + if (nextAddY + rect.h() > h) + return false; + + Level newLevel = new Level(w, rect.h(), nextAddY, this); + levels.add(newLevel); + nextAddY += rect.h(); + boolean res = newLevel.add(rect); + if (!res) + throw new RuntimeException("Unexpected failure in addition to new Level"); + return true; + } + + /** Removes the given Rect from this LevelSet. */ + public boolean remove(Rect rect) { + for (int i = levels.size() - 1; i >= 0; --i) { + Level level = (Level) levels.get(i); + if (level.remove(rect)) + return true; + } + + return false; + } + + /** Allocates the given Rectangle, performing compaction of a Level + if necessary. This is the correct fallback path to {@link + #add(Rect)} above. Returns true if allocated successfully, false + otherwise (indicating the need to expand the backing store). */ + public boolean compactAndAdd(Rect rect, + Object backingStore, + BackingStoreManager manager) { + for (int i = levels.size() - 1; i >= 0; --i) { + Level level = (Level) levels.get(i); + if (level.couldAllocateIfCompacted(rect)) { + level.compact(backingStore, manager); + boolean res = level.add(rect); + if (!res) + throw new RuntimeException("Unexpected failure to add after compaction"); + return true; + } + } + + return false; + } + + /** Indicates whether it's legal to trivially increase the height of + the given Level. This is only possible if it's the last Level + added and there's enough room in the backing store. */ + public boolean canExpand(Level level, int height) { + if (levels.isEmpty()) + return false; // Should not happen + if (levels.get(levels.size() - 1) == level && + (h - nextAddY >= height - level.h())) + return true; + return false; + } + + public void expand(Level level, int oldHeight, int newHeight) { + nextAddY += (newHeight - oldHeight); + } + + public Iterator iterator() { + return levels.iterator(); + } + + /** Visits all Rects contained in this LevelSet. */ + public void visit(RectVisitor visitor) { + for (Iterator iter = levels.iterator(); iter.hasNext(); ) { + Level level = (Level) iter.next(); + level.visit(visitor); + } + } + + /** Updates the references to the Rect objects in this LevelSet with + the "next locations" of those Rects. This is actually used to + update the new Rects in a newly laid-out LevelSet with the + original Rects. */ + public void updateRectangleReferences() { + for (Iterator iter = levels.iterator(); iter.hasNext(); ) { + Level level = (Level) iter.next(); + level.updateRectangleReferences(); + } + } +} diff --git a/src/classes/com/sun/opengl/impl/packrect/Rect.java b/src/classes/com/sun/opengl/impl/packrect/Rect.java new file mode 100755 index 000000000..f47660e94 --- /dev/null +++ b/src/classes/com/sun/opengl/impl/packrect/Rect.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.impl.packrect; + +/** Represents a rectangular region on the backing store. The edges of + the rectangle are the infinitely thin region between adjacent + pixels on the screen. The origin of the rectangle is its + upper-left corner. It is inclusive of the pixels on the top and + left edges and exclusive of the pixels on the bottom and right + edges. For example, a rect at position (0, 0) and of size (1, 1) + would include only the pixel at (0, 0). <P> + + Negative coordinates and sizes are not supported, since they make + no sense in the context of the packer, which deals only with + positively sized regions. <P> + + This class contains a user data field for efficient hookup to + external data structures as well as enough other hooks to + efficiently plug into the rectangle packer. */ + +public class Rect { + private int x; + private int y; + private int w; + private int h; + + // The level we're currently installed in in the parent + // RectanglePacker, or null if not hooked in to the table yet + private Level level; + + // The user's object this rectangle represents. + private Object userData; + + // Used transiently during re-layout of the backing store (when + // there is no room left due either to fragmentation or just being + // out of space) + private Rect nextLocation; + + public Rect() { + this(null); + } + + public Rect(Object userData) { + this(0, 0, 0, 0, userData); + } + + public Rect(int x, int y, int w, int h, Object userData) { + setPosition(x, y); + setSize(w, h); + setUserData(userData); + } + + public int x() { return x; } + public int y() { return y; } + public int w() { return w; } + public int h() { return h; } + public Object getUserData() { return userData; } + public Rect getNextLocation() { return nextLocation; } + + public void setPosition(int x, int y) { + if (x < 0) + throw new IllegalArgumentException("Negative x"); + if (y < 0) + throw new IllegalArgumentException("Negative y"); + this.x = x; + this.y = y; + } + + public void setSize(int w, int h) throws IllegalArgumentException { + if (w < 0) + throw new IllegalArgumentException("Negative width"); + if (h < 0) + throw new IllegalArgumentException("Negative height"); + this.w = w; + this.h = h; + } + + public void setUserData(Object obj) { userData = obj; } + public void setNextLocation(Rect nextLocation) { this.nextLocation = nextLocation; } + + // Helpers for computations. + + /** Returns the maximum x-coordinate contained within this + rectangle. Note that this returns a different result than Java + 2D's rectangles; for a rectangle of position (0, 0) and size (1, + 1) this will return 0, not 1. Returns -1 if the width of this + rectangle is 0. */ + public int maxX() { + if (w() < 1) + return -1; + return x() + w() - 1; + } + + /** Returns the maximum y-coordinate contained within this + rectangle. Note that this returns a different result than Java + 2D's rectangles; for a rectangle of position (0, 0) and size (1, + 1) this will return 0, not 1. Returns -1 if the height of this + rectangle is 0. */ + public int maxY() { + if (h() < 1) + return -1; + return y() + h() - 1; + } + + public boolean canContain(Rect other) { + return (w() >= other.w() && + h() >= other.h()); + } + + public String toString() { + return "[Rect x: " + x() + " y: " + y() + " w: " + w() + " h: " + h() + "]"; + } + + // Unclear whether it's a good idea to override hashCode and equals + // for these objects + /* + public boolean equals(Object other) { + if (other == null || (!(other instanceof Rect))) { + return false; + } + + Rect r = (Rect) other; + return (this.x() == r.x() && + this.y() == r.y() && + this.w() == r.w() && + this.h() == r.h()); + } + + public int hashCode() { + return (x + y * 13 + w * 17 + h * 23); + } + */ +} diff --git a/src/classes/com/sun/opengl/impl/packrect/RectVisitor.java b/src/classes/com/sun/opengl/impl/packrect/RectVisitor.java new file mode 100755 index 000000000..6474f204e --- /dev/null +++ b/src/classes/com/sun/opengl/impl/packrect/RectVisitor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.impl.packrect; + +/** Iteration construct without exposing the internals of the + RectanglePacker and without implementing a complex Iterator. */ + +public interface RectVisitor { + public void visit(Rect rect); +} diff --git a/src/classes/com/sun/opengl/impl/packrect/RectanglePacker.java b/src/classes/com/sun/opengl/impl/packrect/RectanglePacker.java new file mode 100755 index 000000000..453f5e1e2 --- /dev/null +++ b/src/classes/com/sun/opengl/impl/packrect/RectanglePacker.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.impl.packrect; + +import java.util.*; + +/** Packs rectangles supplied by the user (typically representing + image regions) into a larger backing store rectangle (typically + representing a large texture). Supports automatic compaction of + the space on the backing store, and automatic expansion of the + backing store, when necessary. */ + +public class RectanglePacker { + private BackingStoreManager manager; + private Object backingStore; + private LevelSet levels; + private float EXPANSION_FACTOR = 0.5f; + + static class RectHComparator implements Comparator { + public int compare(Object o1, Object o2) { + Rect r1 = (Rect) o1; + Rect r2 = (Rect) o2; + return r2.h() - r1.h(); + } + + public boolean equals(Object obj) { + return this == obj; + } + } + private static final Comparator rectHComparator = new RectHComparator(); + + public RectanglePacker(BackingStoreManager manager, + int initialWidth, + int initialHeight) { + this.manager = manager; + levels = new LevelSet(initialWidth, initialHeight); + } + + public Object getBackingStore() { + if (backingStore == null) { + backingStore = manager.allocateBackingStore(levels.w(), levels.h()); + } + + return backingStore; + } + + /** 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. */ + public void add(Rect rect) { + // Allocate backing store if we don't have any yet + if (backingStore == null) + backingStore = manager.allocateBackingStore(levels.w(), levels.h()); + + // Try to allocate + if (levels.add(rect)) + return; + + // Try to allocate with compaction + if (levels.compactAndAdd(rect, backingStore, manager)) + return; + + // 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. + boolean done = false; + int newWidth = levels.w(); + int newHeight = levels.h(); + LevelSet nextLevelSet = null; + while (!done) { + if (rect.w() > newWidth) { + newWidth = rect.w(); + } else { + newHeight = (int) (newHeight * (1.0f + EXPANSION_FACTOR)); + } + nextLevelSet = new LevelSet(newWidth, newHeight); + + // Make copies of all existing rectangles + List/*<Rect>*/ newRects = new ArrayList/*<Rect>*/(); + for (Iterator i1 = levels.iterator(); i1.hasNext(); ) { + Level level = (Level) i1.next(); + for (Iterator i2 = level.iterator(); i2.hasNext(); ) { + Rect cur = (Rect) i2.next(); + Rect newRect = new Rect(0, 0, cur.w(), cur.h(), null); + cur.setNextLocation(newRect); + // Hook up the reverse mapping too for easier replacement + newRect.setNextLocation(cur); + newRects.add(newRect); + } + } + // Sort them by decreasing height (note: this isn't really + // guaranteed to improve the chances of a successful layout) + Collections.sort(newRects, rectHComparator); + // Try putting all of these rectangles into the new level set + done = true; + for (Iterator iter = newRects.iterator(); iter.hasNext(); ) { + if (!nextLevelSet.add((Rect) iter.next())) { + done = false; + break; + } + } + } + // 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 + // old one. + Object newBackingStore = manager.allocateBackingStore(nextLevelSet.w(), + nextLevelSet.h()); + manager.beginMovement(backingStore, newBackingStore); + for (Iterator i1 = levels.iterator(); i1.hasNext(); ) { + Level level = (Level) i1.next(); + for (Iterator i2 = level.iterator(); i2.hasNext(); ) { + Rect cur = (Rect) i2.next(); + manager.move(backingStore, cur, + newBackingStore, cur.getNextLocation()); + } + } + // Replace references to temporary rectangles with original ones + nextLevelSet.updateRectangleReferences(); + manager.endMovement(backingStore, newBackingStore); + // Now delete the old backing store + manager.deleteBackingStore(backingStore); + // 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); + } + + /** Disposes the backing store allocated by the + BackingStoreManager. This RectanglePacker may no longer be used + after calling this method. */ + public void dispose() { + manager.deleteBackingStore(backingStore); + backingStore = null; + levels = null; + } +} diff --git a/src/classes/com/sun/opengl/impl/packrect/package.html b/src/classes/com/sun/opengl/impl/packrect/package.html new file mode 100755 index 000000000..7f2522244 --- /dev/null +++ b/src/classes/com/sun/opengl/impl/packrect/package.html @@ -0,0 +1,7 @@ +This package implements a rectangle packing algorithm suitable for +tracking the placement of multiple rectangles inside a larger one. It +is useful for cases such as placing the contents of multiple windows +on a larger backing store texture for a compositing window manager; +placing multiple rasterized strings in a texture map for quick +rendering to the screen; and many other situations where it is useful +to carve up a larger texture into smaller pieces dynamically. <P> diff --git a/src/classes/com/sun/opengl/util/j2d/TextRenderer.java b/src/classes/com/sun/opengl/util/j2d/TextRenderer.java new file mode 100755 index 000000000..75fc5ae70 --- /dev/null +++ b/src/classes/com/sun/opengl/util/j2d/TextRenderer.java @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.util.j2d; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.font.*; +import java.awt.geom.*; +import java.util.*; + +import javax.media.opengl.*; +import javax.media.opengl.glu.*; +import com.sun.opengl.impl.packrect.*; + +/** 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 + internally to avoid repeated font rasterization. The caching is + completely automatic, does not require any user intervention, and + has no visible controls in the public API. <P> + + Using the {@link TextRenderer TextRenderer} is simple. Add a + "<code>TextRenderer renderer;</code>" field to your {@link + GLEventListener GLEventListener}. In your {@link + GLEventListener#init init} method, add: + +<PRE> + renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36)); +</PRE> + + <P> In the {@link GLEventListener#display display} method of your + {@link GLEventListener GLEventListener}, add: +<PRE> + renderer.beginRendering(drawable.getWidth(), drawable.getHeight()); + // optionally set the color + renderer.setColor(1.0f, 0.2f, 0.2f, 0.8f); + renderer.draw("Text to draw", xPosition, yPosition); + // ... more draw commands, color changes, etc. + renderer.endRendering(); +</PRE> + + Internally, the renderer uses a rectangle packing algorithm to + pack multiple full Strings' rendering results (which are variable + size) onto a larger OpenGL texture. The internal backing store is + maintained using a {@link TextureRenderer TextureRenderer}. A + least recently used (LRU) algorithm is used to discard previously + rendered strings; the specific algorithm is undefined, but is + currently implemented by flushing unused Strings' rendering + results every few hundred rendering cycles, where a rendering + cycle is defined as a pair of calls to {@link #beginRendering + beginRendering} / {@link #endRendering endRendering}. +*/ + +public class TextRenderer { + private Font font; + private boolean antialiased; + private boolean useFractionalMetrics; + + private RectanglePacker packer; + private TextureRenderer cachedBackingStore; + private Graphics2D cachedGraphics; + private FontRenderContext cachedFontRenderContext; + private Map/*<String,Rect>*/ stringLocations = new HashMap/*<String,Rect>*/(); + private static final Color TRANSPARENT_BLACK = new Color(0.0f, 0.0f, 0.0f, 0.0f); + + // Support tokenization of space-separated words + // NOTE: not exposing this at the present time as we aren't + // producing identical (or even vaguely similar) rendering results; + // may ultimately yield more efficient use of the backing store, but + // also seems to have performance issues due to rendering more quads + private boolean splitAtSpaces; + private int spaceWidth = -1; + private List/*<String>*/ tokenizationResults = new ArrayList/*<String>*/(); + + // Every certain number of render cycles, flush the strings which + // haven't been used recently + private static final int CYCLES_PER_FLUSH = 200; + private int numRenderCycles; + + // Current text color + private float r = 1.0f; + private float g = 1.0f; + private float b = 1.0f; + private float a = 1.0f; + + // Data associated with each rectangle of text + static class TextData { + private String str; // Back-pointer to String this TextData describes + // The following must be defined and used VERY precisely. This is + // the offset from the upper-left corner of this rectangle (Java + // 2D coordinate system) at which the string must be rasterized in + // order to fit within the rectangle -- the leftmost point of the + // baseline. + private Point origin; + private boolean used; // Whether this text was used recently + + TextData(String str, Point origin) { + this.str = str; + this.origin = origin; + } + + String string() { return str; } + Point origin() { return origin; } + boolean used() { return used; } + void markUsed() { used = true; } + void clearUsed() { used = false; } + } + + /** Creates a new TextRenderer with the given font, using no + antialiasing or fractional metrics. Equivalent to + <code>TextRenderer(font, false, false)</code>. + + @param font the font to render with + */ + public TextRenderer(Font font) { + this(font, false, false); + } + + /** Creates a new TextRenderer with the given Font and specified + font properties. The <code>antialiased</code> and + <code>useFractionalMetrics</code> flags provide control over the + same properties at the Java 2D level. + + @param font the font to render with + @param antialiased whether to use antialiased fonts + @param useFractionalMetrics whether to use fractional font + metrics at the Java 2D level + */ + public TextRenderer(Font font, + boolean antialiased, + boolean useFractionalMetrics) { + this.font = font; + this.antialiased = antialiased; + this.useFractionalMetrics = useFractionalMetrics; + + // FIXME: consider adjusting the size based on font size + // (it will already automatically resize if necessary) + packer = new RectanglePacker(new Manager(), 256, 256); + } + + /** Returns the bounding rectangle of the given String, assuming it + was rendered at the origin. The coordinate system of the + returned rectangle is Java 2D's, with increasing Y coordinates + in the downward direction. The relative coordinate (0, 0) in the + returned rectangle corresponds to the baseline of the leftmost + character of the rendered string, in similar fashion to the + results returned by, for example, {@link + GlyphVector#getVisualBounds}. Most applications will use only + the width and height of the returned Rectangle for the purposes + of centering or justifying the String. It is not specified which + Java 2D bounds ({@link GlyphVector#getVisualBounds + getVisualBounds}, {@link GlyphVector#getPixelBounds + getPixelBounds}, etc.) the returned bounds correspond to, + although every effort is made to ensure an accurate bound. */ + public Rectangle2D getBounds(String str) { + // FIXME: this doesn't hit the cache if tokenization is enabled -- + // needs more work + // Prefer a more optimized approach + Rect r = null; + if ((r = (Rect) stringLocations.get(str)) != null) { + TextData data = (TextData) r.getUserData(); + // Reconstitute the Java 2D results based on the cached values + return new Rectangle2D.Double(-data.origin().x, + -data.origin().y, + r.w(), r.h()); + } + + FontRenderContext frc = getFontRenderContext(); + GlyphVector gv = font.createGlyphVector(frc, str); + // Must return a Rectangle compatible with the layout algorithm -- + // must be idempotent + return normalize(gv.getPixelBounds(frc, 0, 0)); + } + + /** 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 + two-dimensional orthographic projection with (0, 0) as the + lower-left coordinate and (width, height) as the upper-right + coordinate. Binds and enables the internal OpenGL texture + object, sets the texture environment mode to GL_MODULATE, and + changes the current color to the last color set with this + TextRenderer via {@link #setColor setColor}. + + @param width the width of the current on-screen OpenGL drawable + @param height the height of the current on-screen OpenGL drawable + @throws GLException If an OpenGL context is not current when this method is called + */ + public void beginRendering(int width, int height) throws GLException { + getBackingStore().beginOrthoRendering(width, height); + GL gl = GLU.getCurrentGL(); + // 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 + gl.glColor4f(r, g, b, a); + } + + /** Changes the current color of this TextRenderer to the supplied + one, where each component ranges from 0.0f - 1.0f. The alpha + component, if used, does not need to be premultiplied into the + color channels as described in the documentation for {@link + Texture Texture}, although premultiplied colors are used + internally. The default color is opaque white. + + @param r the red component of the new color + @param g the green component of the new color + @param b the blue component of the new color + @param alpha the alpha component of the new color, 0.0f = + completely transparent, 1.0f = completely opaque + @throws GLException If an OpenGL context is not current when this method is called + */ + public void setColor(float r, float g, float b, float a) throws GLException { + GL gl = GLU.getCurrentGL(); + this.r = r * a; + this.g = g * a; + this.b = b * a; + this.a = a; + + gl.glColor4f(this.r, this.g, this.b, this.a); + } + + /** Draws the supplied String at the desired location using the + renderer's current color. The baseline of the leftmost character + is at position (x, y) specified in OpenGL coordinates, where the + origin is at the lower-left of the drawable and the Y coordinate + increases in the upward direction. + + @param str the string to draw + @param x the x coordinate at which to draw + @param y the y coordinate at which to draw + @throws GLException If an OpenGL context is not current when this method is called + */ + public void draw(String str, int x, int y) throws GLException { + // Split up the string into space-separated pieces + tokenize(str); + int xOffset = 0; + for (Iterator iter = tokenizationResults.iterator(); iter.hasNext(); ) { + String curStr = (String) iter.next(); + if (curStr != null) { + // Look up the string on the backing store + Rect rect = (Rect) stringLocations.get(curStr); + if (rect == null) { + // Rasterize this string and place it on the backing store + Graphics2D g = getGraphics2D(); + FontRenderContext frc = getFontRenderContext(); + GlyphVector gv = font.createGlyphVector(frc, curStr); + Rectangle2D bbox = normalize(gv.getPixelBounds(frc, 0, 0)); + Point origin = new Point((int) -bbox.getMinX(), + (int) -bbox.getMinY()); + rect = new Rect(0, 0, + (int) bbox.getWidth(), + (int) bbox.getHeight(), + new TextData(curStr, origin)); + packer.add(rect); + stringLocations.put(curStr, rect); + // Re-fetch the Graphics2D in case the addition of the rectangle + // caused the old backing store to be thrown away + g = getGraphics2D(); + // OK, should now have an (x, y) for this rectangle; rasterize + // the String + // FIXME: need to verify that this causes the String to be + // rasterized fully into the bounding rectangle + int strx = rect.x() + origin.x; + int stry = rect.y() + origin.y; + // Clear out the area we're going to draw into + g.setColor(TRANSPARENT_BLACK); + g.fillRect(rect.x(), rect.y(), rect.w(), rect.h()); + g.setColor(Color.WHITE); + // Draw the string + g.drawString(curStr, strx, stry); + } + + // OK, now draw the portion of the backing store to the screen + TextureRenderer renderer = getBackingStore(); + // NOTE that the rectangles managed by the packer have their + // origin at the upper-left but the TextureRenderer's origin is + // at its lower left!!! + TextData data = (TextData) rect.getUserData(); + data.markUsed(); + + // Align the leftmost point of the baseline to the (x, y) coordinate requested + renderer.drawOrthoRect(x - data.origin().x + xOffset, + y - (rect.h() - data.origin().y), + rect.x(), + renderer.getHeight() - rect.y() - rect.h(), + rect.w(), rect.h()); + xOffset += rect.w(); + } + xOffset += getSpaceWidth(); + } + } + + /** Ends a render cycle with this {@link TextRenderer TextRenderer}. + Restores the projection and modelview matrices as well as + several OpenGL state bits. + + @throws GLException If an OpenGL context is not current when this method is called + */ + public void endRendering() throws GLException { + 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()); + } + } + } + + //---------------------------------------------------------------------- + // Internals only below this point + // + + private static Rectangle2D normalize(Rectangle2D src) { + return new Rectangle2D.Double((int) Math.floor(src.getMinX()), + (int) Math.floor(src.getMinY()), + (int) Math.ceil(src.getWidth()), + (int) Math.ceil(src.getHeight())); + } + + private TextureRenderer getBackingStore() { + TextureRenderer renderer = (TextureRenderer) packer.getBackingStore(); + if (renderer != cachedBackingStore) { + // Backing store changed since last time; discard any cached Graphics2D + if (cachedGraphics != null) { + cachedGraphics.dispose(); + cachedGraphics = null; + cachedFontRenderContext = null; + } + cachedBackingStore = renderer; + } + return cachedBackingStore; + } + + private Graphics2D getGraphics2D() { + TextureRenderer renderer = getBackingStore(); + if (cachedGraphics == null) { + cachedGraphics = renderer.createGraphics(); + // Set up composite, font and rendering hints + cachedGraphics.setComposite(AlphaComposite.Src); + cachedGraphics.setColor(Color.WHITE); + cachedGraphics.setFont(font); + cachedGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + (antialiased ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON + : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)); + cachedGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + (useFractionalMetrics ? RenderingHints.VALUE_FRACTIONALMETRICS_ON + : RenderingHints.VALUE_FRACTIONALMETRICS_OFF)); + } + return cachedGraphics; + } + + private FontRenderContext getFontRenderContext() { + if (cachedFontRenderContext == null) { + cachedFontRenderContext = getGraphics2D().getFontRenderContext(); + } + return cachedFontRenderContext; + } + + private int getSpaceWidth() { + if (spaceWidth < 0) { + Graphics2D g = getGraphics2D(); + FontRenderContext frc = getFontRenderContext(); + GlyphVector gv = font.createGlyphVector(frc, " "); + Rectangle2D bbox = gv.getLogicalBounds(); + spaceWidth = (int) bbox.getWidth(); + } + return spaceWidth; + } + + private void tokenize(String str) { + // Avoid lots of little allocations per render + tokenizationResults.clear(); + if (!splitAtSpaces) { + tokenizationResults.add(str); + } else { + int startChar = 0; + char c = (char) 0; + int len = str.length(); + int i = 0; + while (i < len) { + if (str.charAt(i) == ' ') { + // Terminate any substring + if (startChar < i) { + tokenizationResults.add(str.substring(startChar, i)); + } else { + tokenizationResults.add(null); + } + startChar = i + 1; + } + ++i; + } + // Add on any remaining (all?) characters + if (startChar == 0) { + tokenizationResults.add(str); + } else if (startChar < len) { + tokenizationResults.add(str.substring(startChar, len)); + } + } + } + + static class Manager implements BackingStoreManager { + private Graphics2D g; + + public Object allocateBackingStore(int w, int h) { + // FIXME: should consider checking Font's attributes to see + // whether we're likely to need to support a full RGBA backing + // store (i.e., non-default Paint, foreground color, etc.), but + // for now, let's just be more efficient + TextureRenderer renderer = TextureRenderer.createAlphaOnlyRenderer(w, h); + renderer.setSmoothing(false); + return renderer; + } + + public void deleteBackingStore(Object backingStore) { + ((TextureRenderer) backingStore).dispose(); + } + + public void beginMovement(Object oldBackingStore, Object newBackingStore) { + TextureRenderer newRenderer = (TextureRenderer) newBackingStore; + g = newRenderer.createGraphics(); + } + + public void move(Object oldBackingStore, + Rect oldLocation, + Object newBackingStore, + Rect newLocation) { + TextureRenderer oldRenderer = (TextureRenderer) oldBackingStore; + TextureRenderer newRenderer = (TextureRenderer) newBackingStore; + + if (oldRenderer == newRenderer) { + // Movement on the same backing store -- easy case + g.copyArea(oldLocation.x(), oldLocation.y(), + oldLocation.w(), oldLocation.h(), + newLocation.x() - oldLocation.x(), + newLocation.y() - oldLocation.y()); + } else { + // Need to draw from the old renderer's image into the new one + Image img = oldRenderer.getImage(); + g.drawImage(img, + newLocation.x(), newLocation.y(), + newLocation.x() + newLocation.w(), newLocation.y() + newLocation.h(), + oldLocation.x(), oldLocation.y(), + oldLocation.x() + oldLocation.w(), oldLocation.y() + oldLocation.h(), + null); + } + } + + public void endMovement(Object oldBackingStore, Object newBackingStore) { + g.dispose(); + // Sync the whole surface + TextureRenderer newRenderer = (TextureRenderer) newBackingStore; + newRenderer.sync(0, 0, newRenderer.getWidth(), newRenderer.getHeight()); + } + } +} diff --git a/src/classes/com/sun/opengl/util/j2d/TextureRenderer.java b/src/classes/com/sun/opengl/util/j2d/TextureRenderer.java index 552a66a3d..f758e4f6c 100755 --- a/src/classes/com/sun/opengl/util/j2d/TextureRenderer.java +++ b/src/classes/com/sun/opengl/util/j2d/TextureRenderer.java @@ -64,7 +64,18 @@ public class TextureRenderer { // OpenGL-related work must be. This implies that the user's code // would need to be split up into multiple callbacks run from the // appropriate threads, which would be somewhat unfortunate. + + // Whether we have an alpha channel in the (RGB/A) backing store private boolean alpha; + + // Whether we're using only a GL_INTENSITY backing store + private boolean intensity; + + // Whether smoothing is enabled for the OpenGL texture (switching + // between GL_LINEAR and GL_NEAREST filtering) + private boolean smoothing = true; + + // The backing store itself private BufferedImage image; private Texture texture; @@ -82,10 +93,24 @@ public class TextureRenderer { @param alpha whether to allocate an alpha channel for the texture */ public TextureRenderer(int width, int height, boolean alpha) { + this(width, height, alpha, false); + } + + // Internal constructor to avoid confusion since alpha only makes + // sense when intensity is not set + private TextureRenderer(int width, int height, boolean alpha, boolean intensity) { this.alpha = alpha; + this.intensity = intensity; init(width, height); } + /** Creates a new renderer with a special kind of backing store + which acts only as an alpha channel. Internally, this associates + a GL_INTENSITY OpenGL texture with the backing store. */ + public static TextureRenderer createAlphaOnlyRenderer(int width, int height) { + return new TextureRenderer(width, height, false, true); + } + /** Returns the width of the backing store of this renderer. @return the width of the backing store of this renderer @@ -135,22 +160,54 @@ public class TextureRenderer { @param width the new width of the backing store of this renderer @param height the new height of the backing store of this renderer + @throws GLException If an OpenGL context is not current when this method is called */ - public void setSize(int width, int height) { + public void setSize(int width, int height) throws GLException { init(width, height); } - /** Sets the size of the backing store of this renderer. This may cause the OpenGL texture object associated with this renderer to be invalidated. @param d the new size of the backing store of this renderer + @throws GLException If an OpenGL context is not current when this method is called */ - public void setSize(Dimension d) { + public void setSize(Dimension d) throws GLException { setSize(d.width, d.height); } + /** Sets whether smoothing is enabled for the OpenGL texture; if so, + uses GL_LINEAR interpolation for the minification and + magnification filters. Defaults to true. + + @param smoothing whether smoothing is enabled for the OpenGL texture + @throws GLException If an OpenGL context is not current when this method is called + */ + public void setSmoothing(boolean smoothing) throws GLException { + this.smoothing = smoothing; + // This can be set lazily + if (texture != null) { + GL gl = GLU.getCurrentGL(); + if (smoothing) { + texture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); + texture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); + } else { + texture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); + texture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); + } + } + } + + /** Returns whether smoothing is enabled for the OpenGL texture; see + {@link #setSmoothing setSmoothing}. Defaults to true. + + @return whether smoothing is enabled for the OpenGL texture + */ + public boolean getSmoothing() { + return smoothing; + } + /** Creates a {@link java.awt.Graphics2D Graphics2D} instance for rendering to the backing store of this renderer. The returned object should be disposed of using the normal {@link @@ -340,13 +397,17 @@ public class TextureRenderer { image = null; } - int imageType = (alpha ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_RGB); + // Infer the internal format if not an intensity texture + int internalFormat = (intensity ? GL.GL_INTENSITY : 0); + int imageType = + (intensity ? BufferedImage.TYPE_BYTE_GRAY : + (alpha ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_RGB)); image = new BufferedImage(width, height, imageType); // Always realllocate the TextureData associated with this // BufferedImage; it's just a reference to the contents but we // need it in order to update sub-regions of the underlying // texture - textureData = TextureIO.newTextureData(image, false); + textureData = new TextureData(internalFormat, 0, false, image); // For now, always reallocate the underlying OpenGL texture when // the backing store size changes mustReallocateTexture = true; @@ -363,6 +424,11 @@ public class TextureRenderer { if (texture == null) { texture = TextureIO.newTexture(textureData); + if (!smoothing) { + // The TextureIO classes default to GL_LINEAR filtering + texture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); + texture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); + } return true; } |