aboutsummaryrefslogtreecommitdiffstats
path: root/src/classes/com/sun/opengl/util
diff options
context:
space:
mode:
authorKenneth Russel <[email protected]>2007-01-04 07:20:30 +0000
committerKenneth Russel <[email protected]>2007-01-04 07:20:30 +0000
commite4daa5d745f3d084e0c7180a88597b2582131b63 (patch)
tree3e5b35a4b3ec92a74abaa11228fabca32e11eae0 /src/classes/com/sun/opengl/util
parent10c76681d1507fa527b59cb28bcde5d1c6a4fe18 (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/classes/com/sun/opengl/util')
-rwxr-xr-xsrc/classes/com/sun/opengl/util/j2d/TextRenderer.java512
-rwxr-xr-xsrc/classes/com/sun/opengl/util/j2d/TextureRenderer.java76
2 files changed, 583 insertions, 5 deletions
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;
}