diff --git a/src/jogl/classes/com/jogamp/opengl/util/awt/TextRenderer.java b/src/jogl/classes/com/jogamp/opengl/util/awt/TextRenderer.java index e6f5aaa2e7..e2231bf3ab 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/awt/TextRenderer.java +++ b/src/jogl/classes/com/jogamp/opengl/util/awt/TextRenderer.java @@ -1,2019 +1,1147 @@ /* - * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2010 JogAmp Community. All rights reserved. + * Copyright 2012 JogAmp Community. 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 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. + * 1. Redistributions 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. + * 2. Redistributions 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 BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * 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. + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. */ package com.jogamp.opengl.util.awt; -import com.jogamp.common.nio.Buffers; -import com.jogamp.common.util.PropertyAccess; -import com.jogamp.opengl.GLExtensions; -import com.jogamp.opengl.util.*; -import com.jogamp.opengl.util.packrect.*; -import com.jogamp.opengl.util.texture.*; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLContext; +import com.jogamp.opengl.GLException; +import com.jogamp.opengl.util.texture.TextureCoords; -import java.awt.AlphaComposite; import java.awt.Color; - -// For debugging purposes -import java.awt.EventQueue; import java.awt.Font; -import java.awt.Frame; import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Point; -import java.awt.RenderingHints; -import java.awt.event.*; -import java.awt.font.*; -import java.awt.geom.*; -import java.nio.*; -import java.text.*; -import java.util.*; - -import com.jogamp.opengl.*; -import com.jogamp.opengl.fixedfunc.GLPointerFunc; -import com.jogamp.opengl.glu.*; -import com.jogamp.opengl.awt.*; - -import jogamp.opengl.Debug; - - -/** 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.

- - Using the {@link TextRenderer TextRenderer} is simple. Add a - "TextRenderer renderer;" field to your {@link - com.jogamp.opengl.GLEventListener GLEventListener}. In your {@link - com.jogamp.opengl.GLEventListener#init init} method, add: - -

-    renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36));
-    
- -

In the {@link com.jogamp.opengl.GLEventListener#display display} method of your - {@link com.jogamp.opengl.GLEventListener GLEventListener}, add: -

-    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();
-    
- - Unless you are sharing textures and display lists between OpenGL - contexts, you do not need to call the {@link #dispose dispose} - method of the TextRenderer; the OpenGL resources it uses - internally will be cleaned up automatically when the OpenGL - context is destroyed.

- - Note that the TextRenderer may cause the vertex and texture - coordinate array buffer bindings to change, or to be unbound. This - is important to note if you are using Vertex Buffer Objects (VBOs) - in your application.

- - Internally, the renderer uses a rectangle packing algorithm to - pack both glyphs and full Strings' rendering results (which are - variable size) onto a larger OpenGL texture. The internal backing - store is maintained using a {@link - com.jogamp.opengl.util.awt.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}. - - @author John Burkey - @author Kenneth Russell -*/ -public class TextRenderer { - private static final boolean DEBUG; - - static { - Debug.initSingleton(); - DEBUG = PropertyAccess.isPropertyDefined("jogl.debug.TextRenderer", true); - } +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; +import java.lang.Character.UnicodeBlock; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jogamp.opengl.util.awt.text.Check; +import jogamp.opengl.util.awt.text.Glyph; +import jogamp.opengl.util.awt.text.GlyphCache; +import jogamp.opengl.util.awt.text.GlyphProducer; +import jogamp.opengl.util.awt.text.GlyphProducers; +import jogamp.opengl.util.awt.text.GlyphRenderer; +import jogamp.opengl.util.awt.text.GlyphRenderers; + + +/** + * Utility for rendering bitmapped Java 2D text into an OpenGL window. + * + *

+ * {@code TextRenderer} has high performance, full Unicode support, and a simple API. It 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. + * + *

+ * Using {@code TextRenderer} is simple. Add a {@code TextRenderer} field to your {@code + * GLEventListener} and in your {@code init} method, add: + * + *

+ * renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36));
+ * 
+ * + *

+ * In the {@code display} method of your {@code GLEventListener}, add: + * + *

+ * 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();
+ * 
+ * + *

+ * Unless you are sharing textures between OpenGL contexts, you do not need to call the {@link + * #dispose dispose} method of the {@code TextRenderer}; the OpenGL resources it uses internally + * will be cleaned up automatically when the OpenGL context is destroyed. + * + *

+ * Note that a {@code TextRenderer} will cause the Vertex Array Object binding to change, or to be + * unbound. + * + *

+ * Internally, the renderer uses a rectangle packing algorithm to pack both glyphs and full + * strings' rendering results (which are variable size) onto a larger OpenGL texture. The internal + * backing store is maintained using a {@link com.jogamp.opengl.util.awt.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}. + * + * @author John Burkey + * @author Kenneth Russell + */ +/*@NotThreadSafe*/ +public final class TextRenderer { - // These are occasionally useful for more in-depth debugging - private static final boolean DISABLE_GLYPH_CACHE = false; - private static final boolean DRAW_BBOXES = false; - - static final int kSize = 256; - - // Every certain number of render cycles, flush the strings which - // haven't been used recently - private static final int CYCLES_PER_FLUSH = 100; - - // The amount of vertical dead space on the backing store before we - // force a compaction - private static final float MAX_VERTICAL_FRAGMENTATION = 0.7f; - static final int kQuadsPerBuffer = 100; - static final int kCoordsPerVertVerts = 3; - static final int kCoordsPerVertTex = 2; - static final int kVertsPerQuad = 4; - static final int kTotalBufferSizeVerts = kQuadsPerBuffer * kVertsPerQuad; - static final int kTotalBufferSizeCoordsVerts = kQuadsPerBuffer * kVertsPerQuad * kCoordsPerVertVerts; - static final int kTotalBufferSizeCoordsTex = kQuadsPerBuffer * kVertsPerQuad * kCoordsPerVertTex; - static final int kTotalBufferSizeBytesVerts = kTotalBufferSizeCoordsVerts * 4; - static final int kTotalBufferSizeBytesTex = kTotalBufferSizeCoordsTex * 4; - static final int kSizeInBytes_OneVertices_VertexData = kCoordsPerVertVerts * 4; - static final int kSizeInBytes_OneVertices_TexData = kCoordsPerVertTex * 4; - private final Font font; - private final boolean antialiased; - private final boolean useFractionalMetrics; - - // Whether we're attempting to use automatic mipmap generation support - private boolean mipmap; - private RectanglePacker packer; - private boolean haveMaxSize; - private final RenderDelegate renderDelegate; - private TextureRenderer cachedBackingStore; - private Graphics2D cachedGraphics; - private FontRenderContext cachedFontRenderContext; - private final Map stringLocations = new HashMap(); - private final GlyphProducer mGlyphProducer; - - private int numRenderCycles; - - // Need to keep track of whether we're in a beginRendering() / - // endRendering() cycle so we can re-enter the exact same state if - // we have to reallocate the backing store - private boolean inBeginEndPair; - private boolean isOrthoMode; - private int beginRenderingWidth; - private int beginRenderingHeight; - private boolean beginRenderingDepthTestDisabled; - - // For resetting the color after disposal of the old backing store - private boolean haveCachedColor; - private float cachedR; - private float cachedG; - private float cachedB; - private float cachedA; - private Color cachedColor; - private boolean needToResetColor; - - // For debugging only - private Frame dbgFrame; - - // Debugging purposes only - private boolean debugged; - Pipelined_QuadRenderer mPipelinedQuadRenderer; - - //emzic: added boolean flag - private boolean useVertexArrays = true; - - //emzic: added boolean flag - private boolean isExtensionAvailable_GL_VERSION_1_5; - private boolean checkFor_isExtensionAvailable_GL_VERSION_1_5; - - // Whether GL_LINEAR filtering is enabled for the backing store - private boolean smoothing = true; - - /** Creates a new TextRenderer with the given font, using no - antialiasing or fractional metrics, and the default - RenderDelegate. Equivalent to TextRenderer(font, false, - false). - - @param font the font to render with - */ - public TextRenderer(final Font font) { - this(font, false, false, null, false); - } + /** + * True to print debugging information. + */ + static final boolean DEBUG = false; - /** Creates a new TextRenderer with the given font, using no - antialiasing or fractional metrics, and the default - RenderDelegate. If mipmap is true, attempts to use - OpenGL's automatic mipmap generation for better smoothing when - rendering the TextureRenderer's contents at a distance. - Equivalent to TextRenderer(font, false, false). - - @param font the font to render with - @param mipmap whether to attempt use of automatic mipmap generation - */ - public TextRenderer(final Font font, final boolean mipmap) { - this(font, false, false, null, mipmap); - } + /** + * Common instance of the default render delegate. + */ + /*@Nonnull*/ + private static final RenderDelegate DEFAULT_RENDER_DELEGATE = new DefaultRenderDelegate(); - /** Creates a new TextRenderer with the given Font, specified font - properties, and default RenderDelegate. The - antialiased and useFractionalMetrics - flags provide control over the same properties at the Java 2D - level. No mipmap support is requested. Equivalent to - TextRenderer(font, antialiased, useFractionalMetrics, - null). - - @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(final Font font, final boolean antialiased, - final boolean useFractionalMetrics) { - this(font, antialiased, useFractionalMetrics, null, false); - } + /** + * Face, style, and size of text to render with. + */ + /*@Nonnull*/ + private final Font font; - /** Creates a new TextRenderer with the given Font, specified font - properties, and given RenderDelegate. The - antialiased and useFractionalMetrics - flags provide control over the same properties at the Java 2D - level. The renderDelegate provides more control - over the text rendered. No mipmap support is requested. - - @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 - @param renderDelegate the render delegate to use to draw the - text's bitmap, or null to use the default one - */ - public TextRenderer(final Font font, final boolean antialiased, - final boolean useFractionalMetrics, final RenderDelegate renderDelegate) { - this(font, antialiased, useFractionalMetrics, renderDelegate, false); - } + /** + * Delegate to store glyphs. + */ + /*@Nonnull*/ + private final GlyphCache glyphCache; - /** Creates a new TextRenderer with the given Font, specified font - properties, and given RenderDelegate. The - antialiased and useFractionalMetrics - flags provide control over the same properties at the Java 2D - level. The renderDelegate provides more control - over the text rendered. If mipmap is true, attempts - to use OpenGL's automatic mipmap generation for better smoothing - when rendering the TextureRenderer's contents at a distance. - - @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 - @param renderDelegate the render delegate to use to draw the - text's bitmap, or null to use the default one - @param mipmap whether to attempt use of automatic mipmap generation - */ - public TextRenderer(final Font font, final boolean antialiased, - final boolean useFractionalMetrics, RenderDelegate renderDelegate, - final boolean mipmap) { - this.font = font; - this.antialiased = antialiased; - this.useFractionalMetrics = useFractionalMetrics; - this.mipmap = mipmap; + /** + * Delegate to create glyphs. + */ + /*@Nonnull*/ + private final GlyphProducer glyphProducer; - // FIXME: consider adjusting the size based on font size - // (it will already automatically resize if necessary) - packer = new RectanglePacker(new Manager(), kSize, kSize); + /** + * Delegate to draw glyphs. + */ + /*@Nonnull*/ + private final GlyphRenderer glyphRenderer = new GlyphRendererProxy(); - if (renderDelegate == null) { - renderDelegate = new DefaultRenderDelegate(); - } + /** + * Mediator coordinating components. + */ + /*@Nonnull*/ + private final Mediator mediator = new Mediator(); - this.renderDelegate = renderDelegate; + /** + * True if this text renderer is ready to be used. + */ + private boolean ready = false; - mGlyphProducer = new GlyphProducer(font.getNumGlyphs()); + /** + * Constructs a {@link TextRenderer}. + * + *

+ * The resulting {@code TextRenderer} will use no antialiasing or fractional metrics and the + * default render delegate. It will not attempt to use OpenGL's automatic mipmap generation + * for better scaling. All Unicode characters will be available. + * + * @param font Font to render text with + * @throws NullPointerException if font is null + */ + public TextRenderer(/*@Nonnull*/ final Font font) { + this(font, false, false, null, false, null); } - /** Returns the bounding rectangle of the given String, assuming it - was rendered at the origin. See {@link #getBounds(CharSequence) - getBounds(CharSequence)}. */ - public Rectangle2D getBounds(final String str) { - return getBounds((CharSequence) str); + /** + * Constructs a {@link TextRenderer} with optional mipmapping. + * + *

+ * The resulting {@code TextRenderer} will use no antialiasing or fractional metrics, and the + * default render delegate. If mipmapping is requested, the text renderer will attempt to use + * OpenGL's automatic mipmap generation for better scaling. All Unicode characters will be + * available. + * + * @param font Font to render text with + * @param mipmap True to generate mipmaps (to make the text scale better) + * @throws NullPointerException if font is null + */ + public TextRenderer(/*@Nonnull*/ final Font font, final boolean mipmap) { + this(font, false, false, null, mipmap, null); } - /** Returns the bounding rectangle of the given CharSequence, - 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 - java.awt.font.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 - java.awt.font.GlyphVector#getVisualBounds getVisualBounds}, - {@link java.awt.font.GlyphVector#getPixelBounds getPixelBounds}, - etc.) the returned bounds correspond to, although every effort - is made to ensure an accurate bound. */ - public Rectangle2D getBounds(final CharSequence str) { - // FIXME: this should be more optimized and use the glyph cache - final Rect r = stringLocations.get(str); - - if (r != null) { - final 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()); - } + /** + * Constructs a {@link TextRenderer} with optional text properties. + * + *

+ * The resulting {@code TextRenderer} will use antialiasing and fractional metrics if + * requested, and the default render delegate. It will not attempt to use OpenGL's automatic + * mipmap generation for better scaling. All Unicode characters will be available. + * + * @param font Font to render text with + * @param antialias True to smooth edges of text + * @param subpixel True to use subpixel accuracy + * @throws NullPointerException if font is null + */ + public TextRenderer(/*@Nonnull*/ final Font font, + final boolean antialias, + final boolean subpixel) { + this(font, antialias, subpixel, null, false, null); + } - // Must return a Rectangle compatible with the layout algorithm -- - // must be idempotent - return normalize(renderDelegate.getBounds(str, font, - getFontRenderContext())); + /** + * Constructs a {@link TextRenderer} with optional text properties and a render delegate. + * + *

+ * The resulting {@code TextRenderer} will use antialiasing and fractional metrics if + * requested. The optional render delegate provides more control over the text rendered. The + * {@code TextRenderer} will not attempt to use OpenGL's automatic mipmap generation for better + * scaling. All Unicode characters will be available. + * + * @param font Font to render text with + * @param antialias True to smooth edges of text + * @param subpixel True to use subpixel accuracy + * @param rd Optional controller of rendering details + * @throws NullPointerException if font is null + */ + public TextRenderer(/*@Nonnull*/ final Font font, + final boolean antialias, + final boolean subpixel, + /*@CheckForNull*/ final RenderDelegate rd) { + this(font, antialias, subpixel, rd, false, null); } - /** Returns the Font this renderer is using. */ - public Font getFont() { - return font; + /** + * Constructs a {@link TextRenderer} with optional text properties, a render delegate, and + * mipmapping. + * + *

+ * The resulting {@code TextRenderer} will use antialiasing and fractional metrics if + * requested. The optional render delegate provides more control over the text rendered. If + * mipmapping is requested, the {@code TextRenderer} will attempt to use OpenGL's automatic + * mipmap generation for better scaling. All Unicode characters will be available. + * + * @param font Font to render text with + * @param antialias True to smooth edges of text + * @param subpixel True to use subpixel accuracy + * @param rd Optional controller of rendering details + * @param mipmap Whether to generate mipmaps to make the text scale better + * @throws NullPointerException if font is null + */ + public TextRenderer(/*@Nonnull*/ final Font font, + final boolean antialias, + final boolean subpixel, + /*CheckForNull*/ final RenderDelegate rd, + final boolean mipmap) { + this(font, antialias, subpixel, rd, mipmap, null); } - /** 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(); + /** + * Constructs a {@link TextRenderer} with optional text properties, a render delegate, + * mipmapping, and a range of characters. + * + *

+ * The resulting {@code TextRenderer} will use antialiasing and fractional metrics if + * requested. The optional render delegate provides more control over the text rendered. If + * mipmapping is requested, the text renderer will attempt to use OpenGL's automatic mipmap + * generation for better scaling. If a character range is specified, the text renderer will + * limit itself to those characters to try to achieve better performance. Otherwise all + * Unicode characters will be available. + * + * @param font Font to render text with + * @param antialias True to smooth edges of text + * @param subpixel True to use subpixel accuracy + * @param rd Controller of rendering details, or null to use the default + * @param mipmap Whether to generate mipmaps to make the text scale better + * @param ub Range of unicode characters, or null to use the default + * @throws NullPointerException if font is null + */ + public TextRenderer(/*@Nonnull*/ final Font font, + final boolean antialias, + final boolean subpixel, + /*@CheckForNull*/ RenderDelegate rd, + final boolean mipmap, + /*@CheckForNull*/ final UnicodeBlock ub) { + + Check.notNull(font, "Font cannot be null"); + if (rd == null) { + rd = DEFAULT_RENDER_DELEGATE; } - return cachedFontRenderContext; + this.font = font; + this.glyphCache = GlyphCache.newInstance(font, rd, antialias, subpixel, mipmap); + this.glyphProducer = GlyphProducers.get(font, rd, glyphCache.getFontRenderContext(), ub); } - /** 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}. This method - disables the depth test and is equivalent to - beginRendering(width, height, true). - - @param width the width of the current on-screen OpenGL drawable - @param height the height of the current on-screen OpenGL drawable - @throws com.jogamp.opengl.GLException If an OpenGL context is not current when this method is called - */ - public void beginRendering(final int width, final int height) throws GLException { - beginRendering(width, height, true); + /** + * Starts a 3D render cycle. + * + *

+ * Assumes the end user is responsible for setting up the modelview and projection matrices, + * and will render text using the {@link #draw3D} method. + * + * @throws GLException if an OpenGL context is not current + */ + public void begin3DRendering() { + beginRendering(false, 0, 0, false); } - /** 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}. Disables the depth - test if the disableDepthTest argument is true. - - @param width the width of the current on-screen OpenGL drawable - @param height the height of the current on-screen OpenGL drawable - @param disableDepthTest whether to disable the depth test - @throws GLException If an OpenGL context is not current when this method is called - */ - public void beginRendering(final int width, final int height, final boolean disableDepthTest) - throws GLException { - beginRendering(true, width, height, disableDepthTest); + /** + * Starts an orthographic render cycle. + * + *

+ * Sets 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 text drawer via {@link #setColor}. + * + *

+ * This method disables the depth test and is equivalent to beginRendering(width, height, + * true). + * + * @param width Width of the current on-screen OpenGL drawable + * @param height Height of the current on-screen OpenGL drawable + * @throws GLException if an OpenGL context is not current + * @throws IllegalArgumentException if width or height is negative + */ + public void beginRendering(/*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height) { + beginRendering(true, width, height, true); } - /** Begins rendering of 2D text in 3D with this {@link TextRenderer - TextRenderer} into the current OpenGL drawable. Assumes the end - user is responsible for setting up the modelview and projection - matrices, and will render text using the {@link #draw3D draw3D} - method. This method pushes some OpenGL state bits, 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}. - - @throws GLException If an OpenGL context is not current when this method is called - */ - public void begin3DRendering() throws GLException { - beginRendering(false, 0, 0, false); + /** + * Starts an orthographic render cycle. + * + *

+ * Sets 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 text drawer via {@link #setColor}. + * + *

+ * Disables the depth test if requested. + * + * @param width Width of the current on-screen OpenGL drawable + * @param height Height of the current on-screen OpenGL drawable + * @param disableDepthTest True to disable the depth test + * @throws GLException if an OpenGL context is not current + * @throws IllegalArgumentException if width or height is negative + */ + public void beginRendering(/*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean disableDepthTest) { + beginRendering(true, width, height, disableDepthTest); } - /** Changes the current color of this TextRenderer to the supplied - one. The default color is opaque white. - - @param color the new color to use for rendering text - @throws GLException If an OpenGL context is not current when this method is called - */ - public void setColor(final Color color) throws GLException { - final boolean noNeedForFlush = (haveCachedColor && (cachedColor != null) && - color.equals(cachedColor)); + /** + * Starts a render cycle. + * + * @param ortho True to use orthographic projection + * @param width Width of the current OpenGL viewport + * @param height Height of the current OpenGL viewport + * @param disableDepthTest True to ignore depth values + * @throws GLException if no OpenGL context is current or it's an unexpected version + * @throws IllegalArgumentException if width or height is negative + */ + private void beginRendering(final boolean ortho, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean disableDepthTest) { - if (!noNeedForFlush) { - flushGlyphPipeline(); - } + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); - getBackingStore().setColor(color); - haveCachedColor = true; - cachedColor = color; - } + // Get the current OpenGL context + final GL gl = GLContext.getCurrentGL(); - /** 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 - com.jogamp.opengl.util.texture.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 a 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(final float r, final float g, final float b, final float a) - throws GLException { - final boolean noNeedForFlush = (haveCachedColor && (cachedColor == null) && - (r == cachedR) && (g == cachedG) && (b == cachedB) && - (a == cachedA)); - - if (!noNeedForFlush) { - flushGlyphPipeline(); + // Make sure components are set up properly + if (!ready) { + glyphCache.addListener(mediator); + glyphRenderer.addListener(mediator); + ready = true; } - getBackingStore().setColor(r, g, b, a); - haveCachedColor = true; - cachedR = r; - cachedG = g; - cachedB = b; - cachedA = a; - cachedColor = null; + // Delegate to components + glyphCache.beginRendering(gl); + glyphRenderer.beginRendering(gl, ortho, width, height, disableDepthTest); } - /** Draws the supplied CharSequence 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(final CharSequence str, final int x, final int y) throws GLException { - draw3D(str, x, y, 0, 1); - } + /** + * Destroys resources used by the text renderer. + * + * @throws GLException if no OpenGL context is current, or is unexpected version + */ + public void dispose() { - /** Draws the supplied String at the desired location using the - renderer's current color. See {@link #draw(CharSequence, int, - int) draw(CharSequence, int, int)}. */ - public void draw(final String str, final int x, final int y) throws GLException { - draw3D(str, x, y, 0, 1); - } + // Get the current OpenGL context + final GL gl = GLContext.getCurrentGL(); - /** Draws the supplied CharSequence at the desired 3D location using - the renderer's current color. The baseline of the leftmost - character is placed at position (x, y, z) in the current - coordinate system. - - @param str the string to draw - @param x the x coordinate at which to draw - @param y the y coordinate at which to draw - @param z the z coordinate at which to draw - @param scaleFactor a uniform scale factor applied to the width and height of the drawn rectangle - @throws GLException If an OpenGL context is not current when this method is called - */ - public void draw3D(final CharSequence str, final float x, final float y, final float z, - final float scaleFactor) { - internal_draw3D(str, x, y, z, scaleFactor); - } + // Destroy the glyph cache + glyphCache.dispose(gl); - /** Draws the supplied String at the desired 3D location using the - renderer's current color. See {@link #draw3D(CharSequence, - float, float, float, float) draw3D(CharSequence, float, float, - float, float)}. */ - public void draw3D(final String str, final float x, final float y, final float z, final float scaleFactor) { - internal_draw3D(str, x, y, z, scaleFactor); + // Destroy the glyph renderer + glyphRenderer.dispose(gl); } - /** Returns the pixel width of the given character. */ - public float getCharWidth(final char inChar) { - return mGlyphProducer.getGlyphPixelWidth(inChar); + /** + * Draws a character sequence at a location. + * + *

+ * 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 text Text to draw + * @param x Position to draw on X axis + * @param y Position to draw on Y axis + * @throws NullPointerException if text is null + * @throws GLException if an OpenGL context is not current, or is unexpected version + */ + public void draw(/*@Nonnull*/ final CharSequence text, + /*@CheckForSigned*/ final int x, + /*@CheckForSigned*/ final int y) { + draw3D(text, x, y, 0, 1); } - /** Causes the TextRenderer to flush any internal caches it may be - maintaining and draw its rendering results to the screen. This - should be called after each call to draw() if you are setting - OpenGL state such as the modelview matrix between calls to - draw(). */ - public void flush() { - flushGlyphPipeline(); + /** + * Draws a string at a location. + * + *

+ * 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 text Text to draw + * @param x Position to draw on X axis + * @param y Position to draw on Y axis + * @throws NullPointerException if text is null + * @throws GLException if an OpenGL context is not current, or is unexpected version + */ + public void draw(/*@Nonnull*/ final String text, + /*@CheckForSigned*/ final int x, + /*@CheckForSigned*/ final int y) { + draw3D(text, x, y, 0, 1); } - /** Ends a render cycle with this {@link TextRenderer TextRenderer}. - Restores the projection and modelview matrices as well as - several OpenGL state bits. Should be paired with {@link - #beginRendering beginRendering}. - - @throws GLException If an OpenGL context is not current when this method is called - */ - public void endRendering() throws GLException { - endRendering(true); + /** + * Draws a character sequence at a location in 3D space. + * + *

+ * The baseline of the leftmost character is placed at position (x, y, z) in the current + * coordinate system. + * + * @param text Text to draw + * @param x X coordinate at which to draw + * @param y Y coordinate at which to draw + * @param z Z coordinate at which to draw + * @param scale Uniform scale applied to width and height of text + * @throws NullPointerException if text is null + * @throws GLException if an OpenGL context is not current, or is unexpected version + */ + public void draw3D(/*@Nonnull*/ final CharSequence text, + /*@CheckForSigned*/ final float x, + /*@CheckForSigned*/ final float y, + /*@CheckForSigned*/ final float z, + /*@CheckForSigned*/ final float scale) { + draw3D(text.toString(), x, y, z, scale); } - /** Ends a 3D render cycle with this {@link TextRenderer TextRenderer}. - Restores several OpenGL state bits. Should be paired with {@link - #begin3DRendering begin3DRendering}. + /** + * Draws text at a location in 3D space. + * + *

+ * Uses the renderer's current color. The baseline of the leftmost character is placed at + * position (x, y, z) in the current coordinate system. + * + * @param text Text to draw + * @param x Position to draw on X axis + * @param y Position to draw on Y axis + * @param z Position to draw on Z axis + * @param scale Uniform scale applied to width and height of text + * @throws GLException if no OpenGL context is current, or is unexpected version + * @throws NullPointerException if text is null + */ + public void draw3D(/*@Nonnull*/ final String text, + /*@CheckForSigned*/ float x, + /*@CheckForSigned*/ final float y, + /*@CheckForSigned*/ final float z, + /*@CheckForSigned*/ final float scale) { - @throws GLException If an OpenGL context is not current when this method is called - */ - public void end3DRendering() throws GLException { - endRendering(false); - } + Check.notNull(text, "Text cannot be null"); - /** Disposes of all resources this TextRenderer is using. It is not - valid to use the TextRenderer after this method is called. + // Get the current OpenGL context + final GL gl = GLContext.getCurrentGL(); - @throws GLException If an OpenGL context is not current when this method is called - */ - public void dispose() throws GLException { - packer.dispose(); - packer = null; - cachedBackingStore = null; - cachedGraphics = null; - cachedFontRenderContext = null; + // Get all the glyphs for the string + final List glyphs = glyphProducer.createGlyphs(text); - if (dbgFrame != null) { - dbgFrame.dispose(); + // Render each glyph + for (final Glyph glyph : glyphs) { + if (glyph.location == null) { + glyphCache.upload(glyph); + } + final TextureCoords coords = glyphCache.find(glyph); + final float advance = glyphRenderer.drawGlyph(gl, glyph, x, y, z, scale, coords); + x += advance * scale; } } - //---------------------------------------------------------------------- - // Internals only below this point - // - - private static Rectangle2D preNormalize(final Rectangle2D src) { - // Need to round to integer coordinates - // Also give ourselves a little slop around the reported - // bounds of glyphs because it looks like neither the visual - // nor the pixel bounds works perfectly well - final int minX = (int) Math.floor(src.getMinX()) - 1; - final int minY = (int) Math.floor(src.getMinY()) - 1; - final int maxX = (int) Math.ceil(src.getMaxX()) + 1; - final int maxY = (int) Math.ceil(src.getMaxY()) + 1; - return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); + /** + * Finishes a 3D render cycle. + */ + public void end3DRendering() { + endRendering(); } + /** + * Finishes a render cycle. + */ + public void endRendering() { - private Rectangle2D normalize(final Rectangle2D src) { - // Give ourselves a boundary around each entity on the backing - // store in order to prevent bleeding of nearby Strings due to - // the fact that we use linear filtering - - // NOTE that this boundary is quite heuristic and is related - // to how far away in 3D we may view the text -- - // heuristically, 1.5% of the font's height - final int boundary = (int) Math.max(1, 0.015 * font.getSize()); + // Get the current OpenGL context + final GL gl = GLContext.getCurrentGL(); - return new Rectangle2D.Double((int) Math.floor(src.getMinX() - boundary), - (int) Math.floor(src.getMinY() - boundary), - (int) Math.ceil(src.getWidth() + 2 * boundary), - (int) Math.ceil(src.getHeight()) + 2 * boundary); + // Tear down components + glyphCache.endRendering(gl); + glyphRenderer.endRendering(gl); } - private TextureRenderer getBackingStore() { - final TextureRenderer renderer = (TextureRenderer) packer.getBackingStore(); + /** + * Forces all stored text to be rendered. + * + *

+ * This should be called after each call to {@code draw} if you are setting OpenGL state such + * as the modelview matrix between calls to {@code draw}. + * + * @throws GLException if no OpenGL context is current, or is unexpected version + * @throws IllegalStateException if not in a render cycle + */ + public void flush() { - if (renderer != cachedBackingStore) { - // Backing store changed since last time; discard any cached Graphics2D - if (cachedGraphics != null) { - cachedGraphics.dispose(); - cachedGraphics = null; - cachedFontRenderContext = null; - } + // Get the current OpenGL context + final GL gl = GLContext.getCurrentGL(); - cachedBackingStore = renderer; - } + // Make sure glyph cache is up to date + glyphCache.update(gl); - return cachedBackingStore; + // Render outstanding glyphs + glyphRenderer.flush(gl); } - private Graphics2D getGraphics2D() { - final 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; + /** + * Determines the bounding box of a character sequence. + * + *

+ * Assumes 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 + * 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. + * + * @param text Text to get bounding box for + * @return Rectangle surrounding the given text, not null + * @throws NullPointerException if text is null + */ + /*@Nonnull*/ + public Rectangle2D getBounds(/*@Nonnull*/ final CharSequence text) { + Check.notNull(text, "Text cannot be null"); + return getBounds(text.toString()); } - private void beginRendering(final boolean ortho, final int width, final int height, - final boolean disableDepthTestForOrtho) { - final GL2 gl = GLContext.getCurrentGL().getGL2(); - - if (DEBUG && !debugged) { - debug(gl); - } - - inBeginEndPair = true; - isOrthoMode = ortho; - beginRenderingWidth = width; - beginRenderingHeight = height; - beginRenderingDepthTestDisabled = disableDepthTestForOrtho; - - if (ortho) { - getBackingStore().beginOrthoRendering(width, height, - disableDepthTestForOrtho); - } else { - getBackingStore().begin3DRendering(); - } - - // Push client attrib bits used by the pipelined quad renderer - gl.glPushClientAttrib((int) GL2.GL_ALL_CLIENT_ATTRIB_BITS); - - if (!haveMaxSize) { - // Query OpenGL for the maximum texture size and set it in the - // RectanglePacker to keep it from expanding too large - final int[] sz = new int[1]; - gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, sz, 0); - packer.setMaxSize(sz[0], sz[0]); - haveMaxSize = true; - } - - if (needToResetColor && haveCachedColor) { - if (cachedColor == null) { - getBackingStore().setColor(cachedR, cachedG, cachedB, cachedA); - } else { - getBackingStore().setColor(cachedColor); - } - - needToResetColor = false; - } - - // Disable future attempts to use mipmapping if TextureRenderer - // doesn't support it - if (mipmap && !getBackingStore().isUsingAutoMipmapGeneration()) { - if (DEBUG) { - System.err.println("Disabled mipmapping in TextRenderer"); - } - - mipmap = false; - } + /** + * Determines the bounding box of a string. + * + * @param text Text to get bounding box for + * @return Rectangle surrounding the given text, not null + * @throws NullPointerException if text is null + */ + /*@Nonnull*/ + public Rectangle2D getBounds(/*@Nonnull*/ final String text) { + Check.notNull(text, "Text cannot be null"); + return glyphProducer.findBounds(text); } /** - * emzic: here the call to glBindBuffer crashes on certain graphicscard/driver combinations - * this is why the ugly try-catch block has been added, which falls back to the old textrenderer + * Determines the pixel width of a character. * - * @param ortho - * @throws GLException + * @param c Character to get pixel width of + * @return Number of pixels required to advance past the character */ - private void endRendering(final boolean ortho) throws GLException { - flushGlyphPipeline(); - - inBeginEndPair = false; - - final GL2 gl = GLContext.getCurrentGL().getGL2(); - - // Pop client attrib bits used by the pipelined quad renderer - gl.glPopClientAttrib(); - - // The OpenGL spec is unclear about whether this changes the - // buffer bindings, so preemptively zero out the GL_ARRAY_BUFFER - // binding - if (getUseVertexArrays() && is15Available(gl)) { - try { - gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); - } catch (final Exception e) { - isExtensionAvailable_GL_VERSION_1_5 = false; - } - } - - if (ortho) { - getBackingStore().endOrthoRendering(); - } else { - getBackingStore().end3DRendering(); - } - - if (++numRenderCycles >= CYCLES_PER_FLUSH) { - numRenderCycles = 0; - - if (DEBUG) { - System.err.println("Clearing unused entries in endRendering()"); - } - - clearUnusedEntries(); - } + public float getCharWidth(final char c) { + return glyphProducer.findAdvance(c); } - private void clearUnusedEntries() { - final java.util.List deadRects = new ArrayList(); - - // Iterate through the contents of the backing store, removing - // text strings that haven't been used recently - packer.visit(new RectVisitor() { - @Override - public void visit(final Rect rect) { - final TextData data = (TextData) rect.getUserData(); - - if (data.used()) { - data.clearUsed(); - } else { - deadRects.add(rect); - } - } - }); - - for (final Rect r : deadRects) { - packer.remove(r); - stringLocations.remove(((TextData) r.getUserData()).string()); - - final int unicodeToClearFromCache = ((TextData) r.getUserData()).unicodeID; - - if (unicodeToClearFromCache > 0) { - mGlyphProducer.clearCacheEntry(unicodeToClearFromCache); - } - - // if (DEBUG) { - // Graphics2D g = getGraphics2D(); - // g.setComposite(AlphaComposite.Clear); - // g.fillRect(r.x(), r.y(), r.w(), r.h()); - // g.setComposite(AlphaComposite.Src); - // } - } - - // If we removed dead rectangles this cycle, try to do a compaction - final 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().markDirty(0, 0, getBackingStore().getWidth(), - getBackingStore().getHeight()); - } + /** + * Determines the font this {@link TextRenderer} is using. + * + * @return Font used by this text renderer, not null + */ + /*@Nonnull*/ + public Font getFont() { + return font; } - private void internal_draw3D(final CharSequence str, float x, final float y, final float z, - final float scaleFactor) { - for (final Glyph glyph : mGlyphProducer.getGlyphs(str)) { - final float advance = glyph.draw3D(x, y, z, scaleFactor); - x += advance * scaleFactor; - } + /** + * Checks if the backing texture is using linear interpolation. + * + * @return True if the backing texture is using linear interpolation. + */ + public boolean getSmoothing() { + return glyphCache.getUseSmoothing(); } - private void flushGlyphPipeline() { - if (mPipelinedQuadRenderer != null) { - mPipelinedQuadRenderer.draw(); - } + /** + * Checks if vertex arrays are in-use. + * + *

+ * Indicates whether vertex arrays are being used internally for rendering, or whether text is + * rendered using the OpenGL immediate mode commands. Defaults to true. + */ + public boolean getUseVertexArrays() { + return glyphRenderer.getUseVertexArrays(); } - private void draw3D_ROBUST(final CharSequence str, final float x, final float y, final float z, - final float scaleFactor) { - String curStr; - if (str instanceof String) { - curStr = (String) str; - } else { - curStr = str.toString(); - } - - // Look up the string on the backing store - Rect rect = stringLocations.get(curStr); - - if (rect == null) { - // Rasterize this string and place it on the backing store - Graphics2D g = getGraphics2D(); - final Rectangle2D origBBox = preNormalize(renderDelegate.getBounds(curStr, font, getFontRenderContext())); - final Rectangle2D bbox = normalize(origBBox); - final 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, origBBox, -1)); - - 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 - final int strx = rect.x() + origin.x; - final int stry = rect.y() + origin.y; - - // Clear out the area we're going to draw into - g.setComposite(AlphaComposite.Clear); - g.fillRect(rect.x(), rect.y(), rect.w(), rect.h()); - g.setComposite(AlphaComposite.Src); - - // Draw the string - renderDelegate.draw(g, curStr, strx, stry); - - if (DRAW_BBOXES) { - final TextData data = (TextData) rect.getUserData(); - // Draw a bounding box on the backing store - g.drawRect(strx - data.origOriginX(), - stry - data.origOriginY(), - (int) data.origRect().getWidth(), - (int) data.origRect().getHeight()); - g.drawRect(strx - data.origin().x, - stry - data.origin().y, - rect.w(), - rect.h()); - } - - // Mark this region of the TextureRenderer as dirty - getBackingStore().markDirty(rect.x(), rect.y(), rect.w(), - rect.h()); - } - - // OK, now draw the portion of the backing store to the screen - final 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!!! - final TextData data = (TextData) rect.getUserData(); - data.markUsed(); + /** + * Specifies the current color of this {@link TextRenderer} using a {@link Color}. + * + * @param color Color to use for rendering text + * @throws NullPointerException if color is null + * @throws GLException if an OpenGL context is not current + */ + public void setColor(/*@Nonnull*/ final Color color) { - final Rectangle2D origRect = data.origRect(); + Check.notNull(color, "Color cannot be null"); - // Align the leftmost point of the baseline to the (x, y, z) coordinate requested - renderer.draw3DRect(x - (scaleFactor * data.origOriginX()), - y - (scaleFactor * ((float) origRect.getHeight() - data.origOriginY())), z, - rect.x() + (data.origin().x - data.origOriginX()), - renderer.getHeight() - rect.y() - (int) origRect.getHeight() - - (data.origin().y - data.origOriginY()), - (int) origRect.getWidth(), (int) origRect.getHeight(), scaleFactor); + final float r = ((float) color.getRed()) / 255f; + final float g = ((float) color.getGreen()) / 255f; + final float b = ((float) color.getBlue()) / 255f; + final float a = ((float) color.getAlpha()) / 255f; + setColor(r, g, b, a); } - //---------------------------------------------------------------------- - // Debugging functionality - // - private void debug(final GL gl) { - dbgFrame = new Frame("TextRenderer Debug Output"); - - final GLCanvas dbgCanvas = new GLCanvas(new GLCapabilities(gl.getGLProfile())); - dbgCanvas.setSharedContext(GLContext.getCurrent()); - dbgCanvas.addGLEventListener(new DebugListener(gl, dbgFrame)); - dbgFrame.add(dbgCanvas); - - final FPSAnimator anim = new FPSAnimator(dbgCanvas, 10); - dbgFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(final 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() { - @Override - public void run() { - anim.stop(); - } - }).start(); - } - }); - dbgFrame.setSize(kSize, kSize); - dbgFrame.setVisible(true); - anim.start(); - debugged = true; + /** + * Specifies the current color of this {@link TextRenderer} using individual components. + * + *

+ * Each component ranges from 0.0f to 1.0f. The alpha component, if used, does not need to be + * premultiplied into the color channels as described in the documentation for {@link + * com.jogamp.opengl.util.texture.Texture Texture} (although premultiplied colors are used + * internally). The default color is opaque white. + * + * @param r Red component of the new color + * @param g Green component of the new color + * @param b Blue component of the new color + * @param a Alpha component of the new color + */ + public void setColor(/*@CheckForSigned*/ final float r, + /*@CheckForSigned*/ final float g, + /*@CheckForSigned*/ final float b, + /*@CheckForSigned*/ final float a) { + glyphRenderer.setColor(r, g, b, a); } - /** Class supporting more full control over the process of rendering - the bitmapped text. Allows customization of whether the backing - store text bitmap is full-color or intensity only, the size of - each individual rendered text rectangle, and the contents of - each individual rendered text string. The default implementation - of this interface uses an intensity-only texture, a - closely-cropped rectangle around the text, and renders text - using the color white, which is modulated by the set color - during the rendering process. */ - public static interface RenderDelegate { - /** Indicates whether the backing store of this TextRenderer - should be intensity-only (the default) or full-color. */ - public boolean intensityOnly(); - - /** Computes the bounds of the given String relative to the - origin. */ - public Rectangle2D getBounds(String str, Font font, - FontRenderContext frc); - - /** Computes the bounds of the given character sequence relative - to the origin. */ - public Rectangle2D getBounds(CharSequence str, Font font, - FontRenderContext frc); - - /** Computes the bounds of the given GlyphVector, already - assumed to have been created for a particular Font, - relative to the origin. */ - public Rectangle2D getBounds(GlyphVector gv, FontRenderContext frc); - - /** Render the passed character sequence at the designated - location using the supplied Graphics2D instance. The - surrounding region will already have been cleared to the RGB - color (0, 0, 0) with zero alpha. The initial drawing context - of the passed Graphics2D will be set to use - AlphaComposite.Src, the color white, the Font specified in the - TextRenderer's constructor, and the rendering hints specified - in the TextRenderer constructor. Changes made by the end user - may be visible in successive calls to this method, but are not - guaranteed to be preserved. Implementors of this method - should reset the Graphics2D's state to that desired each time - this method is called, in particular those states which are - not the defaults. */ - public void draw(Graphics2D graphics, String str, int x, int y); - - /** Render the passed GlyphVector at the designated location using - the supplied Graphics2D instance. The surrounding region will - already have been cleared to the RGB color (0, 0, 0) with zero - alpha. The initial drawing context of the passed Graphics2D - will be set to use AlphaComposite.Src, the color white, the - Font specified in the TextRenderer's constructor, and the - rendering hints specified in the TextRenderer constructor. - Changes made by the end user may be visible in successive - calls to this method, but are not guaranteed to be preserved. - Implementors of this method should reset the Graphics2D's - state to that desired each time this method is called, in - particular those states which are not the defaults. */ - public void drawGlyphVector(Graphics2D graphics, GlyphVector str, - int x, int y); + /** + * Specifies whether the backing texture will use linear interpolation. + * + *

+ * If smoothing is enabled, {@code GL_LINEAR} will be used. Otherwise it uses {@code + * GL_NEAREST}. + * + *

+ * Defaults to true. + * + *

+ * A few graphics cards do not behave well when this is enabled, resulting in fuzzy text. + */ + public void setSmoothing(final boolean smoothing) { + glyphCache.setUseSmoothing(smoothing); } - private static class CharSequenceIterator implements CharacterIterator { - CharSequence mSequence; - int mLength; - int mCurrentIndex; - - CharSequenceIterator() { - } - - CharSequenceIterator(final CharSequence sequence) { - initFromCharSequence(sequence); - } - - public void initFromCharSequence(final CharSequence sequence) { - mSequence = sequence; - mLength = mSequence.length(); - mCurrentIndex = 0; - } - - @Override - public char last() { - mCurrentIndex = Math.max(0, mLength - 1); + /** + * Changes the transformation matrix used for drawing text in 3D. + * + * @param matrix Transformation matrix in column-major order + * @throws NullPointerException if matrix is null + * @throws IndexOutOfBoundsException if length of matrix is less than sixteen + * @throws IllegalStateException if in orthographic mode + */ + public void setTransform(/*@Nonnull*/ final float matrix[]) { + Check.notNull(matrix, "Matrix cannot be null"); + glyphRenderer.setTransform(matrix, false); + } - return current(); - } + /** + * Changes whether vertex arrays are in use. + * + *

+ * This is provided as a concession for certain graphics cards which have poor vertex array + * performance. If passed true, the text renderer will use vertex arrays or a vertex buffer + * internally for rendering. Otherwise it will use immediate mode commands. Defaults to + * true. + * + * @param useVertexArrays True to render with vertex arrays + */ + public void setUseVertexArrays(final boolean useVertexArrays) { + glyphRenderer.setUseVertexArrays(useVertexArrays); + } - @Override - public char current() { - if ((mLength == 0) || (mCurrentIndex >= mLength)) { - return CharacterIterator.DONE; - } + /** + * Utility supporting more full control over rendering the bitmapped text. + * + *

+ * Allows customization of whether the backing store text bitmap is full-color or intensity + * only, the size of each individual rendered text rectangle, and the contents of each + * individual rendered text string. + */ + public interface RenderDelegate { + + /** + * Renders text into a graphics instance at a specific location. + * + *

+ * The surrounding region will already have been cleared to the RGB color (0, 0, 0) with + * zero alpha. The initial drawing context of the passed Graphics2D will be set to use + * AlphaComposite.Src, the color white, the Font specified in the TextRenderer's + * constructor, and the rendering hints specified in the TextRenderer constructor. + * + *

+ * Changes made by the end user may be visible in successive calls to this method, but are + * not guaranteed to be preserved. Implementations should reset the Graphics2D's state to + * that desired each time this method is called, in particular those states which are not + * the defaults. + * + * @param g2d Graphics to render into + * @param str Text to render + * @param x Location on X axis to render at + * @param y Location on Y axis to render at + * @throws NullPointerException if graphics or text is null + */ + void draw(/*@Nonnull*/ Graphics2D g2d, + /*@Nonnull*/ String str, + /*@CheckForSigned*/ int x, + /*@CheckForSigned*/ int y); + + /** + * Renders a glyph into a graphics instance at a specific location. + * + *

+ * The surrounding region will already have been cleared to the RGB color (0, 0, 0) with + * zero alpha. The initial drawing context of the passed Graphics2D will be set to use + * AlphaComposite.Src, the color white, the Font specified in the TextRenderer's + * constructor, and the rendering hints specified in the TextRenderer constructor. + * + *

+ * Changes made by the end user may be visible in successive calls to this method, but are + * not guaranteed to be preserved. Implementations should reset the Graphics2D's state to + * that desired each time this method is called, in particular those states which are not + * the defaults. + * + * @param g2d Graphics to render into + * @param gv Glyph to render + * @param x Location on X axis to render at + * @param y Location on Y axis to render at + * @throws NullPointerException if graphics or glyph is null + */ + void drawGlyphVector(/*@Nonnull*/ Graphics2D g2d, + /*@Nonnull*/ GlyphVector gv, + /*@CheckForSigned*/ int x, + /*@CheckForSigned*/ int y); + + /** + * Computes the bounds of a character sequence relative to the origin. + * + * @param text Text to compute bounds of + * @param font Font text renderer is using + * @param frc Device-dependent details of how text should be rendered + * @return Rectangle surrounding the text, not null + * @throws NullPointerException if text, font, or font render context is null + */ + /*@Nonnull*/ + Rectangle2D getBounds(/*@Nonnull*/ CharSequence text, + /*@Nonnull*/ Font font, + /*@Nonnull*/ FontRenderContext frc); + + /** + * Computes the bounds of a glyph relative to the origin. + * + * @param gv Glyph to compute bounds of (non-null) + * @param frc Device-dependent details of how text should be rendered (non-null) + * @return Rectangle surrounding the text (non-null) + * @throws NullPointerException if glyph or font render context is null + */ + Rectangle2D getBounds(/*@Nonnull*/ GlyphVector gv, /*@Nonnull*/ FontRenderContext frc); + + /** + * Computes the bounds of a string relative to the origin. + * + * @param text Text to compute bounds of + * @param font Font text renderer is using + * @param frc Device-dependent details of how text should be rendered (non-null) + * @return Rectangle surrounding the text, not null + * @throws NullPointerException if text, font, or font render context is null + */ + Rectangle2D getBounds(/*@Nonnull*/ String text, + /*@Nonnull*/ Font font, + /*@Nonnull*/ FontRenderContext frc); + + /** + * Indicates whether the backing store should be intensity-only or full-color. + * + *

+ * Note that currently the text renderer does not support full-color. It will throw an + * {@link UnsupportedOperationException} if the render delegate requests full-color. + * + * @return True if the backing store should be intensity-only + */ + boolean intensityOnly(); + } - return mSequence.charAt(mCurrentIndex); - } + /** + * Simple render delegate if one is not specified by the user. + */ + /*@Immmutable*/ + public static class DefaultRenderDelegate implements RenderDelegate { @Override - public char next() { - mCurrentIndex++; - - return current(); - } + public void draw(/*@Nonnull*/ final Graphics2D g2d, + /*@Nonnull*/ final String str, + /*@CheckForSigned*/ final int x, + /*@CheckForSigned*/ final int y) { - @Override - public char previous() { - mCurrentIndex = Math.max(mCurrentIndex - 1, 0); + Check.notNull(g2d, "Graphics cannot be null"); + Check.notNull(str, "String cannot be null"); - return current(); + g2d.drawString(str, x, y); } @Override - public char setIndex(final int position) { - mCurrentIndex = position; + public void drawGlyphVector(/*@Nonnull*/ final Graphics2D g2d, + /*@Nonnull*/ final GlyphVector gv, + /*@CheckForSigned*/ final int x, + /*@CheckForSigned*/ final int y) { - return current(); - } + Check.notNull(g2d, "Graphics cannot be null"); + Check.notNull(gv, "Glyph vector cannot be null"); - @Override - public int getBeginIndex() { - return 0; + g2d.drawGlyphVector(gv, x, y); } + /*@Nonnull*/ @Override - public int getEndIndex() { - return mLength; - } + public Rectangle2D getBounds(/*@Nonnull*/ final CharSequence text, + /*@Nonnull*/ final Font font, + /*@Nonnull*/ final FontRenderContext frc) { - @Override - public int getIndex() { - return mCurrentIndex; - } + Check.notNull(text, "Text cannot be null"); + Check.notNull(font, "Font cannot be null"); + Check.notNull(frc, "Font render context cannot be null"); - @Override - public Object clone() { - final CharSequenceIterator iter = new CharSequenceIterator(mSequence); - iter.mCurrentIndex = mCurrentIndex; - - return iter; + return getBounds(text.toString(), font, frc); } + /*@Nonnull*/ @Override - public char first() { - if (mLength == 0) { - return CharacterIterator.DONE; - } + public Rectangle2D getBounds(/*@Nonnull*/ final GlyphVector gv, + /*@Nonnull*/ final FontRenderContext frc) { - mCurrentIndex = 0; + Check.notNull(gv, "Glyph vector cannot be null"); + Check.notNull(frc, "Font render context cannot be null"); - return current(); - } - } - - // Data associated with each rectangle of text - static class TextData { - // Back-pointer to String this TextData describes, if it - // represents a String rather than a single glyph - private final String str; - - // If this TextData represents a single glyph, this is its - // unicode ID - int unicodeID; - - // 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 final Point origin; - - // This represents the pre-normalized rectangle, which fits - // within the rectangle on the backing store. We keep a - // one-pixel border around entries on the backing store to - // prevent bleeding of adjacent letters when using GL_LINEAR - // filtering for rendering. The origin of this rectangle is - // equivalent to the origin above. - private final Rectangle2D origRect; - - private boolean used; // Whether this text was used recently - - TextData(final String str, final Point origin, final Rectangle2D origRect, final int unicodeID) { - this.str = str; - this.origin = origin; - this.origRect = origRect; - this.unicodeID = unicodeID; - } - - String string() { - return str; - } - - Point origin() { - return origin; - } - - // The following three methods are used to locate the glyph - // within the expanded rectangle coming from normalize() - int origOriginX() { - return (int) -origRect.getMinX(); - } - - int origOriginY() { - return (int) -origRect.getMinY(); - } - - Rectangle2D origRect() { - return origRect; - } - - boolean used() { - return used; - } - - void markUsed() { - used = true; + return gv.getVisualBounds(); } - void clearUsed() { - used = false; - } - } - - class Manager implements BackingStoreManager { - private Graphics2D g; - + /*@Nonnull*/ @Override - public Object allocateBackingStore(final int w, final 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; - - if (renderDelegate.intensityOnly()) { - renderer = TextureRenderer.createAlphaOnlyRenderer(w, h, mipmap); - } else { - renderer = new TextureRenderer(w, h, true, mipmap); - } - renderer.setSmoothing(smoothing); + public Rectangle2D getBounds(/*@Nonnull*/ final String text, + /*@Nonnull*/ final Font font, + /*@Nonnull*/ final FontRenderContext frc) { - if (DEBUG) { - System.err.println(" TextRenderer allocating backing store " + - w + " x " + h); - } + Check.notNull(text, "Text cannot be null"); + Check.notNull(font, "Font cannot be null"); + Check.notNull(frc, "Font render context cannot be null"); - return renderer; + return getBounds(font.createGlyphVector(frc, text), frc); } @Override - public void deleteBackingStore(final Object backingStore) { - ((TextureRenderer) backingStore).dispose(); + public boolean intensityOnly() { + return true; } + } - @Override - public boolean preExpand(final Rect cause, final 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); - } - - if (inBeginEndPair) { - // Draw any outstanding glyphs - flush(); - } - - clearUnusedEntries(); - - return true; - } - - return false; - } + /** + * Utility for coordinating text renderer components. + */ + private final class Mediator implements GlyphCache.EventListener, GlyphRenderer.EventListener { @Override - public boolean additionFailed(final Rect cause, final int attemptNumber) { - // Heavy hammer -- might consider doing something different - packer.clear(); - stringLocations.clear(); - mGlyphProducer.clearAllCacheEntries(); - - if (DEBUG) { - System.err.println( - " *** Cleared all text because addition failed ***"); - } + public void onGlyphCacheEvent(/*@Nonnull*/ final GlyphCache.EventType type, + /*@Nonnull*/ final Object data) { - if (attemptNumber == 0) { - return true; - } + Check.notNull(type, "Event type cannot be null"); + Check.notNull(data, "Data cannot be null"); - return false; - } - - @Override - public boolean canCompact() { - return true; - } - - @Override - public void beginMovement(final Object oldBackingStore, final Object newBackingStore) { - // Exit the begin / end pair if necessary - if (inBeginEndPair) { - // Draw any outstanding glyphs + switch (type) { + case REALLOCATE: flush(); - - final GL2 gl = GLContext.getCurrentGL().getGL2(); - - // Pop client attrib bits used by the pipelined quad renderer - gl.glPopClientAttrib(); - - // The OpenGL spec is unclear about whether this changes the - // buffer bindings, so preemptively zero out the GL_ARRAY_BUFFER - // binding - if (getUseVertexArrays() && is15Available(gl)) { - try { - gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); - } catch (final Exception e) { - isExtensionAvailable_GL_VERSION_1_5 = false; - } - } - - if (isOrthoMode) { - ((TextureRenderer) oldBackingStore).endOrthoRendering(); - } else { - ((TextureRenderer) oldBackingStore).end3DRendering(); - } + break; + case CLEAR: + glyphProducer.clearGlyphs(); + break; + case CLEAN: + glyphProducer.removeGlyph((Glyph) data); + break; } - - final TextureRenderer newRenderer = (TextureRenderer) newBackingStore; - g = newRenderer.createGraphics(); } @Override - public void move(final Object oldBackingStore, final Rect oldLocation, - final Object newBackingStore, final Rect newLocation) { - final TextureRenderer oldRenderer = (TextureRenderer) oldBackingStore; - final 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 - final 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 onGlyphRendererEvent(/*@Nonnull*/ final GlyphRenderer.EventType type) { - @Override - public void endMovement(final Object oldBackingStore, final Object newBackingStore) { - g.dispose(); - - // Sync the whole surface - final TextureRenderer newRenderer = (TextureRenderer) newBackingStore; - newRenderer.markDirty(0, 0, newRenderer.getWidth(), - newRenderer.getHeight()); - - // Re-enter the begin / end pair if necessary - if (inBeginEndPair) { - if (isOrthoMode) { - ((TextureRenderer) newBackingStore).beginOrthoRendering(beginRenderingWidth, - beginRenderingHeight, beginRenderingDepthTestDisabled); - } else { - ((TextureRenderer) newBackingStore).begin3DRendering(); - } + Check.notNull(type, "Event type cannot be null"); - // Push client attrib bits used by the pipelined quad renderer - final GL2 gl = GLContext.getCurrentGL().getGL2(); - gl.glPushClientAttrib((int) GL2.GL_ALL_CLIENT_ATTRIB_BITS); - - if (haveCachedColor) { - if (cachedColor == null) { - ((TextureRenderer) newBackingStore).setColor(cachedR, - cachedG, cachedB, cachedA); - } else { - ((TextureRenderer) newBackingStore).setColor(cachedColor); - } - } - } else { - needToResetColor = true; + switch (type) { + case AUTOMATIC_FLUSH: + final GL gl = GLContext.getCurrentGL(); + glyphCache.update(gl); + break; } } } - public static class DefaultRenderDelegate implements RenderDelegate { - @Override - public boolean intensityOnly() { - return true; + /** + * Proxy for a {@link GlyphRenderer}. + */ + /*@NotThreadSafe*/ + private static final class GlyphRendererProxy implements GlyphRenderer { + + /** + * Delegate to actually render. + */ + /*@CheckForNull*/ + private GlyphRenderer delegate; + + /** + * Listeners added before a delegate is chosen. + */ + /*@Nonnull*/ + private final List listeners = new ArrayList(); + + /** + * Red component of color. + */ + /*@CheckForSigned*/ + private Float r; + + /** + * Green component of color. + */ + /*@CheckForSigned*/ + private Float g; + + /** + * Blue component of color. + */ + /*@CheckForSigned*/ + private Float b; + + /** + * Alpha component of color. + */ + /*@CheckForSigned*/ + private Float a; + + /** + * Transform matrix. + */ + /*@CheckForNull*/ + private float[] transform; + + /** + * True if transform is transposed. + */ + /*@CheckForNull*/ + private Boolean transposed; + + /** + * True to use vertex arrays. + */ + private boolean useVertexArrays = true; + + GlyphRendererProxy() { + // empty } @Override - public Rectangle2D getBounds(final CharSequence str, final Font font, - final FontRenderContext frc) { - return getBounds(font.createGlyphVector(frc, - new CharSequenceIterator(str)), - frc); - } + public void addListener(/*@Nonnull*/ final EventListener listener) { - @Override - public Rectangle2D getBounds(final String str, final Font font, - final FontRenderContext frc) { - return getBounds(font.createGlyphVector(frc, str), frc); - } + Check.notNull(listener, "Listener cannot be null"); - @Override - public Rectangle2D getBounds(final GlyphVector gv, final FontRenderContext frc) { - return gv.getVisualBounds(); - } - - @Override - public void drawGlyphVector(final Graphics2D graphics, final GlyphVector str, - final int x, final int y) { - graphics.drawGlyphVector(str, x, y); + if (delegate == null) { + listeners.add(listener); + } else { + delegate.addListener(listener); + } } @Override - public void draw(final Graphics2D graphics, final String str, final int x, final int y) { - graphics.drawString(str, x, y); - } - } - - //---------------------------------------------------------------------- - // Glyph-by-glyph rendering support - // - - // A temporary to prevent excessive garbage creation - private final char[] singleUnicode = new char[1]; - - /** A Glyph represents either a single unicode glyph or a - substring of characters to be drawn. The reason for the dual - behavior is so that we can take in a sequence of unicode - characters and partition them into runs of individual glyphs, - but if we encounter complex text and/or unicode sequences we - don't understand, we can render them using the - string-by-string method.

- - Glyphs need to be able to re-upload themselves to the backing - store on demand as we go along in the render sequence. - */ - - class Glyph { - // If this Glyph represents an individual unicode glyph, this - // is its unicode ID. If it represents a String, this is -1. - private int unicodeID; - // If the above field isn't -1, then these fields are used. - // The glyph code in the font - private int glyphCode; - // The GlyphProducer which created us - private GlyphProducer producer; - // The advance of this glyph - private float advance; - // The GlyphVector for this single character; this is passed - // in during construction but cleared during the upload - // process - private GlyphVector singleUnicodeGlyphVector; - // The rectangle of this glyph on the backing store, or null - // if it has been cleared due to space pressure - private Rect glyphRectForTextureMapping; - // If this Glyph represents a String, this is the sequence of - // characters - private String str; - // Whether we need a valid advance when rendering this string - // (i.e., whether it has other single glyphs coming after it) - private boolean needAdvance; - - // Creates a Glyph representing an individual Unicode character - public Glyph(final int unicodeID, - final int glyphCode, - final float advance, - final GlyphVector singleUnicodeGlyphVector, - final GlyphProducer producer) { - this.unicodeID = unicodeID; - this.glyphCode = glyphCode; - this.advance = advance; - this.singleUnicodeGlyphVector = singleUnicodeGlyphVector; - this.producer = producer; - } + public void beginRendering(/*@Nonnull*/ final GL gl, + final boolean ortho, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean disableDepthTest) { - // Creates a Glyph representing a sequence of characters, with - // an indication of whether additional single glyphs are being - // rendered after it - public Glyph(final String str, final boolean needAdvance) { - this.str = str; - this.needAdvance = needAdvance; - } + Check.notNull(gl, "GL cannot be null"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); - /** Returns this glyph's unicode ID */ - public int getUnicodeID() { - return unicodeID; - } + if (delegate == null) { - /** Returns this glyph's (font-specific) glyph code */ - public int getGlyphCode() { - return glyphCode; - } + // Create the glyph renderer + delegate = GlyphRenderers.get(gl); - /** Returns the advance for this glyph */ - public float getAdvance() { - return advance; - } - - /** Draws this glyph and returns the (x) advance for this glyph */ - public float draw3D(final float inX, final float inY, final float z, final float scaleFactor) { - if (str != null) { - draw3D_ROBUST(str, inX, inY, z, scaleFactor); - if (!needAdvance) { - return 0; - } - // Compute and return the advance for this string - final GlyphVector gv = font.createGlyphVector(getFontRenderContext(), str); - float totalAdvance = 0; - for (int i = 0; i < gv.getNumGlyphs(); i++) { - totalAdvance += gv.getGlyphMetrics(i).getAdvance(); + // Add the event listeners + for (EventListener listener : listeners) { + delegate.addListener(listener); } - return totalAdvance; - } - - // This is the code path taken for individual glyphs - if (glyphRectForTextureMapping == null) { - upload(); - } - try { - if (mPipelinedQuadRenderer == null) { - mPipelinedQuadRenderer = new Pipelined_QuadRenderer(); + // Specify the color + if ((r != null) && (g != null) && (b != null) && (a != null)) { + delegate.setColor(r, g, b, a); } - final TextureRenderer renderer = getBackingStore(); - // Handles case where NPOT texture is used for backing store - final TextureCoords wholeImageTexCoords = renderer.getTexture().getImageTexCoords(); - final float xScale = wholeImageTexCoords.right(); - final float yScale = wholeImageTexCoords.bottom(); - - final Rect rect = glyphRectForTextureMapping; - final TextData data = (TextData) rect.getUserData(); - data.markUsed(); - - final Rectangle2D origRect = data.origRect(); - - final float x = inX - (scaleFactor * data.origOriginX()); - final float y = inY - (scaleFactor * ((float) origRect.getHeight() - data.origOriginY())); - - final int texturex = rect.x() + (data.origin().x - data.origOriginX()); - final int texturey = renderer.getHeight() - rect.y() - (int) origRect.getHeight() - - (data.origin().y - data.origOriginY()); - final int width = (int) origRect.getWidth(); - final int height = (int) origRect.getHeight(); - - final float tx1 = xScale * texturex / renderer.getWidth(); - final float ty1 = yScale * (1.0f - - ((float) texturey / (float) renderer.getHeight())); - final float tx2 = xScale * (texturex + width) / renderer.getWidth(); - final float ty2 = yScale * (1.0f - - ((float) (texturey + height) / (float) renderer.getHeight())); - - mPipelinedQuadRenderer.glTexCoord2f(tx1, ty1); - mPipelinedQuadRenderer.glVertex3f(x, y, z); - mPipelinedQuadRenderer.glTexCoord2f(tx2, ty1); - mPipelinedQuadRenderer.glVertex3f(x + (width * scaleFactor), y, - z); - mPipelinedQuadRenderer.glTexCoord2f(tx2, ty2); - mPipelinedQuadRenderer.glVertex3f(x + (width * scaleFactor), - y + (height * scaleFactor), z); - mPipelinedQuadRenderer.glTexCoord2f(tx1, ty2); - mPipelinedQuadRenderer.glVertex3f(x, - y + (height * scaleFactor), z); - } catch (final Exception e) { - e.printStackTrace(); - } - return advance; - } - - /** Notifies this glyph that it's been cleared out of the cache */ - public void clear() { - glyphRectForTextureMapping = null; - } - - private void upload() { - final GlyphVector gv = getGlyphVector(); - final Rectangle2D origBBox = preNormalize(renderDelegate.getBounds(gv, getFontRenderContext())); - final Rectangle2D bbox = normalize(origBBox); - final Point origin = new Point((int) -bbox.getMinX(), - (int) -bbox.getMinY()); - final Rect rect = new Rect(0, 0, (int) bbox.getWidth(), - (int) bbox.getHeight(), - new TextData(null, origin, origBBox, unicodeID)); - packer.add(rect); - glyphRectForTextureMapping = rect; - final Graphics2D g = getGraphics2D(); - // OK, should now have an (x, y) for this rectangle; rasterize - // the glyph - final int strx = rect.x() + origin.x; - final int stry = rect.y() + origin.y; - - // Clear out the area we're going to draw into - g.setComposite(AlphaComposite.Clear); - g.fillRect(rect.x(), rect.y(), rect.w(), rect.h()); - g.setComposite(AlphaComposite.Src); - - // Draw the string - renderDelegate.drawGlyphVector(g, gv, strx, stry); - - if (DRAW_BBOXES) { - final TextData data = (TextData) rect.getUserData(); - // Draw a bounding box on the backing store - g.drawRect(strx - data.origOriginX(), - stry - data.origOriginY(), - (int) data.origRect().getWidth(), - (int) data.origRect().getHeight()); - g.drawRect(strx - data.origin().x, - stry - data.origin().y, - rect.w(), - rect.h()); - } - - // Mark this region of the TextureRenderer as dirty - getBackingStore().markDirty(rect.x(), rect.y(), rect.w(), - rect.h()); - // Re-register ourselves with our producer - producer.register(this); - } - - private GlyphVector getGlyphVector() { - final GlyphVector gv = singleUnicodeGlyphVector; - if (gv != null) { - singleUnicodeGlyphVector = null; // Don't need this anymore - return gv; - } - singleUnicode[0] = (char) unicodeID; - return font.createGlyphVector(getFontRenderContext(), singleUnicode); - } - } - - class GlyphProducer { - static final int undefined = -2; - final FontRenderContext fontRenderContext = null; // FIXME: Never initialized! - List glyphsOutput = new ArrayList(); - HashMap fullGlyphVectorCache = new HashMap(); - HashMap glyphMetricsCache = new HashMap(); - // The mapping from unicode character to font-specific glyph ID - int[] unicodes2Glyphs; - // The mapping from glyph ID to Glyph - Glyph[] glyphCache; - // We re-use this for each incoming string - CharSequenceIterator iter = new CharSequenceIterator(); - - GlyphProducer(final int fontLengthInGlyphs) { - unicodes2Glyphs = new int[512]; - glyphCache = new Glyph[fontLengthInGlyphs]; - clearAllCacheEntries(); - } - - public List getGlyphs(final CharSequence inString) { - glyphsOutput.clear(); - GlyphVector fullRunGlyphVector; - fullRunGlyphVector = fullGlyphVectorCache.get(inString.toString()); - if (fullRunGlyphVector == null) { - iter.initFromCharSequence(inString); - fullRunGlyphVector = font.createGlyphVector(getFontRenderContext(), iter); - fullGlyphVectorCache.put(inString.toString(), fullRunGlyphVector); - } - final boolean complex = (fullRunGlyphVector.getLayoutFlags() != 0); - if (complex || DISABLE_GLYPH_CACHE) { - // Punt to the robust version of the renderer - glyphsOutput.add(new Glyph(inString.toString(), false)); - return glyphsOutput; - } - - final int lengthInGlyphs = fullRunGlyphVector.getNumGlyphs(); - int i = 0; - while (i < lengthInGlyphs) { - final Character letter = CharacterCache.valueOf(inString.charAt(i)); - GlyphMetrics metrics = glyphMetricsCache.get(letter); - if (metrics == null) { - metrics = fullRunGlyphVector.getGlyphMetrics(i); - glyphMetricsCache.put(letter, metrics); - } - final Glyph glyph = getGlyph(inString, metrics, i); - if (glyph != null) { - glyphsOutput.add(glyph); - i++; - } else { - // Assemble a run of characters that don't fit in - // the cache - final StringBuilder buf = new StringBuilder(); - while (i < lengthInGlyphs && - getGlyph(inString, fullRunGlyphVector.getGlyphMetrics(i), i) == null) { - buf.append(inString.charAt(i++)); - } - glyphsOutput.add(new Glyph(buf.toString(), - // Any more glyphs after this run? - i < lengthInGlyphs)); + // Specify the transform + if ((transform != null) && (transposed != null)) { + delegate.setTransform(transform, transposed); } - } - return glyphsOutput; - } - - public void clearCacheEntry(final int unicodeID) { - final int glyphID = unicodes2Glyphs[unicodeID]; - if (glyphID != undefined) { - final Glyph glyph = glyphCache[glyphID]; - if (glyph != null) { - glyph.clear(); - } - glyphCache[glyphID] = null; - } - unicodes2Glyphs[unicodeID] = undefined; - } - - public void clearAllCacheEntries() { - for (int i = 0; i < unicodes2Glyphs.length; i++) { - clearCacheEntry(i); - } - } - - public void register(final Glyph glyph) { - unicodes2Glyphs[glyph.getUnicodeID()] = glyph.getGlyphCode(); - glyphCache[glyph.getGlyphCode()] = glyph; - } - public float getGlyphPixelWidth(final char unicodeID) { - final Glyph glyph = getGlyph(unicodeID); - if (glyph != null) { - return glyph.getAdvance(); + // Specify whether to use vertex arrays or not + delegate.setUseVertexArrays(useVertexArrays); } - - // Have to do this the hard / uncached way - singleUnicode[0] = unicodeID; - if( null == fontRenderContext ) { // FIXME: Never initialized! - throw new InternalError("fontRenderContext never initialized!"); - } - final GlyphVector gv = font.createGlyphVector(fontRenderContext, - singleUnicode); - return gv.getGlyphMetrics(0).getAdvance(); + delegate.beginRendering(gl, ortho, width, height, disableDepthTest); } - // Returns a glyph object for this single glyph. Returns null - // if the unicode or glyph ID would be out of bounds of the - // glyph cache. - private Glyph getGlyph(final CharSequence inString, - final GlyphMetrics glyphMetrics, - final int index) { - final char unicodeID = inString.charAt(index); - - if (unicodeID >= unicodes2Glyphs.length) { - return null; - } - - final int glyphID = unicodes2Glyphs[unicodeID]; - if (glyphID != undefined) { - return glyphCache[glyphID]; - } - - // Must fabricate the glyph - singleUnicode[0] = unicodeID; - final GlyphVector gv = font.createGlyphVector(getFontRenderContext(), singleUnicode); - return getGlyph(unicodeID, gv, glyphMetrics); - } + @Override + public void dispose(/*@Nonnull*/ final GL gl) { - // It's unclear whether this variant might produce less - // optimal results than if we can see the entire GlyphVector - // for the incoming string - private Glyph getGlyph(final int unicodeID) { - if (unicodeID >= unicodes2Glyphs.length) { - return null; - } + Check.notNull(gl, "GL cannot be null"); - final int glyphID = unicodes2Glyphs[unicodeID]; - if (glyphID != undefined) { - return glyphCache[glyphID]; + if (delegate != null) { + delegate.dispose(gl); } - singleUnicode[0] = (char) unicodeID; - final GlyphVector gv = font.createGlyphVector(getFontRenderContext(), singleUnicode); - return getGlyph(unicodeID, gv, gv.getGlyphMetrics(0)); } - private Glyph getGlyph(final int unicodeID, - final GlyphVector singleUnicodeGlyphVector, - final GlyphMetrics metrics) { - final int glyphCode = singleUnicodeGlyphVector.getGlyphCode(0); - // Have seen huge glyph codes (65536) coming out of some fonts in some Unicode situations - if (glyphCode >= glyphCache.length) { - return null; + @Override + public float drawGlyph(/*@Nonnull*/ final GL gl, + /*@Nonnull*/ final Glyph glyph, + /*@CheckForSigned*/ final float x, + /*@CheckForSigned*/ final float y, + /*@CheckForSigned*/ final float z, + /*@CheckForSigned*/ final float scale, + /*@Nonnull*/ final TextureCoords coords) { + + Check.notNull(gl, "GL cannot be null"); + Check.notNull(glyph, "Glyph cannot be null"); + Check.notNull(coords, "Texture coordinates cannot be null"); + + if (delegate == null) { + throw new IllegalStateException("Must be in render cycle!"); + } else { + return delegate.drawGlyph(gl, glyph, x, y, z, scale, coords); } - final Glyph glyph = new Glyph(unicodeID, - glyphCode, - metrics.getAdvance(), - singleUnicodeGlyphVector, - this); - register(glyph); - return glyph; - } - } - - private static class CharacterCache { - private CharacterCache() { } - static final Character cache[] = new Character[127 + 1]; - - static { - for (int i = 0; i < cache.length; i++) { - cache[i] = Character.valueOf((char) i); - } - } + @Override + public void endRendering(/*@Nonnull*/ final GL gl) { - public static Character valueOf(final char c) { - if (c <= 127) { // must cache - return CharacterCache.cache[c]; - } - return Character.valueOf(c); - } - } + Check.notNull(gl, "GL cannot be null"); - class Pipelined_QuadRenderer { - int mOutstandingGlyphsVerticesPipeline = 0; - FloatBuffer mTexCoords; - FloatBuffer mVertCoords; - boolean usingVBOs; - int mVBO_For_ResuableTileVertices; - int mVBO_For_ResuableTileTexCoords; - - Pipelined_QuadRenderer() { - final GL2 gl = GLContext.getCurrentGL().getGL2(); - mVertCoords = Buffers.newDirectFloatBuffer(kTotalBufferSizeCoordsVerts); - mTexCoords = Buffers.newDirectFloatBuffer(kTotalBufferSizeCoordsTex); - - usingVBOs = getUseVertexArrays() && is15Available(gl); - - if (usingVBOs) { - try { - final int[] vbos = new int[2]; - gl.glGenBuffers(2, IntBuffer.wrap(vbos)); - - mVBO_For_ResuableTileVertices = vbos[0]; - mVBO_For_ResuableTileTexCoords = vbos[1]; - - gl.glBindBuffer(GL.GL_ARRAY_BUFFER, - mVBO_For_ResuableTileVertices); - gl.glBufferData(GL.GL_ARRAY_BUFFER, kTotalBufferSizeBytesVerts, - null, GL2ES2.GL_STREAM_DRAW); // stream draw because this is a single quad use pipeline - - gl.glBindBuffer(GL.GL_ARRAY_BUFFER, - mVBO_For_ResuableTileTexCoords); - gl.glBufferData(GL.GL_ARRAY_BUFFER, kTotalBufferSizeBytesTex, - null, GL2ES2.GL_STREAM_DRAW); // stream draw because this is a single quad use pipeline - } catch (final Exception e) { - isExtensionAvailable_GL_VERSION_1_5 = false; - usingVBOs = false; - } + if (delegate == null) { + throw new IllegalStateException("Must be in render cycle!"); + } else { + delegate.endRendering(gl); } } - public void glTexCoord2f(final float v, final float v1) { - mTexCoords.put(v); - mTexCoords.put(v1); - } - - public void glVertex3f(final float inX, final float inY, final float inZ) { - mVertCoords.put(inX); - mVertCoords.put(inY); - mVertCoords.put(inZ); - - mOutstandingGlyphsVerticesPipeline++; + @Override + public void flush(/*@Nonnull*/ final GL gl) { - if (mOutstandingGlyphsVerticesPipeline >= kTotalBufferSizeVerts) { - this.draw(); - } - } + Check.notNull(gl, "GL cannot be null"); - private void draw() { - if (useVertexArrays) { - drawVertexArrays(); + if (delegate == null) { + throw new IllegalStateException("Must be in render cycle!"); } else { - drawIMMEDIATE(); + delegate.flush(gl); } } - private void drawVertexArrays() { - if (mOutstandingGlyphsVerticesPipeline > 0) { - final GL2 gl = GLContext.getCurrentGL().getGL2(); - - final TextureRenderer renderer = getBackingStore(); - renderer.getTexture(); // triggers texture uploads. Maybe this should be more obvious? - - mVertCoords.rewind(); - mTexCoords.rewind(); - - gl.glEnableClientState(GLPointerFunc.GL_VERTEX_ARRAY); - - if (usingVBOs) { - gl.glBindBuffer(GL.GL_ARRAY_BUFFER, - mVBO_For_ResuableTileVertices); - gl.glBufferSubData(GL.GL_ARRAY_BUFFER, 0, - mOutstandingGlyphsVerticesPipeline * kSizeInBytes_OneVertices_VertexData, - mVertCoords); // upload only the new stuff - gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0); - } else { - gl.glVertexPointer(3, GL.GL_FLOAT, 0, mVertCoords); - } - - gl.glEnableClientState(GLPointerFunc.GL_TEXTURE_COORD_ARRAY); - - if (usingVBOs) { - gl.glBindBuffer(GL.GL_ARRAY_BUFFER, - mVBO_For_ResuableTileTexCoords); - gl.glBufferSubData(GL.GL_ARRAY_BUFFER, 0, - mOutstandingGlyphsVerticesPipeline * kSizeInBytes_OneVertices_TexData, - mTexCoords); // upload only the new stuff - gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, 0); - } else { - gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, mTexCoords); - } - - gl.glDrawArrays(GL2GL3.GL_QUADS, 0, - mOutstandingGlyphsVerticesPipeline); - - mVertCoords.rewind(); - mTexCoords.rewind(); - mOutstandingGlyphsVerticesPipeline = 0; + @Override + public boolean getUseVertexArrays() { + if (delegate == null) { + return useVertexArrays; + } else { + return delegate.getUseVertexArrays(); } } - private void drawIMMEDIATE() { - if (mOutstandingGlyphsVerticesPipeline > 0) { - final TextureRenderer renderer = getBackingStore(); - renderer.getTexture(); // triggers texture uploads. Maybe this should be more obvious? - - final GL2 gl = GLContext.getCurrentGL().getGL2(); - gl.glBegin(GL2GL3.GL_QUADS); - - try { - final int numberOfQuads = mOutstandingGlyphsVerticesPipeline / 4; - mVertCoords.rewind(); - mTexCoords.rewind(); - - for (int i = 0; i < numberOfQuads; i++) { - gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get()); - gl.glVertex3f(mVertCoords.get(), mVertCoords.get(), - mVertCoords.get()); - - gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get()); - gl.glVertex3f(mVertCoords.get(), mVertCoords.get(), - mVertCoords.get()); - - gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get()); - gl.glVertex3f(mVertCoords.get(), mVertCoords.get(), - mVertCoords.get()); - - gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get()); - gl.glVertex3f(mVertCoords.get(), mVertCoords.get(), - mVertCoords.get()); - } - } catch (final Exception e) { - e.printStackTrace(); - } finally { - gl.glEnd(); - mVertCoords.rewind(); - mTexCoords.rewind(); - mOutstandingGlyphsVerticesPipeline = 0; - } + @Override + public void setColor(/*@CheckForSigned*/ final float r, + /*@CheckForSigned*/ final float g, + /*@CheckForSigned*/ final float b, + /*@CheckForSigned*/ final float a) { + if (delegate == null) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } else { + delegate.setColor(r, g, b, a); } } - } - - class DebugListener implements GLEventListener { - private GLU glu; - private Frame frame; - - DebugListener(final GL gl, final Frame frame) { - this.glu = GLU.createGLU(gl); - this.frame = frame; - } @Override - public void display(final GLAutoDrawable drawable) { - final GL2 gl = GLContext.getCurrentGL().getGL2(); - gl.glClear(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT); + public void setTransform(/*@Nonnull*/ final float[] value, final boolean transpose) { - if (packer == null) { - return; - } + Check.notNull(value, "Value cannot be null"); - final 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() { - @Override - public void run() { - frame.setSize(w, h); - } - }); + if (delegate == null) { + this.transform = Arrays.copyOf(value, value.length); + this.transposed = transpose; + } else { + delegate.setTransform(value, transpose); } } @Override - public void dispose(final GLAutoDrawable drawable) { - glu=null; - frame=null; - } - - // Unused methods - @Override - public void init(final GLAutoDrawable drawable) { - } - - @Override - public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, - final int height) { - } - - public void displayChanged(final GLAutoDrawable drawable, - final boolean modeChanged, final boolean deviceChanged) { - } - } - - /** - * Sets whether vertex arrays are being used internally for - * rendering, or whether text is rendered using the OpenGL - * immediate mode commands. This is provided as a concession for - * certain graphics cards which have poor vertex array - * performance. Defaults to true. - */ - public void setUseVertexArrays(final boolean useVertexArrays) { - this.useVertexArrays = useVertexArrays; - } - - /** - * Indicates whether vertex arrays are being used internally for - * rendering, or whether text is rendered using the OpenGL - * immediate mode commands. Defaults to true. - */ - public final boolean getUseVertexArrays() { - return useVertexArrays; - } - - /** - * Sets whether smoothing (i.e., GL_LINEAR filtering) is enabled - * in the backing TextureRenderer of this TextRenderer. A few - * graphics cards do not behave well when this is enabled, - * resulting in fuzzy text. Defaults to true. - */ - public void setSmoothing(final boolean smoothing) { - this.smoothing = smoothing; - getBackingStore().setSmoothing(smoothing); - } - - /** - * Indicates whether smoothing is enabled in the backing - * TextureRenderer of this TextRenderer. A few graphics cards do - * not behave well when this is enabled, resulting in fuzzy text. - * Defaults to true. - */ - public boolean getSmoothing() { - return smoothing; - } - - private final boolean is15Available(final GL gl) { - if (!checkFor_isExtensionAvailable_GL_VERSION_1_5) { - isExtensionAvailable_GL_VERSION_1_5 = gl.isExtensionAvailable(GLExtensions.VERSION_1_5); - checkFor_isExtensionAvailable_GL_VERSION_1_5 = true; + public void setUseVertexArrays(final boolean useVertexArrays) { + if (delegate == null) { + this.useVertexArrays = useVertexArrays; + } else { + delegate.setUseVertexArrays(useVertexArrays); + } } - return isExtensionAvailable_GL_VERSION_1_5; } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/packrect/RectanglePacker.java b/src/jogl/classes/com/jogamp/opengl/util/packrect/RectanglePacker.java index 794ae44938..46da66334a 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/packrect/RectanglePacker.java +++ b/src/jogl/classes/com/jogamp/opengl/util/packrect/RectanglePacker.java @@ -48,10 +48,13 @@ backing store, when necessary. */ public class RectanglePacker { + + private static final float DEFAULT_EXPANSION_FACTOR = 0.5f; + private final BackingStoreManager manager; private Object backingStore; private LevelSet levels; - private static final float EXPANSION_FACTOR = 0.5f; + private final float EXPANSION_FACTOR; private static final float SHRINK_FACTOR = 0.3f; private final int initialWidth; @@ -76,10 +79,18 @@ public boolean equals(final Object obj) { public RectanglePacker(final BackingStoreManager manager, final int initialWidth, final int initialHeight) { + this(manager, initialWidth, initialHeight, DEFAULT_EXPANSION_FACTOR); + } + + public RectanglePacker(final BackingStoreManager manager, + final int initialWidth, + final int initialHeight, + final float expansionFactor) { this.manager = manager; levels = new LevelSet(initialWidth, initialHeight); this.initialWidth = initialWidth; this.initialHeight = initialHeight; + EXPANSION_FACTOR = expansionFactor; } public Object getBackingStore() { diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractGlyphProducer.java b/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractGlyphProducer.java new file mode 100644 index 0000000000..c22cbc19ac --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractGlyphProducer.java @@ -0,0 +1,393 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.util.awt.TextRenderer.RenderDelegate; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.HashMap; +import java.util.Map; + + +/** + * Skeletal implementation of {@link GlyphProducer}. + */ +abstract class AbstractGlyphProducer implements GlyphProducer { + + /** + * Reusable array for creating glyph vectors for a single character. + */ + /*@Nonnull*/ + private final char[] characters = new char[1]; + + /** + * Font glyphs made from. + */ + /*@Nonnull*/ + private final Font font; + + /** + * Rendering controller. + */ + /*@Nonnull*/ + private final RenderDelegate renderDelegate; + + /** + * Font render details. + */ + /*@Nonnull*/ + private final FontRenderContext fontRenderContext; + + /** + * Cached glyph vectors. + */ + /*@Nonnull*/ + private final Map glyphVectors = new HashMap(); + + /** + * Returned glyphs. + */ + /*@Nonnull*/ + private final List output = new ArrayList(); + + /** + * View of glyphs. + */ + /*@Nonnull*/ + private final List outputView = Collections.unmodifiableList(output); + + /** + * Constructs an abstract glyph producer. + * + * @param font Font glyphs will be made from + * @param rd Object for controlling rendering + * @param frc Details on how to render fonts + * @throws NullPointerException if font, render delegate, or font render context is null + */ + AbstractGlyphProducer(/*@Nonnull*/ final Font font, + /*@Nonnull*/ final RenderDelegate rd, + /*@Nonnull*/ final FontRenderContext frc) { + + Check.notNull(font, "Font cannot be null"); + Check.notNull(rd, "Render delegate cannot be null"); + Check.notNull(frc, "Font render context cannot be null"); + + this.font = font; + this.renderDelegate = rd; + this.fontRenderContext = frc; + } + + /** + * Adds outer space around a rectangle. + * + *

+ * This method was formally called "normalize." + * + *

+ * Give ourselves a boundary around each entity on the backing store in order to prevent + * bleeding of nearby Strings due to the fact that we use linear filtering + * + *

+ * Note that this boundary is quite heuristic and is related to how far away in 3D we may view + * the text -- heuristically, 1.5% of the font's height. + * + * @param src Original rectangle + * @param font Font being used to create glyphs + * @return Rectangle with margin added, not null + * @throws NullPointerException if rectangle or font is null + */ + /*@Nonnull*/ + private static Rectangle2D addMarginTo(/*@Nonnull*/ final Rectangle2D src, + /*@Nonnull*/ final Font font) { + + final int boundary = (int) Math.max(1, 0.015 * font.getSize()); + final int x = (int) Math.floor(src.getMinX() - boundary); + final int y = (int) Math.floor(src.getMinY() - boundary); + final int w = (int) Math.ceil(src.getWidth() + 2 * boundary); + final int h = (int) Math.ceil(src.getHeight() + 2 * boundary);; + + return new Rectangle2D.Float(x, y, w, h); + } + + /** + * Adds inner space to a rectangle. + * + *

+ * This method was formally called "preNormalize." + * + *

+ * Need to round to integer coordinates. + * + *

+ * Also give ourselves a little slop around the reported bounds of glyphs because it looks like + * neither the visual nor the pixel bounds works perfectly well. + * + * @param src Original rectangle + * @return Rectangle with padding added, not null + * @throws NullPointerException if rectangle is null + */ + /*@Nonnull*/ + private static Rectangle2D addPaddingTo(/*@Nonnull*/ final Rectangle2D src) { + + final int minX = (int) Math.floor(src.getMinX()) - 1; + final int minY = (int) Math.floor(src.getMinY()) - 1; + final int maxX = (int) Math.ceil(src.getMaxX()) + 1; + final int maxY = (int) Math.ceil(src.getMaxY()) + 1; + + return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY); + } + + /** + * Adds a glyph to the reusable list for output. + * + * @param glyph Glyph to add to output + * @throws NullPointerException if glyph is null + */ + protected final void addToOutput(/*@Nonnull*/ final Glyph glyph) { + Check.notNull(glyph, "Glyph cannot be null"); + output.add(glyph); + } + + /** + * Clears the reusable list for output. + */ + protected final void clearOutput() { + output.clear(); + } + + /** + * Makes a glyph vector for a character. + * + * @param c Character to create glyph vector from + * @return Glyph vector for the character, not null + */ + /*@Nonnull*/ + protected final GlyphVector createGlyphVector(final char c) { + characters[0] = c; + return font.createGlyphVector(fontRenderContext, characters); + } + + /** + * Makes a glyph vector for a string. + * + * @param font Style of text + * @param frc Details on how to render font + * @param str Text as a string + * @return Glyph vector for the string, not null + * @throws NullPointerException if string is null + */ + /*@Nonnull*/ + protected final GlyphVector createGlyphVector(/*@Nonnull*/ final String str) { + + Check.notNull(str, "String cannot be null"); + + GlyphVector gv = glyphVectors.get(str); + + // Check if already made + if (gv != null) { + return gv; + } + + // Otherwise make and store it + final char[] text = str.toCharArray(); + final int len = str.length(); + gv = font.layoutGlyphVector(fontRenderContext, text, 0, len, 0); + glyphVectors.put(str, gv); + return gv; + } + + /*@CheckForSigned*/ + @Override + public final float findAdvance(final char c) { + + // Check producer's inventory first + final Glyph glyph = createGlyph(c); + if (glyph != null) { + return glyph.advance; + } + + // Otherwise create the glyph vector + final GlyphVector gv = createGlyphVector(c); + final GlyphMetrics gm = gv.getGlyphMetrics(0); + return gm.getAdvance(); + } + + /*@Nonnull*/ + @Override + public final Rectangle2D findBounds(/*@Nonnull*/ final String str) { + + Check.notNull(str, "String cannot be null"); + + final List glyphs = createGlyphs(str); + + // Check if already computed bounds + if (glyphs.size() == 1) { + final Glyph glyph = glyphs.get(0); + return glyph.bounds; + } + + // Otherwise just recompute it + return addPaddingTo(renderDelegate.getBounds(str, font, fontRenderContext)); + } + + /** + * Returns the font used to create glyphs. + * + * @return Font used to create glyphs, not null + */ + /*@Nonnull*/ + protected final Font getFont() { + return font; + } + + /** + * Returns a read-only view of this producer's reusable list for output. + * + * @return Read-only view of reusable list, not null + */ + /*@Nonnull*/ + protected final List getOutput() { + return outputView; + } + + /** + * Checks if any characters in a string require full layout. + * + *

+ * The process of creating and laying out glyph vectors is relatively complex and can slow down + * text rendering significantly. This method is intended to increase performance by not + * creating glyph vectors for strings with characters that can be treated independently. + * + *

+ * Currently the decision is very simple. It just treats any characters above the IPA + * Extensions block as complex. This is convenient because most Latin characters are + * treated as simple but Spacing Modifier Letters and Combining Diacritical Marks + * are not. Ideally it would also be nice to have a few other blocks included, especially + * Greek and maybe symbols, but that is perhaps best left for later work. + * + *

+ * A truly correct implementation may require a lot of research or developers with more + * experience in the area. However, the following Unicode blocks are known to require full + * layout in some form: + * + *

+ * + *

+ * Asian scripts will also have letters that combine together, but it appears that the input + * method may take care of that so it may not be necessary to check for them here. + * + *

+ * Finally, it should be noted that even Latin has characters that can combine into glyphs + * called ligatures. The classic example is an 'f' and an 'i'. Java however will not make the + * replacements itself so we do not need to consider that here. + * + * @param str Text of unknown character types + * @return True if a complex character is found + * @throws NullPointerException if string is null + */ + protected static boolean hasComplexCharacters(/*@Nonnull*/ final String str) { + + Check.notNull(str, "String cannot be null"); + + final int len = str.length(); + for (int i = 0; i < len; ++i) { + if (str.charAt(i) > 0x2AE) { + return true; + } + } + return false; + } + + /** + * Checks if a glyph vector is complex. + * + * @param gv Glyph vector to check + * @return True if glyph vector is complex + * @throws NullPointerException if glyph vector is null + */ + protected static boolean isComplex(/*@CheckForNull*/ final GlyphVector gv) { + + Check.notNull(gv, "Glyph vector cannot be null"); + + return gv.getLayoutFlags() != 0; + } + + /** + * Measures a glyph. + * + *

+ * Sets all the measurements in a glyph after it's created. + * + * @param glyph Visual representation of a character + * @throws NullPointerException if glyph is null + */ + protected final void measure(/*@Nonnull*/ final Glyph glyph) { + + Check.notNull(glyph, "Glyph cannot be null"); + + // Compute visual boundary + final Rectangle2D visualBox; + if (glyph.str != null) { + visualBox = renderDelegate.getBounds(glyph.str, font, fontRenderContext); + } else { + visualBox = renderDelegate.getBounds(glyph.glyphVector, fontRenderContext); + } + + // Compute rectangles + final Rectangle2D paddingBox = addPaddingTo(visualBox); + final Rectangle2D marginBox = addMarginTo(paddingBox, font); + + // Set fields + glyph.padding = new Glyph.Boundary(paddingBox, visualBox); + glyph.margin = new Glyph.Boundary(marginBox, paddingBox); + glyph.width = (float) paddingBox.getWidth(); + glyph.height = (float) paddingBox.getHeight(); + glyph.ascent = (float) paddingBox.getMinY() * -1; + glyph.descent = (float) paddingBox.getMaxY(); + glyph.kerning = (float) paddingBox.getMinX(); + glyph.bounds = paddingBox; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractGlyphRenderer.java b/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractGlyphRenderer.java new file mode 100644 index 0000000000..bb80501723 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractGlyphRenderer.java @@ -0,0 +1,474 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLContext; +import com.jogamp.opengl.util.texture.TextureCoords; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Skeletal implementation of {@link GlyphRenderer}. + */ +abstract class AbstractGlyphRenderer implements GlyphRenderer, QuadPipeline.EventListener { + + // Default color + private static float DEFAULT_RED = 1.0f; + private static float DEFAULT_GREEN = 1.0f; + private static float DEFAULT_BLUE = 1.0f; + private static float DEFAULT_ALPHA = 1.0f; + + /** + * Listeners to send events to. + */ + /*@Nonnull*/ + private final List listeners = new ArrayList(); + + /** + * Quad to send to pipeline. + */ + /*@Nonnull*/ + private final Quad quad = new Quad(); + + /** + * Buffer of quads. + */ + /*@CheckForNull*/ + private QuadPipeline pipeline = null; + + /** + * Whether pipeline needs to be flushed. + */ + private boolean pipelineDirty = true; + + /** + * True if between begin and end calls. + */ + private boolean inRenderCycle = false; + + /** + * True if orthographic. + */ + private boolean orthoMode = false; + + /** + * Red component of color. + */ + private float r = DEFAULT_RED; + + /** + * Green component of color. + */ + private float g = DEFAULT_GREEN; + + /** + * Blue component of color. + */ + private float b = DEFAULT_BLUE; + + /** + * Alpha component of color. + */ + private float a = DEFAULT_ALPHA; + + /** + * True if color needs to be updated. + */ + private boolean colorDirty = true; + + /** + * Transformation matrix for 3D mode. + */ + /*@Nonnull*/ + private final float[] transform = new float[16]; + + /** + * Whether transformation matrix is in row-major order instead of column-major. + */ + private boolean transposed = false; + + // TODO: Should `transformDirty` start out as true? + /** + * Whether transformation matrix needs to be updated. + */ + private boolean transformDirty = false; + + /** + * Constructs an {@link AbstractGlyphRenderer}. + */ + AbstractGlyphRenderer() { + // empty + } + + @Override + public final void addListener(/*@Nonnull*/ final EventListener listener) { + + Check.notNull(listener, "Listener cannot be null"); + + listeners.add(listener); + } + + @Override + public final void beginRendering(/*@Nonnull*/ final GL gl, + final boolean ortho, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean disableDepthTest) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + // Perform hook + doBeginRendering(gl, ortho, width, height, disableDepthTest); + + // Store text renderer state + inRenderCycle = true; + orthoMode = ortho; + + // Make sure the pipeline is made + if (pipelineDirty) { + setPipeline(gl, doCreateQuadPipeline(gl)); + } + + // Pass to quad renderer + pipeline.beginRendering(gl); + + // Make sure color is correct + if (colorDirty) { + doSetColor(gl, r, g, b, a); + colorDirty = false; + } + + // Make sure transform is correct + if (transformDirty) { + doSetTransform3d(gl, transform, transposed); + transformDirty = false; + } + } + + /** + * Requests that the pipeline be replaced on the next call to {@link #beginRendering}. + */ + protected final void dirtyPipeline() { + pipelineDirty = true; + } + + @Override + public final void dispose(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + doDispose(gl); + listeners.clear(); + pipeline.dispose(gl); + } + + /** + * Actually starts a render cycle. + * + * @param gl Current OpenGL context + * @param ortho True if using orthographic projection + * @param width Width of current OpenGL viewport + * @param height Height of current OpenGL viewport + * @param disableDepthTest True if should ignore depth values + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if width or height is negative + * @throws GLException if context is unexpected version + */ + protected abstract void doBeginRendering(/*@Nonnull*/ final GL gl, + final boolean ortho, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean disableDepthTest); + + /** + * Actually creates the quad pipeline for rendering quads. + * + * @param gl Current OpenGL context + * @return Quad pipeline to render quads with + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + protected abstract QuadPipeline doCreateQuadPipeline(/*@Nonnull*/ final GL gl); + + /** + * Actually frees resources used by the renderer. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + protected abstract void doDispose(/*@Nonnull*/ final GL gl); + + /** + * Actually finishes a render cycle. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + protected abstract void doEndRendering(/*@Nonnull*/ final GL gl); + + /** + * Actually changes the color when user calls {@link #setColor}. + * + * @param gl Current OpenGL context + * @param r Red component of color + * @param g Green component of color + * @param b Blue component of color + * @param a Alpha component of color + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + protected abstract void doSetColor(/*@Nonnull*/ final GL gl, + float r, + float g, + float b, + float a); + + /** + * Actually changes the MVP matrix when using an arbitrary projection. + * + * @param gl Current OpenGL context + * @param value Matrix as float array + * @param transpose True if in row-major order + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + * @throws IndexOutOfBoundsException if length of value is less than sixteen + */ + protected abstract void doSetTransform3d(/*@Nonnull*/ GL gl, + /*@Nonnull*/ float[] value, + boolean transpose); + + /** + * Actually changes the MVP matrix when using orthographic projection. + * + * @param gl Current OpenGL context + * @param width Width of viewport + * @param height Height of viewport + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + * @throws IllegalArgumentException if width or height is negative + */ + protected abstract void doSetTransformOrtho(/*@Nonnull*/ GL gl, + /*@Nonnegative*/ int width, + /*@Nonnegative*/ int height); + + @Override + public final float drawGlyph(/*@Nonnull*/ final GL gl, + /*@Nonnull*/ final Glyph glyph, + /*@CheckForSigned*/ final float x, + /*@CheckForSigned*/ final float y, + /*@CheckForSigned*/ final float z, + /*@CheckForSigned*/ final float scale, + /*@Nonnull*/ final TextureCoords coords) { + + Check.notNull(gl, "GL cannot be null"); + Check.notNull(glyph, "Glyph cannot be null"); + Check.notNull(coords, "Texture coordinates cannot be null"); + + // Compute position and size + quad.xl = x + (scale * glyph.kerning); + quad.xr = quad.xl + (scale * glyph.width); + quad.yb = y - (scale * glyph.descent); + quad.yt = quad.yb + (scale * glyph.height); + quad.z = z; + quad.sl = coords.left(); + quad.sr = coords.right(); + quad.tb = coords.bottom(); + quad.tt = coords.top(); + + // Draw quad + pipeline.addQuad(gl, quad); + + // Return distance to next character + return glyph.advance; + } + + @Override + public final void endRendering(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + // Store text renderer state + inRenderCycle = false; + + // Pass to quad renderer + pipeline.endRendering(gl); + + // Perform hook + doEndRendering(gl); + } + + /** + * Fires an event to all observers. + * + * @param type Kind of event + * @throws NullPointerException if type is null + */ + protected final void fireEvent(/*@Nonnull*/ final EventType type) { + + Check.notNull(type, "Event type cannot be null"); + + for (final EventListener listener : listeners) { + assert listener != null : "addListener rejects null"; + listener.onGlyphRendererEvent(type); + } + } + + @Override + public final void flush(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + Check.state(inRenderCycle, "Must be in render cycle"); + + pipeline.flush(gl); + gl.glFlush(); + } + + /** + * Determines if a color is the same one that is stored. + * + * @param r Red component of color + * @param g Green component of color + * @param b Blue component of color + * @param a Alpha component of color + * @return True if each component matches + */ + final boolean hasColor(final float r, final float g, final float b, final float a) { + return (this.r == r) && (this.g == g) && (this.b == b) && (this.a == a); + } + + // TODO: Rename to `isOrthographic`? + /** + * Checks if this {@link GlyphRenderer} using an orthographic projection. + * + * @return True if this renderer is using an orthographic projection + */ + final boolean isOrthoMode() { + return orthoMode; + } + + @Override + public final void onQuadPipelineEvent(/*@Nonnull*/ final QuadPipeline.EventType type) { + + Check.notNull(type, "Event type cannot be null"); + + if (type == QuadPipeline.EventType.AUTOMATIC_FLUSH) { + fireEvent(EventType.AUTOMATIC_FLUSH); + } + } + + @Override + public final void setColor(final float r, final float g, final float b, final float a) { + + // Check if already has the color + if (hasColor(r, g, b, a)) { + return; + } + + // Render any outstanding quads first + if (!pipeline.isEmpty()) { + fireEvent(EventType.AUTOMATIC_FLUSH); + final GL gl = GLContext.getCurrentGL(); + flush(gl); + } + + // Store the color + this.r = r; + this.g = g; + this.b = g; + this.a = a; + + // Change the color + if (inRenderCycle) { + final GL gl = GLContext.getCurrentGL(); + doSetColor(gl, r, g, b, a); + } else { + colorDirty = true; + } + } + + /** + * Changes the quad pipeline. + * + * @param gl Current OpenGL context + * @param pipeline Quad pipeline to change to + */ + private final void setPipeline(/*@Nonnull*/ final GL gl, + /*@Nonnull*/ final QuadPipeline pipeline) { + + assert gl != null : "GL should not be null"; + assert pipeline != null : "Pipeline should not be null"; + + final QuadPipeline oldPipeline = this.pipeline; + final QuadPipeline newPipeline = pipeline; + + // Remove the old pipeline + if (oldPipeline != null) { + oldPipeline.removeListener(this); + oldPipeline.dispose(gl); + this.pipeline = null; + } + + // Store the new pipeline + newPipeline.addListener(this); + this.pipeline = newPipeline; + pipelineDirty = false; + } + + @Override + public final void setTransform(/*@Nonnull*/ final float[] value, final boolean transpose) { + + Check.notNull(value, "Transform value cannot be null"); + Check.state(!orthoMode, "Must be in 3D mode"); + + // Render any outstanding quads first + if (!pipeline.isEmpty()) { + fireEvent(EventType.AUTOMATIC_FLUSH); + final GL gl = GLContext.getCurrentGL(); + flush(gl); + } + + // Store the transform + System.arraycopy(value, 0, this.transform, 0, value.length); + this.transposed = transpose; + + // Change the transform + if (inRenderCycle) { + final GL gl = GLContext.getCurrentGL(); + doSetTransform3d(gl, value, transpose); + } else { + transformDirty = true; + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractQuadPipeline.java b/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractQuadPipeline.java new file mode 100644 index 0000000000..2dab54397f --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/AbstractQuadPipeline.java @@ -0,0 +1,440 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2GL3; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + + +/** + * Skeletal implementation of {@link QuadPipeline}. + */ +abstract class AbstractQuadPipeline implements QuadPipeline { + + /** + * Number of bytes in one float. + */ + /*@Nonnegative*/ + static final int SIZEOF_FLOAT = 4; + + /** + * Number of bytes in one int. + */ + /*@Nonnegative*/ + static final int SIZEOF_INT = 4; + + /** + * Maximum number of quads in the buffer. + */ + /*@Nonnegative*/ + static final int QUADS_PER_BUFFER = 100; + + /** + * Number of components in a point attribute. + */ + /*@Nonnegative*/ + static final int FLOATS_PER_POINT = 3; + + /** + * Number of components in a texture coordinate attribute + */ + /*@Nonnegative*/ + static final int FLOATS_PER_COORD = 2; + + /** + * Total components in vertex. + */ + /*@Nonnegative*/ + static final int FLOATS_PER_VERT = FLOATS_PER_POINT + FLOATS_PER_COORD; + + /** + * Size of a point attribute in bytes. + */ + /*@Nonnegative*/ + static final int BYTES_PER_POINT = FLOATS_PER_POINT * SIZEOF_FLOAT; + + /** + * Size of a texture coordinate attribute in bytes. + */ + /*@Nonnegative*/ + static final int BYTES_PER_COORD = FLOATS_PER_COORD * SIZEOF_FLOAT; + + /** + * Total size of a vertex in bytes. + */ + /*@Nonnegative*/ + static final int BYTES_PER_VERT = BYTES_PER_POINT + BYTES_PER_COORD; + + /** + * Number of bytes before first point attribute in buffer. + */ + /*@Nonnegative*/ + static final int POINT_OFFSET = 0; + + /** + * Number of bytes before first texture coordinate in buffer. + */ + /*@Nonnegative*/ + static final int COORD_OFFSET = BYTES_PER_POINT; + + /** + * Number of bytes between successive values for the same attribute. + */ + /*@Nonnegative*/ + static final int STRIDE = BYTES_PER_POINT + BYTES_PER_COORD; + + /** + * Maximum buffer size in floats. + */ + /*@Nonnegative*/ + final int FLOATS_PER_BUFFER; + + /** + * Maximum buffer size in bytes. + */ + /*@Nonnegative*/ + final int BYTES_PER_BUFFER; + + /** + * Number of vertices per primitive. + */ + /*@Nonnegative*/ + final int VERTS_PER_PRIM; + + /** + * Maximum buffer size in primitives. + */ + /*@Nonnegative*/ + final int PRIMS_PER_BUFFER; + + /** + * Maximum buffer size in vertices. + */ + /*@Nonnegative*/ + final int VERTS_PER_BUFFER; + + /** + * Size of a quad in vertices. + */ + /*@Nonnegative*/ + final int VERTS_PER_QUAD; + + /** + * Size of a quad in bytes. + */ + /*@Nonnegative*/ + final int BYTES_PER_QUAD; + + /** + * Size of a quad in primitives. + */ + /*@Nonnegative*/ + final int PRIMS_PER_QUAD; + + /** + * Observers of events. + */ + /*@Nonnull*/ + private final List listeners = new ArrayList(); + + /** + * Buffer of vertices. + */ + /*@Nonnull*/ + private final FloatBuffer data; + + /** + * Number of outstanding quads in the buffer. + */ + /*@Nonnegative*/ + private int size = 0; + + /** + * Constructs an abstract quad pipeline. + * + * @param vertsPerPrim Number of vertices per primitive + * @param primsPerQuad Number of primitives per quad + * @throws IllegalArgumentException if vertices or primitives is less than one + */ + AbstractQuadPipeline(/*@Nonnegative*/ final int vertsPerPrim, + /*@Nonnegative*/ final int primsPerQuad) { + + Check.argument(vertsPerPrim > 0, "Number of vertices is less than one"); + Check.argument(primsPerQuad > 0, "Number of primitives is less than one"); + + VERTS_PER_PRIM = vertsPerPrim; + PRIMS_PER_QUAD = primsPerQuad; + PRIMS_PER_BUFFER = primsPerQuad * QUADS_PER_BUFFER; + VERTS_PER_QUAD = vertsPerPrim * primsPerQuad; + VERTS_PER_BUFFER = PRIMS_PER_BUFFER * VERTS_PER_PRIM; + FLOATS_PER_BUFFER = FLOATS_PER_VERT * VERTS_PER_BUFFER; + BYTES_PER_BUFFER = BYTES_PER_VERT * VERTS_PER_BUFFER; + BYTES_PER_QUAD = BYTES_PER_VERT * VERTS_PER_QUAD; + + this.data = Buffers.newDirectFloatBuffer(FLOATS_PER_BUFFER); + } + + /** + * Adds a texture coordinate to the pipeline. + * + * @param s Texture coordinate for X axis + * @param t Texture coordinate for Y axis + */ + protected final void addCoord(final float s, final float t) { + data.put(s).put(t); + } + + /** + * Adds a point to the pipeline. + * + * @param x Position on X axis + * @param y Position on Y axis + * @param z Position on Z axis + */ + protected final void addPoint(final float x, final float y, final float z) { + data.put(x).put(y).put(z); + } + + @Override + public final void addListener(/*@Nonnull*/ final EventListener listener) { + + Check.notNull(listener, "Listener cannot be null"); + + listeners.add(listener); + } + + @Override + public final void addQuad(/*@Nonnull*/ final GL gl, /*@Nonnull*/ final Quad quad) { + + Check.notNull(gl, "Context cannot be null"); + Check.notNull(quad, "Quad cannot be null"); + + doAddQuad(quad); + if (++size >= QUADS_PER_BUFFER) { + fireEvent(EventType.AUTOMATIC_FLUSH); + flush(gl); + } + } + + @Override + public void beginRendering(/*@Nonnull*/ final GL gl) { + Check.notNull(gl, "GL cannot be null"); + } + + /** + * Rewinds the buffer and resets the number of outstanding quads. + */ + protected final void clear() { + data.rewind(); + size = 0; + } + + /** + * Creates a vertex buffer object for use with a pipeline. + * + * @param gl Current OpenGL context + * @param size Size in bytes of buffer + * @return OpenGL handle to vertex buffer object + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if size is negative + */ + /*@Nonnegative*/ + protected static int createVertexBufferObject(/*@Nonnull*/ final GL2GL3 gl, + /*@Nonnegative*/ final int size) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(size >= 0, "Size cannot be negative"); + + // Generate + final int[] handles = new int[1]; + gl.glGenBuffers(1, handles, 0); + final int vbo = handles[0]; + + // Allocate + gl.glBindBuffer(GL2GL3.GL_ARRAY_BUFFER, vbo); + gl.glBufferData( + GL2GL3.GL_ARRAY_BUFFER, // target + size, // size + null, // data + GL2GL3.GL_STREAM_DRAW); // usage + gl.glBindBuffer(GL2GL3.GL_ARRAY_BUFFER, 0); + + return vbo; + } + + @Override + public void dispose(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + listeners.clear(); + } + + /** + * Actually adds vertices from a quad to the buffer. + * + * @param quad Quad to add + * @throws NullPointerException if quad is null + */ + protected void doAddQuad(/*@Nonnull*/ final Quad quad) { + + Check.notNull(quad, "Quad cannot be null"); + + addPoint(quad.xr, quad.yt, quad.z); + addCoord(quad.sr, quad.tt); + addPoint(quad.xl, quad.yt, quad.z); + addCoord(quad.sl, quad.tt); + addPoint(quad.xl, quad.yb, quad.z); + addCoord(quad.sl, quad.tb); + addPoint(quad.xr, quad.yb, quad.z); + addCoord(quad.sr, quad.tb); + } + + /** + * Actually draws everything in the pipeline. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + protected abstract void doFlush(/*@Nonnull*/ final GL gl); + + @Override + public void endRendering(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + flush(gl); + } + + /** + * Fires an event to all observers. + * + * @param type Type of event to send to observers + * @throws NullPointerException if type is null + */ + protected final void fireEvent(/*@Nonnull*/ final EventType type) { + + Check.notNull(type, "Type cannot be null"); + + for (final EventListener listener : listeners) { + assert listener != null : "addListener rejects null"; + listener.onQuadPipelineEvent(type); + } + } + + @Override + public final void flush(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + if (size > 0) { + doFlush(gl); + } + } + + /** + * Returns NIO buffer backing the pipeline. + */ + /*@Nonnull*/ + protected final FloatBuffer getData() { + return data; + } + + /** + * Returns next float in the pipeline. + */ + /*@CheckForSigned*/ + protected final float getFloat() { + return data.get(); + } + + /*@Nonnegative*/ + @Override + public final int getSize() { + return size; + } + + @Override + public final boolean isEmpty() { + return size == 0; + } + + /** + * Returns size of vertices in the pipeline in bytes. + */ + /*@Nonnegative*/ + public final int getSizeInBytes() { + return size * BYTES_PER_QUAD; + } + + /** + * Returns number of primitives in the pipeline. + */ + /*@Nonnegative*/ + public final int getSizeInPrimitives() { + return size * PRIMS_PER_QUAD; + } + + /** + * Returns number of vertices in the pipeline. + */ + /*@Nonnegative*/ + public final int getSizeInVertices() { + return size * VERTS_PER_QUAD; + } + + /** + * Changes the buffer's position. + * + * @param position Location in buffer to move to + * @throws IllegalArgumentException if position is out-of-range + */ + protected final void position(/*@Nonnegative*/ final int position) { + data.position(position); + } + + @Override + public final void removeListener(/*@CheckForNull*/ final EventListener listener) { + if (listener != null) { + listeners.remove(listener); + } + } + + /** + * Rewinds the data buffer. + */ + protected final void rewind() { + data.rewind(); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/AsciiGlyphProducer.java b/src/jogl/classes/jogamp/opengl/util/awt/text/AsciiGlyphProducer.java new file mode 100644 index 0000000000..50a975c5e9 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/AsciiGlyphProducer.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.util.awt.TextRenderer.RenderDelegate; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.util.List; + + +/** + * {@link GlyphProducer} that creates glyphs in the ASCII range. + */ +/*@NotThreadSafe*/ +final class AsciiGlyphProducer extends AbstractGlyphProducer { + + /** + * Storage for glyphs. + */ + /*@Nonnull*/ + private final Glyph[] inventory = new Glyph[128]; + + /** + * Constructs an {@link AsciiGlyphProducer}. + * + * @param font Font glyphs will be made from + * @param rd Delegate for controlling rendering + * @param frc Details on how to render fonts + * @throws NullPointerException if font, render delegate, or font render context is null + */ + AsciiGlyphProducer(/*@Nonnull*/ final Font font, + /*@Nonnull*/ final RenderDelegate rd, + /*@Nonnull*/ final FontRenderContext frc) { + super(font, rd, frc); + } + + @Override + public void clearGlyphs() { + // empty + } + + /*@Nonnull*/ + @Override + public Glyph createGlyph(char c) { + + // Check if out of range + if (c > 128) { + c = '_'; + } + + // Check if already created + Glyph glyph = inventory[c]; + if (glyph != null) { + return glyph; + } + + // Create glyph + GlyphVector gv = createGlyphVector(c); + glyph = new Glyph(c, gv); + measure(glyph); + + // Store and finish + inventory[c] = glyph; + return glyph; + } + + /*@Nonnull*/ + @Override + public List createGlyphs(/*@Nonnull*/ final String str) { + + Check.notNull(str, "String cannot be null"); + + // Clear the output + clearOutput(); + + // Add each glyph to the output + final int len = str.length(); + for (int i = 0; i < len; ++i) { + final char character = str.charAt(i); + final Glyph glyph = createGlyph(character); + addToOutput(glyph); + } + + // Return the output + return getOutput(); + } + + @Override + public void removeGlyph(/*@CheckForNull*/ final Glyph glyph) { + // empty + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Check.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Check.java new file mode 100644 index 0000000000..cb9b89c8d6 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Check.java @@ -0,0 +1,59 @@ +package jogamp.opengl.util.awt.text; + + +/** + * Utility for checking arguments and preconditions. + */ +/*@ThreadSafe*/ +public final class Check { + + /** + * Prevents instantiation. + */ + private Check() { + // empty + } + + /** + * Ensures an argument is valid. + * + * @param condition Condition involving argument that should be true + * @param message Message of exception thrown if condition is false + * @throws IllegalArgumentException if condition is false + */ + public static void argument(final boolean condition, /*@CheckForNull*/ final String message) { + if (!condition) { + throw new IllegalArgumentException(message); + } + } + + /** + * Ensures an object is not null. + * + * @param obj Object to check + * @param message Message of exception thrown if object is null + * @return Reference to the given object, not null + * @throws NullPointerException if object is null + */ + /*@Nonnull*/ + public static T notNull(/*@Nullable*/ final T obj, + /*@CheckForNull*/ final String message) { + if (obj == null) { + throw new NullPointerException(message); + } + return obj; + } + + /** + * Ensures the state of an object is valid when a method's called. + * + * @param condition Condition involving state that should be true + * @param message Message of exception thrown if condition is false + * @throws IllegalStateException if condition is false + */ + public static void state(final boolean condition, /*@CheckForNull*/ final String message) { + if (!condition) { + throw new IllegalStateException(message); + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Glyph.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Glyph.java new file mode 100644 index 0000000000..d49f1264f7 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Glyph.java @@ -0,0 +1,317 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.util.packrect.Rect; +import com.jogamp.opengl.util.texture.TextureCoords; + +import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; + + +/** + * Representation of one or multiple unicode characters to be drawn. + * + *

+ * The reason for the dual behavior is so that we can take in a sequence of unicode characters and + * partition them into runs of individual glyphs, but if we encounter complex text and/or unicode + * sequences we don't understand, we can render them using the string-by-string method. + * + *

Positioning

+ * + *

+ * In an effort to make positioning glyphs more intuitive for both Java2D's and OpenGL's coordinate + * systems, {@code Glyph} now stores its measurements differently. This new way is patterned off + * of HTML's box model. + * + *

+ * Of course, as expected each glyph maintains its width and height. For spacing however, rather + * than storing positions in Java2D space that must be manipulated on a case-by-case basis, + * {@code Glyph} stores two separate pre-computed boundaries representing space around the text. + * Each of the boundaries has separate top, bottom, left, and right components. These components + * should generally be considered positive, but negative values are sometimes necessary in rare + * situations. + * + *

+ * The first boundary is called padding. Padding is the space between the actual glyph + * itself and its border. It is included in the width and height of the glyph. The second + * boundary that a glyph stores is called margin, which is extra space around the glyph's + * border. The margin is generally used for separating the glyph from other glyphs when it's + * stored. + * + *

+ * The diagram below shows the boundaries of a glyph and how they relate to its width and height. + * The inner rectangle is the glyph's boundary, and the outer rectangle is the edge of the margin. + * + *

+ * +--------------------------------------+
+ * |             top margin               |
+ * |                                      |
+ * |        |------ WIDTH -------|        |
+ * |     -  +--------------------+        |
+ * |     |  |    top padding     |        |
+ * |     |  | l    ________    r |        |
+ * | l   |  | e   /        \   i |      r |
+ * | e      | f  |          |  g |      i |
+ * | f   H  | t  |          |  h |      g |
+ * | t   E  |    |          |  t |      h |
+ * |     I  | p  |     _____     |      t |
+ * | m   G  | a  |          |  p |        |
+ * | a   H  | d  |          |  a |      m |
+ * | r   T  | d  |          |  d |      a |
+ * | g      | i  |          |  d |      r |
+ * | i   |  | n  |          |  i |      g |
+ * | n   |  | g   \________/   n |      i |
+ * |     |  |                  g |      n |
+ * |     |  |   bottom padding   |        |
+ * |     -  +--------------------+        |
+ * |                                      |
+ * |                                      |
+ * |            bottom margin             |
+ * +--------------------------------------+
+ * 
+ * + *

+ * In addition, {@code Glyph} also keeps a few other measurements useful for positioning. + * Ascent is the distance between the baseline and the top border, while descent is + * the distance between the baseline and the bottom border. Kerning is the distance between + * the vertical baseline and the left border. Note that in some cases some of these fields can + * match up with padding components, but in general they should be considered separate. + * + *

+ * Below is a diagram showing ascent, descent, and kerning. + * + *

+ * +--------------------+   -
+ * |                    |   |
+ * |      ________      |   |
+ * |     /        \     |   |
+ * |    |          |    |   |
+ * |    |          |    |   |
+ * |    |          |    |   |
+ * |    |     _____     |   | ascent
+ * |    |          |    |   |
+ * |    |          |    |   |
+ * |    |          |    |   |
+ * |    |          |    |   |
+ * |    |          |    |   |
+ * |     \________/     |   -
+ * |                    |   |
+ * |                    |   | descent
+ * +--------------------+   -
+ *
+ * |--| kerning
+ * 
+ */ +/*@NotThreadSafe*/ +public final class Glyph { + + // TODO: Create separate Glyph implementations -- one for character one for string? + + /** + * Unicode ID if this glyph represents a single character, otherwise -1. + */ + /*@CheckForSigned*/ + final int id; + + /** + * String if this glyph represents multiple characters, otherwise null. + */ + /*@CheckForNull*/ + final String str; + + /** + * Font's identifier of glyph. + */ + final int code; + + /** + * Distance to next glyph. + */ + final float advance; + + /** + * Java2D shape of glyph. + */ + /*@Nonnull*/ + final GlyphVector glyphVector; + + /** + * Actual character if this glyph represents a single character, otherwise NUL. + */ + final char character; + + /** + * Width of text with inner padding. + */ + /*@VisibleForTesting*/ + public float width; + + /** + * Height of text with inner padding. + */ + /*@VisibleForTesting*/ + public float height; + + /** + * Length from baseline to top border. + */ + float ascent; + + /** + * Length from baseline to bottom border. + */ + float descent; + + /** + * Length from baseline to left padding. + */ + float kerning; + + /** + * Outer boundary excluded from size. + */ + /*@CheckForNull*/ + Boundary margin; + + /** + * Inner boundary included in size. + */ + /*@CheckForNull*/ + Boundary padding; + + /** + * Position of this glyph in texture. + */ + /*@CheckForNull*/ + public Rect location; + + /** + * Coordinates of this glyph in texture. + */ + /*@CheckForNull*/ + TextureCoords coordinates; + + /** + * Cached bounding box of glyph. + */ + /*@CheckForNull*/ + Rectangle2D bounds; + + /** + * Constructs a {@link Glyph} representing an individual Unicode character. + * + * @param id Unicode ID of character + * @param gv Vector shape of character + * @throws IllegalArgumentException if ID is negative + * @throws NullPointerException if glyph is null + */ + public Glyph(/*@Nonnegative*/ final int id, /*@Nonnull*/ final GlyphVector gv) { + + Check.argument(id >= 0, "ID cannot be negative"); + Check.notNull(gv, "Glyph vector cannot be null"); + + this.id = id; + this.str = null; + this.code = gv.getGlyphCode(0); + this.advance = gv.getGlyphMetrics(0).getAdvance(); + this.glyphVector = gv; + this.character = (char) id; + } + + /** + * Constructs a {@link Glyph} representing a sequence of characters. + * + * @param str Sequence of characters + * @param gv Vector shape of sequence + * @throws NullPointerException if string or glyph vector is null + */ + public Glyph(/*@Nonnull*/ final String str, /*@Nonnull*/ final GlyphVector gv) { + + Check.notNull(str, "String cannot be null"); + Check.notNull(gv, "Glyph vector cannot be null"); + + this.id = -1; + this.str = str; + this.code = -1; + this.advance = 0; + this.glyphVector = gv; + this.character = '\0'; + } + + /*@Nonnull*/ + @Override + public String toString() { + return (str != null) ? str : Character.toString(character); + } + + /** + * Space around a rectangle. + */ + /*@Immutable*/ + static final class Boundary { + + /** + * Space above rectangle. + */ + final int top; + + /** + * Space below rectangle. + */ + final int bottom; + + /** + * Space beside rectangle to left. + */ + final int left; + + /** + * Space beside rectangle to right. + */ + final int right; + + /** + * Constructs a {@link Boundary} by computing the distances between two rectangles. + * + * @param large Outer rectangle + * @param small Inner rectangle + * @throws NullPointerException if either rectangle is null + */ + Boundary(/*@Nonnull*/ final Rectangle2D large, /*@Nonnull*/ final Rectangle2D small) { + + Check.notNull(large, "Large rectangle cannot be null"); + Check.notNull(small, "Small rectangle cannot be null"); + + top = (int) (large.getMinY() - small.getMinY()) * -1; + left = (int) (large.getMinX() - small.getMinX()) * -1; + bottom = (int) (large.getMaxY() - small.getMaxY()); + right = (int) (large.getMaxX() - small.getMaxX()); + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphCache.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphCache.java new file mode 100644 index 0000000000..853db75f20 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphCache.java @@ -0,0 +1,855 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLException; +import com.jogamp.opengl.util.awt.TextRenderer.RenderDelegate; +import com.jogamp.opengl.util.packrect.BackingStoreManager; +import com.jogamp.opengl.util.packrect.Rect; +import com.jogamp.opengl.util.packrect.RectVisitor; +import com.jogamp.opengl.util.packrect.RectanglePacker; +import com.jogamp.opengl.util.texture.TextureCoords; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.util.ArrayList; +import java.util.List; + + +/** + * Storage of glyphs in an OpenGL texture. + * + *

+ * {@code GlyphCache} andles storing glyphs in a 2D texture and retrieving their coordinates. + * + *

+ * The first step in using a {@code GlyphCache} is to make sure it's set up by calling {@link + * #beginRendering(GL)}. Then glyphs can be added using {@link #upload(Glyph)}. Each glyph will + * be packed efficiently into the texture with a small amount of space around it using {@link + * RectanglePacker}. When all glyphs have been added, be sure to call {@link #update(GL)} or + * {@link #endRendering(GL)} before trying to render with the texture, as the glyphs are not + * actually drawn into the texture right away in order to increase performance. Texture + * coordinates of individual glyphs can be determined with {@link #find(Glyph)}. When reusing the + * glyph cache, {@link #contains(Glyph)} should be called to make sure a glyph is not already + * stored. + * + *

+ * Events fired when: + *

+ * + *

+ * GlyphCache is compatible with GL2 or GL3. + * + * @see TextureBackingStore + */ +/*@NotThreadSafe*/ +public final class GlyphCache implements TextureBackingStore.EventListener { + + /** + * Whether or not glyph cache should print debugging information. + */ + private static final boolean DEBUG = false; + + /** + * Number used to determine size of cache based on font size. + */ + /*@Nonnegative*/ + private static final int FONT_SIZE_MULTIPLIER = 5; + + /** + * How much fragmentation to allow before compacting. + */ + /*@Nonnegative*/ + private static final float MAX_VERTICAL_FRAGMENTATION = 0.7f; + + /** + * Number of render cycles before clearing unused entries. + */ + /*@Nonnegative*/ + private static final int CYCLES_PER_FLUSH = 100; + + /** + * Minimum size of backing store in pixels. + */ + /*@Nonnegative*/ + private static final int MIN_BACKING_STORE_SIZE = 256; + + /** + * Delegate to render text. + */ + /*@Nonnull*/ + private final RenderDelegate renderDelegate; + + /** + * Observers of glyph cache. + */ + /*@Nonnull*/ + private final List listeners = new ArrayList(); + + /** + * Delegate to create textures. + */ + /*@Nonnull*/ + private final TextureBackingStoreManager manager; + + /** + * Delegate to position glyphs. + */ + /*@Nonnull*/ + private final RectanglePacker packer; + + /** + * Texture to draw into. + * + *

+ * This will be null until {@link #beginRendering} is called. + */ + /*@CheckForNull*/ + private TextureBackingStore backingStore; + + /** + * Times cache has been used. + */ + /*@Nonnegative*/ + private int numRenderCycles = 0; + + /** + * True if done initializing. + */ + private boolean ready = false; + + /** + * Constructs a {@link GlyphCache}. + * + * @param font Font that was used to create glyphs that will be stored, assumed not null + * @param rd Controller of rendering bitmapped text, assumed not null + * @param antialias True to render glyphs with smooth edges + * @param subpixel True to consider subpixel positioning + * @param mipmap True to create multiple sizes of texture + * @see #newInstance + */ + private GlyphCache(/*@Nonnull*/ final Font font, + /*@Nonnull*/ final RenderDelegate rd, + final boolean antialias, + final boolean subpixel, + final boolean mipmap) { + this.renderDelegate = rd; + this.manager = new TextureBackingStoreManager(font, antialias, subpixel, mipmap); + this.packer = createPacker(font, manager); + } + + /** + * Registers an {@link EventListener} with this {@link GlyphCache}. + * + * @param listener Listener to register + * @throws NullPointerException if listener is null + */ + public void addListener(/*@Nonnull*/ final EventListener listener) { + + Check.notNull(listener, "Listener cannot be null"); + + listeners.add(listener); + } + + /** + * Sets up the cache for rendering. + * + *

+ * After calling this method the texture storing the glyphs will be bound. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + public void beginRendering(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "Context cannot be null"); + + // Set up if first time rendering + if (!ready) { + setMaxSize(gl); + ready = true; + } + + // Bind the backing store + final TextureBackingStore bs = getBackingStore(); + bs.bind(gl, GL.GL_TEXTURE0); + } + + /** + * Clears all the texture coordinates stored in glyphs. + */ + private void clearTextureCoordinates() { + + log("Clearing texture coordinates"); + + packer.visit(new RectVisitor() { + + @Override + public void visit(/*@Nonnull*/ final Rect rect) { + final Glyph glyph = ((TextData) rect.getUserData()).glyph; + glyph.coordinates = null; + } + }); + } + + /** + * Clears entries that haven't been used in awhile. + */ + private void clearUnusedEntries() { + + log("Trying to clear unused entries..."); + + // Find rectangles in backing store that haven't been used recently + final List deadRects = new ArrayList(); + packer.visit(new RectVisitor() { + + @Override + public void visit(/*@Nonnull*/ final Rect rect) { + final TextData data = (TextData) rect.getUserData(); + if (data.used()) { + data.clearUsed(); + } else { + deadRects.add(rect); + } + } + }); + + // Remove each of those rectangles + final TextureBackingStore bs = getBackingStore(); + for (final Rect rect : deadRects) { + packer.remove(rect); + final Glyph glyph = ((TextData) rect.getUserData()).glyph; + glyph.location = null; + fireEvent(EventType.CLEAN, glyph); + log("Cleared rectangle for glyph: %s", glyph); + if (DEBUG) { + bs.clear(rect.x(), rect.y(), rect.w(), rect.h()); + } + } + + // If we removed dead rectangles this cycle, try to do a compaction + final float frag = packer.verticalFragmentationRatio(); + if (!deadRects.isEmpty() && (frag > MAX_VERTICAL_FRAGMENTATION)) { + log("Compacting due to fragmentation %s", frag); + packer.compact(); + } + + // Force the backing store to update + if (DEBUG) { + bs.mark(0, 0, bs.getWidth(), bs.getHeight()); + } + } + + /** + * Computes the normalized coordinates of a glyph's location. + * + * @param glyph Glyph being uploaded, assumed not null + */ + private void computeCoordinates(/*@Nonnull*/ final Glyph glyph) { + + // Determine dimensions in pixels + final int cacheWidth = getWidth(); + final int cacheHeight = getHeight(); + final float left = getLeftBorderLocation(glyph); + final float bottom = getBottomBorderLocation(glyph); + + // Convert to normalized texture coordinates + final float l = left / cacheWidth; + final float b = bottom / cacheHeight; + final float r = (left + glyph.width) / cacheWidth; + final float t = (bottom - glyph.height) / cacheHeight; + + // Store in glyph + glyph.coordinates = new TextureCoords(l, b, r, t); + } + + /** + * Checks if a glyph is stored in this {@link GlyphCache}. + * + * @param glyph Glyph to check for, which may be null + * @return True if glyph is in the cache + */ + boolean contains(/*@CheckForNull*/ final Glyph glyph) { + + if (glyph == null) { + return false; + } + + return glyph.location != null; + } + + /** + * Makes a packer for positioning glyphs. + * + * @param font Font used to make glyphs being stored, assumed not null + * @param manager Handler of packer events, assumed not null + * @return Resulting packer, not null + */ + /*@Nonnull*/ + private static RectanglePacker createPacker(/*@Nonnull*/ final Font font, + /*@Nonnull*/ final BackingStoreManager manager) { + final int size = findBackingStoreSizeForFont(font); + return new RectanglePacker(manager, size, size, 1f); + } + + /** + * Destroys resources used by this {@link GlyphCache}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + public void dispose(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "Context cannot be null"); + + packer.dispose(); + if (backingStore != null) { + backingStore.dispose(gl); + backingStore = null; + } + } + + /** + * Draws a glyph into the backing store. + * + * @param glyph Glyph being uploaded, assumed not null + */ + private void drawInBackingStore(/*@Nonnull*/ final Glyph glyph) { + + // Get the backing store + final TextureBackingStore bs = getBackingStore(); + + // Clear the area + final Rect loc = glyph.location; + final int x = loc.x(); + final int y = loc.y(); + final int w = loc.w(); + final int h = loc.h(); + bs.clear(x, y, w, h); + + // Draw the text + renderDelegate.drawGlyphVector( + bs.getGraphics(), + glyph.glyphVector, + getLeftBaselineLocation(glyph), + getBottomBaselineLocation(glyph)); + + // Mark it dirty + bs.mark(x, y, w, h); + } + + /** + * Finishes setting up the cache for rendering. + * + *

+ * After calling this method, all uploaded glyphs will be guaranteed to be present in the + * underlying OpenGL texture. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + public void endRendering(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "Context cannot be null"); + + update(gl); + + // Check if reached render cycle limit + if (++numRenderCycles >= CYCLES_PER_FLUSH) { + numRenderCycles = 0; + log("Reached cycle limit."); + clearUnusedEntries(); + } + } + + /** + * Determines the texture coordinates of a glyph in the cache. + * + *

+ * Notes: + *

+ * + * @param glyph Glyph already in cache + * @return Texture coordinates of glyph in the cache, not null + * @throws NullPointerException if glyph is null + */ + /*@Nonnull*/ + public TextureCoords find(/*@Nonnull*/ final Glyph glyph) { + + Check.notNull(glyph, "Glyph cannot be null"); + + // Mark the glyph as being used + markGlyphLocationUsed(glyph); + + // Find the coordinates, recalculating if necessary + if (glyph.coordinates == null) { + computeCoordinates(glyph); + } + return glyph.coordinates; + } + + /** + * Returns the initial size of a {@link GlyphCache} for a font. + * + * @param font Font to create glyphs from, assumed not null + */ + /*@Nonnegative*/ + private static int findBackingStoreSizeForFont(/*@Nonnull*/ final Font font) { + return Math.max(MIN_BACKING_STORE_SIZE, font.getSize() * FONT_SIZE_MULTIPLIER); + } + + /** + * Finds a location in the backing store for a glyph. + * + * @param glyph Glyph being uploaded, assumed not null + */ + private void findLocation(/*@Nonnull*/ final Glyph glyph) { + + // Compute a rectangle that includes glyph's margin + final int x = 0; + final int y = 0; + final int w = glyph.margin.left + ((int) glyph.width) + glyph.margin.right; + final int h = glyph.margin.top + ((int) glyph.height) + glyph.margin.bottom; + final Rect rect = new Rect(x, y, w, h, new TextData(glyph)); + + // Pack it into the cache and store its location + packer.add(rect); + glyph.location = rect; + markGlyphLocationUsed(glyph); + } + + /** + * Determines the maximum texture size supported by OpenGL. + * + * @param gl Current OpenGL context, assumed not null + * @return Maximum texture size + */ + private static int findMaxSize(/*@Nonnull*/ final GL gl) { + final int[] size = new int[1]; + gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, size, 0); + return size[0]; + } + + /** + * Sends an event to all the listeners. + * + * @param type Kind of event, assumed not null + * @param data Information to send with event, assumed not null + */ + private void fireEvent(/*@Nonnull*/ final EventType type, /*@Nonnull*/ final Object data) { + for (final EventListener listener : listeners) { + assert listener != null : "addListener rejects null"; + listener.onGlyphCacheEvent(type, data); + } + } + + /** + * Returns object actually storing the rasterized glyphs. + * + * @return Object actually storing the rasterized glyphs, not null + */ + /*@Nonnull*/ + TextureBackingStore getBackingStore() { + return (TextureBackingStore) packer.getBackingStore(); + } + + /** + * Determines the location of a glyph's bottom baseline. + * + * @param glyph Glyph to determine bottom baseline for, assumed not null + * @return Location of glyph's bottom baseline, which may be negative + */ + /*@CheckForSigned*/ + private int getBottomBaselineLocation(/*@Nonnull*/ final Glyph glyph) { + return (int) (glyph.location.y() + glyph.margin.top + glyph.ascent); + } + + /** + * Determines the location of a glyph's bottom border. + * + * @param glyph Glyph to determine bottom border for, assumed not null + * @return Location of glyph's bottom border, which may be negative + */ + /*@CheckForSigned*/ + private int getBottomBorderLocation(/*@Nonnull*/ final Glyph glyph) { + return (int) (glyph.location.y() + glyph.margin.top + glyph.height); + } + + /** + * Returns the font render context used for text size computations by this {@link GlyphCache}. + * + *

+ * This object should be considered transient and may become invalidated between {@link + * #beginRendering} and {@link #endRendering} pairs. + * + * @return Font render context used for text size computations, not null + */ + /*@Nonnull*/ + public FontRenderContext getFontRenderContext() { + return getBackingStore().getGraphics().getFontRenderContext(); + } + + /** + * Returns the height of this {@link GlyphCache}. + * + * @return Height of this cache, not negative + */ + /*@Nonnegative*/ + int getHeight() { + return getBackingStore().getHeight(); + } + + /** + * Determines the location of a glyph's left baseline. + * + * @param glyph Glyph to determine left baseline for, assumed not null + * @return Location of glyph's left baseline, which may be negative + */ + /*@CheckForSigned*/ + private int getLeftBaselineLocation(/*@Nonnull*/ final Glyph glyph) { + return (int) (glyph.location.x() + glyph.margin.left - glyph.kerning); + } + + /** + * Determines the location of a glyph's left border. + * + * @param glyph Glyph to determine left border for, assumed not null + * @return Location of glyph's left border, which may be negative + */ + /*@CheckForSigned*/ + private int getLeftBorderLocation(/*@Nonnull*/ final Glyph glyph) { + return glyph.location.x() + glyph.margin.left; + } + + /** + * Checks if this {@link GlyphCache} is interpolating when sampling. + * + * @return True if this glyph cache is interpolating when it samples + */ + public boolean getUseSmoothing() { + return ((TextureBackingStoreManager) manager).getUseSmoothing(); + } + + /** + * Returns the width of this {@link GlyphCache}. + * + * @return Width of this cache, not negative + */ + /*@Nonnegative*/ + int getWidth() { + return getBackingStore().getWidth(); + } + + /** + * Checks if Non-Power-Of-Two textures are available. + * + * @param gl Current OpenGL context + * @return True if NPOT textures are available + * @throws NullPointerException if context is null + */ + static boolean isNpotTextureAvailable(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + return gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two"); + } + + private static void log(/*@Nonnull*/ final String message) { + if (DEBUG) { + System.err.println(message); + } + } + + private static void log(/*@Nonnull*/ final String message, + /*@CheckForNull*/ final Object arg) { + if (DEBUG) { + System.err.println(String.format(message, arg)); + } + } + + /** + * Marks a glyph's location as used. + * + * @param glyph Glyph to mark + * @throws NullPointerException if glyph is null + */ + static void markGlyphLocationUsed(/*@Nonnull*/ final Glyph glyph) { + + Check.notNull(glyph, "Glyph cannot be null"); + + ((TextData) glyph.location.getUserData()).markUsed(); + } + + /** + * Creates a new {@link GlyphCache}. + * + * @param font Font that was used to create glyphs that will be stored + * @param rd Controller of rendering bitmapped text + * @param antialias Whether to render glyphs with smooth edges + * @param subpixel Whether to consider subpixel positioning + * @param mipmap Whether to create multiple sizes for texture + * @return New glyph cache instance, not null + * @throws NullPointerException if font or render delegate is null + * @throws IllegalArgumentException if render delegate wants full color + */ + /*@Nonnull*/ + public static GlyphCache newInstance(/*@Nonnull*/ final Font font, + /*@Nonnull*/ final RenderDelegate rd, + final boolean antialias, + final boolean subpixel, + final boolean mipmap) { + + Check.notNull(font, "Font cannot be null"); + Check.notNull(rd, "Render delegate cannot be null"); + + final GlyphCache gc = new GlyphCache(font, rd, antialias, subpixel, mipmap); + gc.manager.addListener(gc); + return gc; + } + + /** + * Responds to an event from the backing store. + * + * @param type Kind of backing store event + * @throws NullPointerException if type is null + */ + @Override + public void onBackingStoreEvent(/*@Nonnull*/ final TextureBackingStore.EventType type) { + + Check.notNull(type, "Event type cannot be null"); + + switch (type) { + case REALLOCATE: + onBackingStoreReallocate(); + break; + case FAILURE: + onBackingStoreFailure(); + break; + } + } + + /** + * Responds to the backing store failing (reallocation). + */ + private void onBackingStoreFailure() { + packer.clear(); + fireEvent(EventType.CLEAR, null); + } + + /** + * Handles when a backing store is reallocated. + * + *

+ * First notifies observers, then tries to remove any unused entries, and finally erases the + * texture coordinates of each entry since the width and height of the total texture has + * changed. Note that since the backing store is just expanded without moving any entries, + * only the texture coordinates need to be recalculated. The locations will still be the same. + * + *

+ * 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. + */ + private void onBackingStoreReallocate() { + fireEvent(EventType.REALLOCATE, null); + clearUnusedEntries(); + clearTextureCoordinates(); + } + + /** + * Changes the maximum size of this {@link GlyphCache}'s rectangle packer. + * + * @param gl Current OpenGL context, assumed not null + */ + private void setMaxSize(/*@Nonnull*/ final GL gl) { + final int maxSize = findMaxSize(gl); + packer.setMaxSize(maxSize, maxSize); + } + + /** + * Changes whether this {@link GlyphCache}'s texture should interpolate when sampling. + * + * @param useSmoothing True to use linear interpolation + */ + public void setUseSmoothing(boolean useSmoothing) { + ((TextureBackingStoreManager) manager).setUseSmoothing(useSmoothing); + getBackingStore().setUseSmoothing(useSmoothing); + } + + /** + * Forces the cache to update the underlying OpenGL texture. + * + *

+ * After calling this method, all uploaded glyphs will be guaranteed to be present in the + * underlying OpenGL texture. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + public void update(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final TextureBackingStore bs = getBackingStore(); + bs.update(gl); + } + + /** + * Stores a glyph in the cache. + * + *

+ * Determines a place to put the glyph in the underlying OpenGL texture, computes the glyph's + * texture coordinates for that position, and requests the glyph be drawn into the texture. + * (Note however that to increase performance the glyph is not guaranteed to actually be in the + * texture until {@link #update(GL)} or {@link #endRendering(GL)} is called.) + * + * @param glyph Glyph not already stored in cache + * @throws NullPointerException if glyph is null + */ + public void upload(/*@Nonnull*/ final Glyph glyph) { + + Check.notNull(glyph, "Glyph cannot be null"); + + // Perform upload steps + findLocation(glyph); + computeCoordinates(glyph); + drawInBackingStore(glyph); + + // Make sure it's marked as used + markGlyphLocationUsed(glyph); + } + + /** + * Object that wants to be notified of cache events. + */ + public interface EventListener { + + /** + * Responds to an event from a {@link GlyphCache}. + * + * @param type Type of event + * @param data Object that triggered the event, i.e., a glyph + * @throws NullPointerException if event type or data is null (optional) + */ + void onGlyphCacheEvent(/*@Nonnull*/ EventType type, /*@Nonnull*/ Object data); + } + + /** + * Type of event fired from the cache. + */ + public enum EventType { + + /** + * All entries were removed from cache. + */ + CLEAR, + + /** + * Unused entries were removed from cache. + */ + CLEAN, + + /** + * Backing store changed size. + */ + REALLOCATE; + } + + /** + * Data associated with each rectangle of text. + */ + /*@NotThreadSafe*/ + static final class TextData { + + /** + * Visual representation of text. + */ + /*@Nonnull*/ + final Glyph glyph; + + /** + * True if text was used recently. + */ + private boolean used; + + /** + * Constructs a {@link TextData} from a glyph. + * + * @param glyph Visual representation of text + * @throws NullPointerException if glyph is null + */ + TextData(/*@Nonnull*/ final Glyph glyph) { + this.glyph = Check.notNull(glyph, "Glyph cannot be null"); + } + + /** + * Indicates this {@link TextData} is no longer being used. + */ + void clearUsed() { + used = false; + } + + /** + * Indicates this {@link TextData} was just used. + */ + void markUsed() { + used = true; + } + + /** + * Returns the actual text stored with a rectangle. + * + * @return Actual text stored with a rectangle, not null + */ + /*@CheckForNull*/ + String string() { + return glyph.str; + } + + /** + * Returns true if text has been used recently. + */ + boolean used() { + return used; + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphMap.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphMap.java new file mode 100644 index 0000000000..e1981c887d --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphMap.java @@ -0,0 +1,180 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + + +/** + * Utility for mapping text to glyphs. + */ +/*@NotThreadSafe*/ +final class GlyphMap { + + /** + * Fast map for ASCII chars. + */ + /*@Nonnull*/ + private final Glyph[] ascii = new Glyph[128]; + + /** + * Map from char to code. + */ + /*@Nonnull*/ + private final Map codes = new HashMap(); + + /** + * Map from code to glyph. + */ + /*@Nonnull*/ + private final Map unicode = new HashMap(); + + /** + * Glyphs with layout flags. + */ + /*@Nonnull*/ + private final Map complex = new HashMap(); + + /** + * Constructs a glyph map. + */ + GlyphMap() { + // empty + } + + /** + * Deletes all glyphs stored in the map. + */ + void clear() { + Arrays.fill(ascii, null); + codes.clear(); + unicode.clear(); + complex.clear(); + } + + /** + * Returns a glyph for a character. + * + * @param c Character to get glyph for + * @return Glyph for the character, or null if it wasn't found + */ + /*@CheckForNull*/ + Glyph get(final char c) { + return (c < 128) ? ascii[c] : unicode.get(codes.get(c)); + } + + /** + * Returns a glyph for a string. + * + * @param str String to get glyph for, which may be null + * @return Glyph for the string, or null if it wasn't found + */ + /*@CheckForNull*/ + Glyph get(/*@CheckForNull*/ final String str) { + return complex.get(str); + } + + /** + * Stores a simple glyph in the map. + * + * @param c Character glyph represents + * @param glyph Glyph to store + * @throws NullPointerException if glyph is null + */ + void put(final char c, /*@Nonnull*/ final Glyph glyph) { + + Check.notNull(glyph, "Glyph cannot be null"); + + if (c < 128) { + ascii[c] = glyph; + } else { + codes.put(c, glyph.code); + unicode.put(glyph.code, glyph); + } + } + + /** + * Stores a complex glyph in the map. + * + * @param str String glyph represents + * @param glyph Glyph to store + * @throws NullPointerException if string or glyph is null + */ + void put(/*@Nonnull*/ final String str, /*@Nonnull*/ final Glyph glyph) { + + Check.notNull(str, "String cannot be null"); + Check.notNull(glyph, "Glyph cannot be null"); + + complex.put(str, glyph); + } + + /** + * Deletes a simple glyph from this {@link GlyphMap}. + * + * @param c Character of glyph to remove + */ + private void remove(final char c) { + if (c < 128) { + ascii[c] = null; + } else { + final Character character = c; + final Integer code = codes.get(character); + unicode.remove(code); + codes.remove(character); + } + } + + /** + * Deletes a single glyph from this {@link GlyphMap}. + * + * @param glyph Glyph to remove, ignored if null + */ + void remove(/*@CheckForNull*/ final Glyph glyph) { + + if (glyph == null) { + return; + } + + if (glyph.str != null) { + remove(glyph.str); + } else { + remove(glyph.character); + } + } + + /** + * Deletes a complex glyph from this {@link GlyphMap}. + * + * @param str Text of glyph to remove, ignored if null + */ + private void remove(/*@CheckForNull*/ final String str) { + complex.remove(str); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphProducer.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphProducer.java new file mode 100644 index 0000000000..b2cb50ee92 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphProducer.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import java.awt.geom.Rectangle2D; +import java.util.List; + + +/** + * Utility for creating glyphs. + */ +public interface GlyphProducer { + + /** + * Deletes all stored glyphs. + */ + void clearGlyphs(); + + /** + * Makes a glyph for a single character. + * + * @param c Character + * @return Reused instance of a glyph + */ + Glyph createGlyph(char c); + + /** + * Makes a glyph for each character in a string. + * + * @param str Text as a string + * @return View of glyphs valid until next call + * @throws NullPointerException if string is null + */ + /*@Nonnull*/ + List createGlyphs(/*@Nonnull*/ String str); + + /** + * Determines the distance to the next character after a glyph. + * + * @param c Character to find advance of + * @return Distance to the next character after a glyph, which may be negative + */ + /*@CheckForSigned*/ + float findAdvance(char c); + + /** + * Determines the visual bounds of a string with padding added. + * + * @param str Text to find visual bounds of + * @return Visual bounds of string with padding added, not null + * @throws NullPointerException if string is null + */ + /*@Nonnull*/ + Rectangle2D findBounds(/*@Nonnull*/ String str); + + /** + * Deletes a single stored glyph. + * + * @param glyph Previously created glyph, ignored if null + */ + void removeGlyph(/*@CheckForNull*/ Glyph glyph); +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphProducers.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphProducers.java new file mode 100644 index 0000000000..62a0b19019 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphProducers.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.util.awt.TextRenderer.RenderDelegate; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.lang.Character.UnicodeBlock; + + +/** + * Utility for working with {@link GlyphProducer}'s. + */ +/*@ThreadSafe*/ +public final class GlyphProducers { + + /** + * Prevents instantiation. + */ + private GlyphProducers() { + // empty + } + + /** + * Creates a {@link GlyphProducer} based on a range of characters. + * + * @param font Style of text + * @param rd Controller of rendering details + * @param frc Details on how fonts are rendered + * @param ub Range of characters to support + * @return Correct glyph producer for unicode block, not null + * @throws NullPointerException if font, render delegate, or render context is null + */ + /*@Nonnull*/ + public static GlyphProducer get(/*@Nonnull*/ final Font font, + /*@Nonnull*/ final RenderDelegate rd, + /*@Nonnull*/ final FontRenderContext frc, + /*@CheckForNull*/ final UnicodeBlock ub) { + + Check.notNull(font, "Font cannot be null"); + Check.notNull(rd, "Render delegate cannot be null"); + Check.notNull(frc, "Font render context cannot be null"); + + if (ub == UnicodeBlock.BASIC_LATIN) { + return new AsciiGlyphProducer(font, rd, frc); + } else { + return new UnicodeGlyphProducer(font, rd, frc); + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRenderer.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRenderer.java new file mode 100644 index 0000000000..d1921b5bdc --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRenderer.java @@ -0,0 +1,175 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.util.texture.TextureCoords; + + +/** + * Utility for drawing glyphs. + */ +public interface GlyphRenderer { + + /** + * Registers an {@link EventListener} with this {@link GlyphRenderer}. + * + * @param listener Listener to register + * @throws NullPointerException if listener is null + */ + void addListener(/*@Nonnull*/ EventListener listener); + + /** + * Starts a render cycle with this {@link GlyphRenderer}. + * + * @param gl Current OpenGL context + * @param ortho True if using orthographic projection + * @param width Width of current OpenGL viewport + * @param height Height of current OpenGL viewport + * @param disableDepthTest True if should ignore depth values + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if width or height is negative + * @throws GLException if context is unexpected version + */ + void beginRendering(/*@Nonnull*/ GL gl, + boolean ortho, + /*@Nonnegative*/ int width, + /*@Nonnegative*/ int height, + boolean disableDepthTest); + + /** + * Frees resources used by this {@link GlyphRenderer}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + void dispose(/*@Nonnull*/ GL gl); + + /** + * Draws a glyph with this {@link GlyphRenderer}. + * + * @param gl Current OpenGL context + * @param glyph Visual representation of a character + * @param x Position to draw on X axis, which may be negative + * @param y Position to draw on Y axis, which may be negative + * @param z Position to draw on Z axis, which may be negative + * @param scale Relative size of glyph, which may be negative + * @param coords Texture coordinates of glyph + * @return Distance to next character, which may be negative + * @throws NullPointerException if context, glyph, or texture coordinate is null + * @throws GLException if context is unexpected version + */ + /*@CheckForSigned*/ + float drawGlyph(/*@Nonnull*/ GL gl, + /*@Nonnull*/ Glyph glyph, + /*@CheckForSigned*/ float x, + /*@CheckForSigned*/ float y, + /*@CheckForSigned*/ float z, + /*@CheckForSigned*/ float scale, + /*@Nonnull*/ TextureCoords coords); + + /** + * Finishes a render cycle with this {@link GlyphRenderer}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + void endRendering(/*@Nonnull*/ GL gl); + + /** + * Forces all stored text to be rendered. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + * @throws IllegalStateException if not in a render cycle + */ + void flush(/*@Nonnull*/ GL gl); + + /** + * Checks if this {@link GlyphRenderer} is using vertex arrays. + * + * @return True if this renderer is using vertex arrays + */ + boolean getUseVertexArrays(); + + /** + * Changes the color used to draw the text. + * + * @param r Red component of color + * @param g Green component of color + * @param b Blue component of color + * @param a Alpha component of color + */ + void setColor(float r, float g, float b, float a); + + /** + * Changes the transformation matrix for drawing in 3D. + * + * @param gl Current OpenGL context + * @param value Matrix as float array + * @param transpose True if array is in in row-major order + * @throws IndexOutOfBoundsException if value's length is less than sixteen + * @throws IllegalStateException if in orthographic mode + */ + void setTransform(/*@Nonnull*/ float[] value, boolean transpose); + + /** + * Changes whether vertex arrays are in use. + * + * @param useVertexArrays true to use vertex arrays + */ + void setUseVertexArrays(boolean useVertexArrays); + + /** + * Observer of a {@link GlyphRenderer}. + */ + public interface EventListener { + + /** + * Responds to an event from a glyph renderer. + * + * @param type Type of event + * @throws NullPointerException if event type is null + */ + public void onGlyphRendererEvent(EventType type); + } + + /** + * Type of event fired from the renderer. + */ + public static enum EventType { + + /** + * Renderer is automatically flushing queued glyphs, e.g., when it's full or color changes. + */ + AUTOMATIC_FLUSH; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRendererGL2.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRendererGL2.java new file mode 100644 index 0000000000..6745a0230b --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRendererGL2.java @@ -0,0 +1,211 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GLExtensions; + + +/** + * {@link GlyphRenderer} for use with OpenGL 2. + */ +/*@VisibleForTesting*/ +/*@NotThreadSafe*/ +public final class GlyphRendererGL2 extends AbstractGlyphRenderer { + + /** + * True if using vertex arrays. + */ + private boolean useVertexArrays = true; + + /** + * Constructs a {@link GlyphRendererGL2}. + */ + /*@VisibleForTesting*/ + public GlyphRendererGL2() { + // empty + } + + @Override + protected void doBeginRendering(/*@Nonnull*/ final GL gl, + final boolean ortho, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean disableDepthTest) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + final GL2 gl2 = gl.getGL2(); + + // Change general settings + gl2.glPushAttrib(getAttribMask(ortho)); + gl2.glDisable(GL2.GL_LIGHTING); + gl2.glEnable(GL2.GL_BLEND); + gl2.glBlendFunc(GL2.GL_ONE, GL2.GL_ONE_MINUS_SRC_ALPHA); + gl2.glEnable(GL2.GL_TEXTURE_2D); + gl2.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE); + + // Set up transformations + if (ortho) { + if (disableDepthTest) { + gl2.glDisable(GL2.GL_DEPTH_TEST); + } + gl2.glDisable(GL2.GL_CULL_FACE); + gl2.glMatrixMode(GL2.GL_PROJECTION); + gl2.glPushMatrix(); + gl2.glLoadIdentity(); + gl2.glOrtho(0, width, 0, height, -1, +1); + gl2.glMatrixMode(GL2.GL_MODELVIEW); + gl2.glPushMatrix(); + gl2.glLoadIdentity(); + gl2.glMatrixMode(GL2.GL_TEXTURE); + gl2.glPushMatrix(); + gl2.glLoadIdentity(); + } + } + + /*@Nonnull*/ + protected QuadPipeline doCreateQuadPipeline(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL2 gl2 = gl.getGL2(); + + if (useVertexArrays) { + if (gl2.isExtensionAvailable(GLExtensions.VERSION_1_5)) { + return new QuadPipelineGL15(gl2); + } else if (gl2.isExtensionAvailable("GL_VERSION_1_1")) { + return new QuadPipelineGL11(); + } else { + return new QuadPipelineGL10(); + } + } else { + return new QuadPipelineGL10(); + } + } + + protected void doDispose(/*@Nonnull*/ final GL gl) { + Check.notNull(gl, "GL cannot be null"); + } + + @Override + protected void doEndRendering(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL2 gl2 = gl.getGL2(); + + // Reset transformations + if (isOrthoMode()) { + gl2.glMatrixMode(GL2.GL_PROJECTION); + gl2.glPopMatrix(); + gl2.glMatrixMode(GL2.GL_MODELVIEW); + gl2.glPopMatrix(); + gl2.glMatrixMode(GL2.GL_TEXTURE); + gl2.glPopMatrix(); + } + + // Reset general settings + gl2.glPopAttrib(); + } + + @Override + protected void doSetColor(/*@Nonnull*/ final GL gl, + final float r, + final float g, + final float b, + final float a) { + + Check.notNull(gl, "GL cannot be null"); + + final GL2 gl2 = gl.getGL2(); + + gl2.glColor4f(r, g, b, a); + } + + @Override + protected void doSetTransform3d(/*@Nonnull*/ final GL gl, + /*@Nonnull*/ final float[] value, + final boolean transpose) { + + Check.notNull(gl, "GL cannot be null"); + Check.notNull(value, "Value cannot be null"); + + // FIXME: Could implement this... + throw new UnsupportedOperationException("Use standard GL instead"); + } + + @Override + protected void doSetTransformOrtho(/*@Nonnull*/ final GL gl, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + final GL2 gl2 = gl.getGL2(); + + gl2.glMatrixMode(GL2.GL_PROJECTION); + gl2.glPushMatrix(); + gl2.glLoadIdentity(); + gl2.glOrtho(0, width, 0, height, -1, +1); + gl2.glMatrixMode(GL2.GL_MODELVIEW); + gl2.glPushMatrix(); + gl2.glLoadIdentity(); + } + + /** + * Returns attribute bits for {@code glPushAttrib} calls. + * + * @param ortho True if using orthographic projection + * @return Attribute bits for {@code glPushAttrib} calls + */ + private static int getAttribMask(final boolean ortho) { + return GL2.GL_ENABLE_BIT | + GL2.GL_TEXTURE_BIT | + GL2.GL_COLOR_BUFFER_BIT | + (ortho ? (GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_TRANSFORM_BIT) : 0); + } + + @Override + public boolean getUseVertexArrays() { + return useVertexArrays; + } + + @Override + public void setUseVertexArrays(final boolean useVertexArrays) { + if (useVertexArrays != this.useVertexArrays) { + dirtyPipeline(); + this.useVertexArrays = useVertexArrays; + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRendererGL3.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRendererGL3.java new file mode 100644 index 0000000000..1f69e2a465 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRendererGL3.java @@ -0,0 +1,269 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL3; + + +/** + * Utility for drawing glyphs with OpenGL 3. + */ +/*@VisibleForTesting*/ +/*@NotThreadSafe*/ +public final class GlyphRendererGL3 extends AbstractGlyphRenderer { + + /** + * Source code of vertex shader. + */ + /*@Nonnull*/ + private static final String VERT_SOURCE = + "#version 140\n" + + "uniform mat4 MVPMatrix;\n" + + "in vec4 MCVertex;\n" + + "in vec2 TexCoord0;\n" + + "out vec2 Coord0;\n" + + "void main() {\n" + + " gl_Position = MVPMatrix * MCVertex;\n" + + " Coord0 = TexCoord0;\n" + + "}\n"; + + /** + * Source code of fragment shader. + */ + /*@Nonnull*/ + private static final String FRAG_SOURCE = + "#version 140\n" + + "uniform sampler2D Texture;\n" + + "uniform vec4 Color=vec4(1,1,1,1);\n" + + "in vec2 Coord0;\n" + + "out vec4 FragColor;\n" + + "void main() {\n" + + " float sample;\n" + + " sample = texture(Texture,Coord0).r;\n" + + " FragColor = Color * sample;\n" + + "}\n"; + + /** + * True if blending needs to be reset. + */ + private boolean restoreBlending; + + /** + * True if depth test needs to be reset. + */ + private boolean restoreDepthTest; + + /** + * Shader program. + */ + /*@Nonnegative*/ + private final int program; + + /** + * Uniform for modelview projection. + */ + /*@Nonnull*/ + private final Mat4Uniform transform; + + /** + * Uniform for color of glyphs. + */ + /*@Nonnull*/ + private final Vec4Uniform color; + + /** + * Width of last orthographic render. + */ + /*@Nonnegative*/ + private int lastWidth = 0; + + /** + * Height of last orthographic render + */ + /*@Nonnegative*/ + private int lastHeight = 0; + + /** + * Constructs a {@link GlyphRendererGL3}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + /*@VisibleForTesting*/ + public GlyphRendererGL3(/*@Nonnull*/ final GL3 gl) { + + Check.notNull(gl, "GL cannot be null"); + + this.program = ShaderLoader.loadProgram(gl, VERT_SOURCE, FRAG_SOURCE); + this.transform = new Mat4Uniform(gl, program, "MVPMatrix"); + this.color = new Vec4Uniform(gl, program, "Color"); + } + + @Override + protected void doBeginRendering(/*@Nonnull*/ final GL gl, + final boolean ortho, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean disableDepthTest) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + final GL3 gl3 = gl.getGL3(); + + // Activate program + gl3.glUseProgram(program); + + // Check blending and depth test + restoreBlending = false; + if (!gl3.glIsEnabled(GL.GL_BLEND)) { + gl3.glEnable(GL.GL_BLEND); + gl3.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA); + restoreBlending = true; + } + restoreDepthTest = false; + if (disableDepthTest && gl3.glIsEnabled(GL.GL_DEPTH_TEST)) { + gl3.glDisable(GL.GL_DEPTH_TEST); + restoreDepthTest = true; + } + + // Check transform + if (ortho) { + doSetTransformOrtho(gl, width, height); + } + } + + @Override + protected QuadPipeline doCreateQuadPipeline(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL3 gl3 = gl.getGL3(); + return new QuadPipelineGL30(gl3, program); + } + + protected void doDispose(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL3 gl3 = gl.getGL3(); + + gl3.glUseProgram(0); + gl3.glDeleteProgram(program); + } + + @Override + protected void doEndRendering(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL3 gl3 = gl.getGL3(); + + // Deactivate program + gl3.glUseProgram(0); + + // Check blending and depth test + if (restoreBlending) { + gl3.glDisable(GL.GL_BLEND); + } + if (restoreDepthTest) { + gl3.glEnable(GL.GL_DEPTH_TEST); + } + } + + @Override + protected void doSetColor(/*@Nonnull*/ final GL gl, + final float r, + final float g, + final float b, + final float a) { + + Check.notNull(gl, "GL cannot be null"); + + final GL3 gl3 = gl.getGL3(); + + color.value[0] = r; + color.value[1] = g; + color.value[2] = b; + color.value[3] = a; + color.update(gl3); + } + + @Override + protected void doSetTransform3d(/*@Nonnull*/ final GL gl, + /*@Nonnull*/ final float[] value, + final boolean transpose) { + + Check.notNull(gl, "GL cannot be null"); + Check.notNull(value, "Value cannot be null"); + + final GL3 gl3 = gl.getGL3(); + + gl3.glUniformMatrix4fv(transform.location, 1, transpose, value, 0); + transform.dirty = true; + } + + @Override + protected void doSetTransformOrtho(/*@Nonnull*/ final GL gl, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + final GL3 gl3 = gl.getGL3(); + + // Recompute if width and height changed + if (width != lastWidth || height != lastHeight) { + Projection.orthographic(transform.value, width, height); + transform.transpose = true; + transform.dirty = true; + lastWidth = width; + lastHeight = height; + } + + // Upload if made dirty anywhere + if (transform.dirty) { + transform.update(gl3); + transform.dirty = false; + } + } + + @Override + public boolean getUseVertexArrays() { + return true; + } + + @Override + public void setUseVertexArrays(final boolean useVertexArrays) { + // empty + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRenderers.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRenderers.java new file mode 100644 index 0000000000..0508acdccc --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GlyphRenderers.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLProfile; + + +/** + * Utility for working with {@link GlyphRenderer}'s. + */ +/*@ThreadSafe*/ +public final class GlyphRenderers { + + /** + * Prevents instantiation. + */ + private GlyphRenderers() { + // pass + } + + /** + * Creates a {@link GlyphRenderer} based on the current OpenGL context. + * + * @param gl Current OpenGL context + * @return New glyph renderer for the given context, not null + * @throws NullPointerException if context is null + * @throws UnsupportedOperationException if GL is unsupported + */ + /*@Nonnull*/ + public static GlyphRenderer get(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GLProfile profile = gl.getGLProfile(); + + if (profile.isGL3()) { + return new GlyphRendererGL3(gl.getGL3()); + } else if (profile.isGL2()) { + return new GlyphRendererGL2(); + } else { + throw new UnsupportedOperationException("Profile currently unsupported"); + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/GrayTexture2D.java b/src/jogl/classes/jogamp/opengl/util/awt/text/GrayTexture2D.java new file mode 100644 index 0000000000..e98b5c9a03 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/GrayTexture2D.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GL3; + + +/** + * Two-dimensional, grayscale OpenGL texture. + */ +final class GrayTexture2D extends Texture2D { + + /** + * Creates a two-dimensional, grayscale texture. + * + * @param gl Current OpenGL context + * @param width Size of texture on X axis + * @param height Size of texture on Y axis + * @param smooth True to interpolate samples + * @param mipmap True for high quality + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if width or height is negative + */ + GrayTexture2D(/*@Nonnull*/ final GL gl, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean smooth, + final boolean mipmap) { + super(gl, width, height, smooth, mipmap); + } + + @Override + protected int getFormat(/*@Nonnull*/ final GL gl) { + Check.notNull(gl, "GL cannot be null"); + return gl.getGLProfile().isGL2() ? GL2.GL_LUMINANCE : GL3.GL_RED; + } + + @Override + protected int getInternalFormat(/*@Nonnull*/ final GL gl) { + Check.notNull(gl, "GL cannot be null"); + return gl.getGLProfile().isGL2() ? GL2.GL_INTENSITY : GL3.GL_RED; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Mat4Uniform.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Mat4Uniform.java new file mode 100644 index 0000000000..c84a493622 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Mat4Uniform.java @@ -0,0 +1,69 @@ + +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL2GL3; + + +/** + * Uniform for a {@code mat4}. + */ +/*@NotThreadSafe*/ +final class Mat4Uniform extends Uniform { + + /** + * Local copy of matrix values. + */ + final float[] value = new float[16]; + + /** + * True if matrix is stored in row-major order. + */ + boolean transpose; + + /** + * Constructs a {@link UniformMatrix}. + * + * @param gl Current OpenGL context + * @param program OpenGL handle to shader program + * @param name Name of the uniform in shader source code + * @throws NullPointerException if context is null + */ + Mat4Uniform(/*@Nonnull*/ final GL2GL3 gl, + /*@Nonnegative*/ final int program, + /*@Nonnull*/ final String name) { + super(gl, program, name); + } + + @Override + void update(/*@Nonnull*/ final GL2GL3 gl) { + Check.notNull(gl, "GL cannot be null"); + gl.glUniformMatrix4fv(location, 1, transpose, value, 0); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Projection.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Projection.java new file mode 100644 index 0000000000..ae842248d3 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Projection.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + + +/** + * Utility for computing projections. + */ +/*@NotThreadSafe*/ +final class Projection { + + /** + * Prevents instantiation. + */ + private Projection() { + // empty + } + + /** + * Computes an orthographic projection matrix. + * + * @param v Computed matrix values, in row-major order + * @param width Width of current OpenGL viewport + * @param height Height of current OpenGL viewport + * @throws NullPointerException if array is null + * @throws IllegalArgumentException if width or height is negative + */ + static void orthographic(/*@Nonnull*/ final float[] v, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height) { + + Check.notNull(v, "Matrix cannot be null"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + // Zero out + for (int i = 0; i < 16; ++i) { + v[i] = 0; + } + + // Translate to origin + v[3] = -1; + v[7] = -1; + + // Scale to unit cube + v[0] = 2f / width; + v[5] = 2f / height; + v[10] = -1; + v[15] = 1; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Quad.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Quad.java new file mode 100644 index 0000000000..03836a6d33 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Quad.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + + +/** + * Structure for points and coordinates. + */ +/*@VisibleForTesting*/ +/*@NotThreadSafe*/ +public final class Quad { + + /** + * Position of left side. + */ + public float xl; + + /** + * Position of right side. + */ + public float xr; + + /** + * Position of bottom side. + */ + public float yb; + + /** + * Position of top side. + */ + public float yt; + + /** + * Depth. + */ + public float z; + + /** + * Left texture coordinate. + */ + public float sl; + + /** + * Right texture coordinate. + */ + public float sr; + + /** + * Bottom texture coordinate. + */ + public float tb; + + /** + * Top texture coordinate. + */ + public float tt; +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipeline.java b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipeline.java new file mode 100644 index 0000000000..b9f2cb3a08 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipeline.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; + + +/** + * Utility for drawing a stream of quads. + */ +/*@VisibleForTesting*/ +public interface QuadPipeline { + + /** + * Registers an {@link EventListener} with this {@link QuadPipeline}. + * + * @param listener Listener to register + * @throws NullPointerException if listener is null + */ + void addListener(/*@Nonnull*/ EventListener listener); + + /** + * Adds a quad to this {@link QuadPipeline}. + * + * @param gl Current OpenGL context + * @param quad Quad to add to pipeline + * @throws NullPointerException if context or quad is null + * @throws GLException if context is unexpected version + */ + void addQuad(/*@Nonnull*/ GL gl, /*@Nonnull*/ Quad quad); + + /** + * Starts a render cycle with this {@link QuadPipeline}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + void beginRendering(/*@Nonnull*/ GL gl); + + /** + * Frees resources used by this {@link QuadPipeline}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + void dispose(/*@Nonnull*/ GL gl); + + /** + * Finishes a render cycle with this {@link QuadPipeline}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + void endRendering(/*@Nonnull*/ GL gl); + + /** + * Draws all vertices in this {@link QuadPipeline}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + * @throws GLException if context is unexpected version + */ + void flush(/*@Nonnull*/ GL gl); + + // TODO: Rename to `size`? + /** + * Returns number of quads in this {@link QuadPipeline}. + * + * @return Number of quads in this pipeline, not negative + */ + /*@Nonnegative*/ + int getSize(); + + /** + * Checks if there aren't any quads in this {@link QuadPipeline}. + * + * @return True if there aren't any quads in this pipeline + */ + boolean isEmpty(); + + /** + * Deregisters an {@link EventListener} from this {@link QuadPipeline}. + * + * @param listener Listener to deregister, ignored if null or unregistered + */ + void removeListener(/*@CheckForNull*/ EventListener listener); + + /** + * Observer of a {@link QuadPipeline}. + */ + interface EventListener { + + /** + * Responds to an event from a {@link QuadPipeline}. + * + * @param type Type of event + * @throws NullPointerException if event type is null + */ + void onQuadPipelineEvent(/*@Nonnull*/ EventType type); + } + + /** + * Kind of event. + */ + enum EventType { + + /** + * Pipeline is automatically flushing all queued quads, e.g., when it's full. + */ + AUTOMATIC_FLUSH; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL10.java b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL10.java new file mode 100644 index 0000000000..83884aa3c3 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL10.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; + + +/** + * {@link QuadPipeline} for use with OpenGL 1.0. + */ +/*@VisibleForTesting*/ +/*@NotThreadSafe*/ +public final class QuadPipelineGL10 extends AbstractQuadPipeline { + + /** + * Number of vertices per primitive. + */ + /*@Nonnegative*/ + private static final int VERTS_PER_PRIM = 4; + + /** + * Number of primitives per quad. + */ + /*@Nonnegative*/ + private static final int PRIMS_PER_QUAD = 1; + + /** + * Constructs a {@link QuadPipelineGL10}. + */ + /*@VisibleForTesting*/ + public QuadPipelineGL10() { + super(VERTS_PER_PRIM, PRIMS_PER_QUAD); + } + + @Override + protected void doFlush(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL2 gl2 = gl.getGL2(); + + gl2.glBegin(GL2.GL_QUADS); + try { + rewind(); + final int size = getSize(); + for (int q = 0; q < size; ++q) { + for (int v = 0; v < VERTS_PER_QUAD; ++v) { + gl2.glVertex3f(getFloat(), getFloat(), getFloat()); + gl2.glTexCoord2f(getFloat(), getFloat()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + gl2.glEnd(); + clear(); + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL11.java b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL11.java new file mode 100644 index 0000000000..f8f9a65b3a --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL11.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; + +import java.nio.FloatBuffer; + + +/** + * {@link QuadPipeline} for use with OpenGL 1.1. + */ +/*@VisibleForTesting*/ +/*@NotThreadSafe*/ +public final class QuadPipelineGL11 extends AbstractQuadPipeline { + + /** + * Number of vertices per primitive. + */ + /*@Nonnegative*/ + private static final int VERTS_PER_PRIM = 4; + + /** + * Number of primitives per quad. + */ + /*@Nonnegative*/ + private static final int PRIMS_PER_QUAD = 1; + + /** + * Vertex array for points. + */ + /*@Nonnull*/ + private final FloatBuffer pointsArray; + + /** + * Vertex array for texture coordinates. + */ + /*@Nonnull*/ + private final FloatBuffer coordsArray; + + /** + * Constructs a {@link QuadPipelineGL11}. + */ + /*@VisibleForTesting*/ + public QuadPipelineGL11() { + + super(VERTS_PER_PRIM, PRIMS_PER_QUAD); + + pointsArray = createFloatBufferView(getData(), POINT_OFFSET); + coordsArray = createFloatBufferView(getData(), COORD_OFFSET); + } + + @Override + public void beginRendering(/*@Nonnull*/ final GL gl) { + + super.beginRendering(gl); + + final GL2 gl2 = gl.getGL2(); + + // Push state + gl2.glPushClientAttrib((int) GL2.GL_ALL_CLIENT_ATTRIB_BITS); + + // Points + gl2.glEnableClientState(GL2.GL_VERTEX_ARRAY); + gl2.glVertexPointer( + FLOATS_PER_POINT, // size + GL2.GL_FLOAT, // type + STRIDE, // stride + pointsArray); // pointer + + // Coordinates + gl2.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY); + gl2.glTexCoordPointer( + FLOATS_PER_COORD, // size + GL2.GL_FLOAT, // type + STRIDE, // stride + coordsArray); // pointer + } + + /** + * Makes a view of a float buffer at a certain position. + * + * @param fb Original float buffer + * @param position Index to start view at + * @return Resulting float buffer + * @throws NullPointerException if float buffer is null + * @throws IllegalArgumentException if position is negative + */ + /*@Nonnull*/ + private static FloatBuffer createFloatBufferView(/*@Nonnull*/ final FloatBuffer fb, + /*@Nonnegative*/ final int position) { + + Check.notNull(fb, "Buffer cannot be null"); + Check.argument(position >= 0, "Possition cannot be negative"); + + // Store original position + final int original = fb.position(); + + // Make a view at desired position + fb.position(position); + final FloatBuffer view = fb.asReadOnlyBuffer(); + + // Reset buffer to original position + fb.position(original); + + return view; + } + + @Override + protected void doFlush(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL2 gl2 = gl.getGL2(); + + gl2.glDrawArrays( + GL2.GL_QUADS, // mode + 0, // first + getSizeInVertices()); // count + clear(); + } + + @Override + public void endRendering(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + super.endRendering(gl); + + final GL2 gl2 = gl.getGL2(); + + // Pop state + gl2.glPopClientAttrib(); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL15.java b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL15.java new file mode 100644 index 0000000000..d16c73dc7c --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL15.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; + + + +/** + * {@link QuadPipeline} for use with OpenGL 1.5. + */ +/*@VisibleForTesting*/ +/*@NotThreadSafe*/ +public final class QuadPipelineGL15 extends AbstractQuadPipeline { + + /** + * Number of vertices per primitive. + */ + /*@Nonnegative*/ + private static final int VERTS_PER_PRIM = 4; + + /** + * Number of primitives per quad. + */ + /*@Nonnegative*/ + private static final int PRIMS_PER_QUAD = 1; + + /** + * OpenGL handle to vertex buffer. + */ + /*@Nonnegative*/ + private final int vbo; + + /** + * Constructs a {@link QuadPipelineGL15}. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + /*@VisibleForTesting*/ + public QuadPipelineGL15(/*@Nonnull*/ final GL2 gl) { + + super(VERTS_PER_PRIM, PRIMS_PER_QUAD); + + Check.notNull(gl, "GL cannot be null"); + + this.vbo = createVertexBufferObject(gl, BYTES_PER_BUFFER); + } + + @Override + public void beginRendering(/*@Nonnull*/ final GL gl) { + + super.beginRendering(gl); + + final GL2 gl2 = gl.getGL2(); + + // Change state + gl2.glPushClientAttrib((int) GL2.GL_ALL_CLIENT_ATTRIB_BITS); + gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, vbo); + + // Points + gl2.glEnableClientState(GL2.GL_VERTEX_ARRAY); + gl2.glVertexPointer( + FLOATS_PER_POINT, // size + GL2.GL_FLOAT, // type + STRIDE, // stride + POINT_OFFSET); // offset + + // Coordinates + gl2.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY); + gl2.glTexCoordPointer( + FLOATS_PER_COORD, // size + GL2.GL_FLOAT, // type + STRIDE, // stride + COORD_OFFSET); // offset + } + + @Override + public void dispose(/*@Nonnull*/ final GL gl) { + + super.dispose(gl); + + final GL2 gl2 = gl.getGL2(); + + // Delete the vertex buffer object + final int[] handles = new int[] { vbo }; + gl2.glDeleteBuffers(1, handles, 0); + } + + @Override + protected void doFlush(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL2 gl2 = gl.getGL2(); + + // Upload data + rewind(); + gl2.glBufferSubData( + GL2.GL_ARRAY_BUFFER, // target + 0, // offset + getSizeInBytes(), // size + getData()); // data + + // Draw + gl2.glDrawArrays( + GL2.GL_QUADS, // mode + 0, // first + getSizeInVertices()); // count + + clear(); + } + + @Override + public void endRendering(/*@Nonnull*/ final GL gl) { + + super.endRendering(gl); + + final GL2 gl2 = gl.getGL2(); + + // Restore state + gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0); + gl2.glPopClientAttrib(); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL30.java b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL30.java new file mode 100644 index 0000000000..ee6f3a96c9 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelineGL30.java @@ -0,0 +1,248 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL3; + + +/** + * {@link QuadPipeline} for use with OpenGL 3. + * + *

+ * {@code QuadPipelineGL30} draws quads using OpenGL 3 features. It uses a Vertex Buffer Object to + * store vertices in graphics memory and a Vertex Array Object to quickly switch which vertex + * attributes are enabled. + * + *

+ * Since {@code GL_QUAD} has been deprecated in OpenGL 3, this implementation uses two triangles to + * represent one quad. An alternative implementation using one {@code GL_FAN} per quad was also + * tested, but proved slower in most cases. Apparently the penalty imposed by the extra work + * required by the driver outweighed the benefit of transferring less vertices. + */ +/*@VisibleForTesting*/ +/*@NotThreadSafe*/ +public final class QuadPipelineGL30 extends AbstractQuadPipeline { + + /** + * Name of point attribute in shader program. + */ + /*@Nonnull*/ + private static final String POINT_ATTRIB_NAME = "MCVertex"; + + /** + * Name of texture coordinate attribute in shader program. + */ + /*@Nonnull*/ + private static final String COORD_ATTRIB_NAME = "TexCoord0"; + + /** + * Number of vertices per primitive. + */ + /*@Nonnegative*/ + private static final int VERTS_PER_PRIM = 3; + + /** + * Number of primitives per quad. + */ + /*@Nonnegative*/ + private static final int PRIMS_PER_QUAD = 2; + + /** + * Vertex Buffer Object with vertex data. + */ + /*@Nonnegative*/ + private final int vbo; + + /** + * Vertex Array Object with vertex attribute state. + */ + /*@Nonnegative*/ + private final int vao; + + /** + * Constructs a {@link QuadPipelineGL30}. + * + * @param gl Current OpenGL context + * @param shaderProgram Shader program to render quads with + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if shader program is less than one + */ + /*@VisibleForTesting*/ + public QuadPipelineGL30(/*@Nonnull*/ final GL3 gl, /*@Nonnegative*/ final int shaderProgram) { + + super(VERTS_PER_PRIM, PRIMS_PER_QUAD); + + Check.notNull(gl, "GL cannot be null"); + Check.argument(shaderProgram > 0, "Shader program cannot be less than one"); + + this.vbo = createVertexBufferObject(gl, BYTES_PER_BUFFER); + this.vao = createVertexArrayObject(gl, shaderProgram, vbo); + } + + @Override + public void beginRendering(/*@Nonnull*/ final GL gl) { + + super.beginRendering(gl); + + final GL3 gl3 = gl.getGL3(); + + // Bind the VBO and VAO + gl3.glBindBuffer(GL3.GL_ARRAY_BUFFER, vbo); + gl3.glBindVertexArray(vao); + } + + /** + * Creates a vertex array object for use with the pipeline. + * + * @param gl Current OpenGL context, assumed not null + * @param program OpenGL handle to the shader program, assumed not negative + * @param vbo OpenGL handle to VBO holding vertices, assumed not negative + * @return OpenGL handle to resulting VAO + */ + /*@Nonnegative*/ + private static int createVertexArrayObject(/*@Nonnull*/ final GL3 gl, + /*@Nonnegative*/ final int program, + /*@Nonnegative*/ final int vbo) { + + // Generate + final int[] handles = new int[1]; + gl.glGenVertexArrays(1, handles, 0); + final int vao = handles[0]; + + // Bind + gl.glBindVertexArray(vao); + gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, vbo); + + // Points + final int pointLoc = gl.glGetAttribLocation(program, POINT_ATTRIB_NAME); + if (pointLoc == -1) { + throw new IllegalStateException("Could not find point attribute location!"); + } else { + gl.glEnableVertexAttribArray(pointLoc); + gl.glVertexAttribPointer( + pointLoc, // location + FLOATS_PER_POINT, // number of components + GL3.GL_FLOAT, // type + false, // normalized + STRIDE, // stride + POINT_OFFSET); // offset + } + + // Coords + final int coordLoc = gl.glGetAttribLocation(program, COORD_ATTRIB_NAME); + if (coordLoc != -1) { + gl.glEnableVertexAttribArray(coordLoc); + gl.glVertexAttribPointer( + coordLoc, // location + FLOATS_PER_COORD, // number of components + GL3.GL_FLOAT, // type + false, // normalized + STRIDE, // stride + COORD_OFFSET); // offset + } + + // Unbind + gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, 0); + gl.glBindVertexArray(0); + + return vao; + } + + @Override + public void dispose(/*@Nonnull*/ final GL gl) { + + super.dispose(gl); + + final GL3 gl3 = gl.getGL3(); + + // Delete VBO and VAO + final int[] handles = new int[1]; + handles[0] = vbo; + gl3.glDeleteBuffers(1, handles, 0); + handles[0] = vao; + gl3.glDeleteVertexArrays(1, handles, 0); + } + + @Override + protected void doAddQuad(/*@Nonnull*/ final Quad quad) { + + Check.notNull(quad, "Quad cannot be null"); + + // Add upper-left triangle + addPoint(quad.xr, quad.yt, quad.z); + addCoord(quad.sr, quad.tt); + addPoint(quad.xl, quad.yt, quad.z); + addCoord(quad.sl, quad.tt); + addPoint(quad.xl, quad.yb, quad.z); + addCoord(quad.sl, quad.tb); + + // Add lower-right triangle + addPoint(quad.xr, quad.yt, quad.z); + addCoord(quad.sr, quad.tt); + addPoint(quad.xl, quad.yb, quad.z); + addCoord(quad.sl, quad.tb); + addPoint(quad.xr, quad.yb, quad.z); + addCoord(quad.sr, quad.tb); + } + + @Override + protected void doFlush(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final GL3 gl3 = gl.getGL3(); + + // Upload data + rewind(); + gl3.glBufferSubData( + GL3.GL_ARRAY_BUFFER, // target + 0, // offset + getSizeInBytes(), // size + getData()); // data + + // Draw + gl3.glDrawArrays( + GL3.GL_TRIANGLES, // mode + 0, // first + getSizeInVertices()); // count + clear(); + } + + @Override + public void endRendering(/*@Nonnull*/ final GL gl) { + + super.endRendering(gl); + + final GL3 gl3 = gl.getGL3(); + + // Unbind the VBO and VAO + gl3.glBindBuffer(GL3.GL_ARRAY_BUFFER, 0); + gl3.glBindVertexArray(0); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelines.java b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelines.java new file mode 100644 index 0000000000..9fcab42d09 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/QuadPipelines.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.GLExtensions; +import com.jogamp.opengl.GLProfile; + + +/** + * Utility for working with {@link QuadPipeline}'s. + */ +/*ThreadSafe*/ +public final class QuadPipelines { + + /** + * Prevents instantiation. + */ + private QuadPipelines() { + // pass + } + + /** + * Creates a {@link QuadPipeline} based on the current OpenGL context. + * + * @param gl Current OpenGL context + * @param program Shader program to use, or zero to use default + * @return New quad pipeline for the version of OpenGL in use, not null + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if shader program is negative + * @throws UnsupportedOperationException if GL is unsupported + */ + /*@Nonnull*/ + public QuadPipeline get(/*@Nonnull*/ final GL gl, + /*@Nonnegative*/ final int program) { + + Check.notNull(gl, "Context cannot be null"); + Check.argument(program >= 0, "Program cannot be negative"); + + final GLProfile profile = gl.getGLProfile(); + + if (profile.isGL3()) { + final GL3 gl3 = gl.getGL3(); + return new QuadPipelineGL30(gl3, program); + } else if (profile.isGL2()) { + final GL2 gl2 = gl.getGL2(); + if (gl2.isExtensionAvailable(GLExtensions.VERSION_1_5)) { + return new QuadPipelineGL15(gl2); + } else if (gl2.isExtensionAvailable("GL_VERSION_1_1")) { + return new QuadPipelineGL11(); + } else { + return new QuadPipelineGL10(); + } + } else { + throw new UnsupportedOperationException("Profile currently unsupported"); + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/ShaderLoader.java b/src/jogl/classes/jogamp/opengl/util/awt/text/ShaderLoader.java new file mode 100644 index 0000000000..f6abcee817 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/ShaderLoader.java @@ -0,0 +1,171 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLException; +import com.jogamp.opengl.util.glsl.ShaderUtil; + + +/** + * Utility to load shaders from files, URLs, and strings. + * + *

+ * {@code ShaderLoader} is a simple utility for loading shaders. It takes shaders directly as + * strings. It will create and compile the shaders, and link them together into a program. Both + * compiling and linking are verified. If a problem occurs a {@link GLException} is thrown with + * the appropriate log attached. + * + *

+ * Note it is highly recommended that if the developer passes the strings directly to {@code + * ShaderLoader} that they contain newlines. That way if any errors do occur their line numbers + * will be reported correctly. This means that if the shader is to be embedded in Java code, a + * "\n" should be appended to every line. + */ +/*@VisibleForTesting*/ +/*@ThreadSafe*/ +public final class ShaderLoader { + + /** + * Prevents instantiation. + */ + private ShaderLoader() { + // empty + } + + /** + * Checks that a shader was compiled correctly. + * + * @param gl OpenGL context, assumed not null + * @param shader OpenGL handle to a shader + * @return True if shader was compiled without errors + */ + private static boolean isShaderCompiled(/*@Nonnull*/ final GL2ES2 gl, final int shader) { + return ShaderUtil.isShaderStatusValid(gl, shader, GL2ES2.GL_COMPILE_STATUS, null); + } + + /** + * Checks that a shader program was linked successfully. + * + * @param gl OpenGL context, assumed not null + * @param program OpenGL handle to a shader program + * @return True if program was linked successfully + */ + private static boolean isProgramLinked(/*@Nonnull*/ final GL2ES2 gl, final int program) { + return ShaderUtil.isProgramStatusValid(gl, program, GL2ES2.GL_LINK_STATUS); + } + + /** + * Checks that a shader program was validated successfully. + * + * @param gl OpenGL context, assumed not null + * @param program OpenGL handle to a shader program + * @return True if program was validated successfully + */ + private static boolean isProgramValidated(/*@Nonnull*/ final GL2ES2 gl, final int program) { + return ShaderUtil.isProgramStatusValid(gl, program, GL2ES2.GL_VALIDATE_STATUS); + } + + /** + * Loads a shader program from a pair of strings. + * + * @param gl Current OpenGL context + * @param vss Vertex shader source + * @param fss Fragment shader source + * @return OpenGL handle to the shader program, not negative + * @throws NullPointerException if context or either source is null + * @throws IllegalArgumentException if either source is empty + * @throws GLException if program did not compile, link, or validate successfully + */ + /*@Nonnegative*/ + public static int loadProgram(/*@Nonnull*/ final GL2ES2 gl, + /*@Nonnull*/ final String vss, + /*@Nonnull*/ final String fss) { + + Check.notNull(gl, "GL cannot be null"); + Check.notNull(vss, "Vertex shader source cannot be null"); + Check.notNull(fss, "Fragment shader source cannot be null"); + Check.argument(!vss.isEmpty(), "Vertex shader source cannot be empty"); + Check.argument(!fss.isEmpty(), "Fragment shader source cannot be empty"); + + // Create the shaders + final int vs = loadShader(gl, vss, GL2ES2.GL_VERTEX_SHADER); + final int fs = loadShader(gl, fss, GL2ES2.GL_FRAGMENT_SHADER); + + // Create a program and attach the shaders + final int program = gl.glCreateProgram(); + gl.glAttachShader(program, vs); + gl.glAttachShader(program, fs); + + // Link and validate the program + gl.glLinkProgram(program); + gl.glValidateProgram(program); + if ((!isProgramLinked(gl, program)) || (!isProgramValidated(gl, program))) { + final String log = ShaderUtil.getProgramInfoLog(gl, program); + throw new GLException(log); + } + + // Clean up the shaders + gl.glDeleteShader(vs); + gl.glDeleteShader(fs); + + return program; + } + + /** + * Loads a shader from a string. + * + * @param gl Current OpenGL context, assumed not null + * @param source Source code of the shader as one long string, assumed not null or empty + * @param type Type of shader, assumed valid + * @return OpenGL handle to the shader, not negative + * @throws GLException if a GLSL-capable context is not active or could not compile shader + */ + /*@Nonnegative*/ + private static int loadShader(/*@Nonnull*/ final GL2ES2 gl, + /*@Nonnull*/ final String source, + final int type) { + + // Create and read source + final int shader = gl.glCreateShader(type); + gl.glShaderSource( + shader, // shader handle + 1, // number of strings + new String[] { source }, // array of strings + null); // lengths of strings + + // Compile + gl.glCompileShader(shader); + if (!isShaderCompiled(gl, shader)) { + final String log = ShaderUtil.getShaderInfoLog(gl, shader); + throw new GLException(log); + } + + return shader; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Texture.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Texture.java new file mode 100644 index 0000000000..5ff3283d00 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Texture.java @@ -0,0 +1,179 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL3; + + +/** + * OpenGL texture. + */ +abstract class Texture { + + /** + * ID of internal OpenGL texture. + */ + /*@Nonnegative*/ + protected final int handle; + + /** + * {@code GL_TEXTURE2D}, etc. + */ + protected final int type; + + /** + * True for quality texturing. + */ + protected final boolean mipmap; + + /** + * Constructs a {@link Texture}. + * + * @param gl Current OpenGL context + * @param type Type of texture + * @param mipmap True for quality texturing + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if type is invalid + */ + Texture(/*@Nonnull*/ final GL gl, final int type, final boolean mipmap) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(isValidTextureType(type), "Texture type is invalid"); + + this.handle = generate(gl); + this.type = type; + this.mipmap = mipmap; + } + + /** + * Binds underlying OpenGL texture on a texture unit. + * + * @param gl Current OpenGL context + * @param unit OpenGL enumeration for a texture unit, i.e., {@code GL_TEXTURE0} + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if unit is invalid + */ + void bind(/*@Nonnull*/ final GL gl, final int unit) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(isValidTextureUnit(unit), "Texture unit is invalid"); + + gl.glActiveTexture(unit); + gl.glBindTexture(type, handle); + } + + /** + * Destroys the texture. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + void dispose(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + final int[] handles = new int[] { handle }; + gl.glDeleteTextures(1, handles, 0); + } + + /** + * Generates an OpenGL texture object. + * + * @param gl Current OpenGL context, assumed not null + * @return Handle to the OpenGL texture + */ + private static int generate(/*@Nonnull*/ final GL gl) { + final int[] handles = new int[1]; + gl.glGenTextures(1, handles, 0); + return handles[0]; + } + + /** + * Checks if an integer is a valid OpenGL enumeration for a texture type. + * + * @param type Integer to check + * @return True if type is valid + */ + private static boolean isValidTextureType(final int type) { + switch (type) { + case GL3.GL_TEXTURE_1D: + case GL3.GL_TEXTURE_2D: + case GL3.GL_TEXTURE_3D: + return true; + default: + return false; + } + } + + /** + * Checks if an integer is a valid OpenGL enumeration for a texture unit. + * + * @param unit Integer to check + * @return True if unit is valid + */ + private static boolean isValidTextureUnit(final int unit) { + return (unit >= GL.GL_TEXTURE0) && (unit <= GL.GL_TEXTURE31); + } + + /** + * Updates filter parameters for the texture. + * + * @param gl Current OpenGL context + * @param smooth True to interpolate samples + * @throws NullPointerException if context is null + */ + void setFiltering(/*@Nonnull*/ final GL gl, final boolean smooth) { + + Check.notNull(gl, "GL cannot be null"); + + final int mag; + final int min; + if (smooth) { + mag = GL.GL_LINEAR; + min = mipmap ? GL.GL_LINEAR_MIPMAP_NEAREST : GL.GL_LINEAR; + } else { + mag = GL.GL_NEAREST; + min = mipmap ? GL.GL_NEAREST_MIPMAP_NEAREST : GL.GL_NEAREST; + } + + setParameter(gl, GL.GL_TEXTURE_MAG_FILTER, mag); + setParameter(gl, GL.GL_TEXTURE_MIN_FILTER, min); + } + + /** + * Changes a texture parameter for a 2D texture. + * + * @param gl Current OpenGL context, assumed not null + * @param name Name of the parameter, assumed valid + * @param value Value of the parameter, assumed valid + */ + private void setParameter(/*@Nonnull*/ final GL gl, final int name, final int value) { + gl.glTexParameteri(type, name, value); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Texture2D.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Texture2D.java new file mode 100644 index 0000000000..08ac0442ff --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Texture2D.java @@ -0,0 +1,177 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; + +import java.awt.Rectangle; +import java.nio.ByteBuffer; + + +/** + * Two-dimensional OpenGL texture. + */ +abstract class Texture2D extends Texture { + + // Size on X axis + /*@Nonnegative*/ + protected final int width; + + // Size on Y axis + /*@Nonnegative*/ + protected final int height; + + /** + * Creates a 2D texture. + * + * @param gl Current OpenGL context + * @param width Size of texture on X axis + * @param height Size of texture on Y axis + * @param smooth True to interpolate samples + * @param mipmap True for high quality + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if width or height is negative + */ + Texture2D(/*@Nonnull*/ final GL gl, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + final boolean smooth, + final boolean mipmap) { + + super(gl, GL.GL_TEXTURE_2D, mipmap); + + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + // Copy parameters + this.width = width; + this.height = height; + + // Set up + bind(gl, GL.GL_TEXTURE0); + allocate(gl); + setFiltering(gl, smooth); + } + + /** + * Allocates a 2D texture for use with a backing store. + * + * @param gl Current OpenGL context, assumed not null + * @param width Width of texture, assumed not negative + * @param height Height of texture, assumed not negative + */ + private void allocate(/*@Nonnull*/ final GL gl) { + gl.glTexImage2D( + GL.GL_TEXTURE_2D, // target + 0, // level + getInternalFormat(gl), // internal format + width, // width + height, // height + 0, // border + GL.GL_RGB, // format (unused) + GL.GL_UNSIGNED_BYTE, // type (unused) + null); // pixels + } + + /** + * Determines the proper texture format for an OpenGL context. + * + * @param gl Current OpenGL context + * @return Texture format enumeration for OpenGL context + * @throws NullPointerException if context is null (optional) + */ + protected abstract int getFormat(/*@Nonnull*/ GL gl); + + /** + * Determines the proper internal texture format for an OpenGL context. + * + * @param gl Current OpenGL context + * @return Internal texture format enumeration for OpenGL context + * @throws NullPointerException if context is null (optional) + */ + protected abstract int getInternalFormat(/*@Nonnull*/ GL gl); + + /** + * Updates the texture. + * + *

+ * Copies any areas marked with {@link #mark(int, int, int, int)} from the local image to the + * OpenGL texture. Only those areas will be modified. + * + * @param gl Current OpenGL context + * @param pixels Data of entire image + * @param area Region to update + * @throws NullPointerException if context, pixels, or area is null + */ + void update(/*@Nonnull*/ final GL gl, + /*@Nonnull*/ final ByteBuffer pixels, + /*@Nonnull*/ final Rectangle area) { + + Check.notNull(gl, "GL cannot be null"); + Check.notNull(pixels, "Pixels cannot be null"); + Check.notNull(area, "Area cannot be null"); + + final int parameters[] = new int[4]; + + // Store unpack parameters + gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, parameters, 0); + gl.glGetIntegerv(GL2.GL_UNPACK_SKIP_ROWS, parameters, 1); + gl.glGetIntegerv(GL2.GL_UNPACK_SKIP_PIXELS, parameters, 2); + gl.glGetIntegerv(GL2.GL_UNPACK_ROW_LENGTH, parameters, 3); + + // Change unpack parameters + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); + gl.glPixelStorei(GL2.GL_UNPACK_SKIP_ROWS, area.y); + gl.glPixelStorei(GL2.GL_UNPACK_SKIP_PIXELS, area.x); + gl.glPixelStorei(GL2.GL_UNPACK_ROW_LENGTH, width); + + // Update the texture + gl.glTexSubImage2D( + GL.GL_TEXTURE_2D, // target + 0, // mipmap level + area.x, // x offset + area.y, // y offset + area.width, // width + area.height, // height + getFormat(gl), // format + GL.GL_UNSIGNED_BYTE, // type + pixels); // pixels + + // Reset unpack parameters + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, parameters[0]); + gl.glPixelStorei(GL2.GL_UNPACK_SKIP_ROWS, parameters[1]); + gl.glPixelStorei(GL2.GL_UNPACK_SKIP_PIXELS, parameters[2]); + gl.glPixelStorei(GL2.GL_UNPACK_ROW_LENGTH, parameters[3]); + + // Generate mipmaps + if (mipmap) { + gl.glGenerateMipmap(GL.GL_TEXTURE_2D); + } + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/TextureBackingStore.java b/src/jogl/classes/jogamp/opengl/util/awt/text/TextureBackingStore.java new file mode 100644 index 0000000000..67a317b713 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/TextureBackingStore.java @@ -0,0 +1,428 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.nio.ByteBuffer; + + +/** + * Wrapper for an OpenGL texture that can be drawn into. + * + *

+ * {@code TextureBackingStore} provides the ability to draw into a grayscale texture using Java 2D. + * To increase performance, the backing store maintains a local copy as a {@link BufferedImage}. + * Changes are applied to the image first and then pushed to the texture all at once. + * + *

+ * After creating a backing store, a client simply needs to grab its {@link Graphics2D} and use its + * AWT or Java 2D drawing methods. Then the area that was drawn to should be noted with the {@link + * #mark(int, int, int, int)} method. After everything is drawn, activate the texture using {@link + * #bind(GL, int)} and call {@link #update(GL)} to actually push the dirty regions to the texture. + * If further changes need to made, consider using {@link #clear(int, int, int, int)} to erase old + * data. + * + *

+ * Note that since texturing hasn't changed much, BackingStore is compatible with GL2 or GL3. For + * that reason, it only requests simple GL objects. + */ +/*@NotThreadSafe*/ +final class TextureBackingStore { + + /** + * Size in X direction. + */ + /*@Nonnegative*/ + private final int width; + + /** + * Size in Y direction. + */ + /*@Nonnegative*/ + private final int height; + + /** + * Local copy of texture. + */ + /*@Nonnull*/ + private final BufferedImage image; + + /** + * Java2D utility for drawing into image. + */ + /*@Nonnull*/ + private final Graphics2D g2d; + + /** + * Raw image data for pushing to texture. + */ + /*@Nonnull*/ + private final ByteBuffer pixels; + + /** + * True for quality texturing. + */ + /*@Nonnull*/ + private final boolean mipmap; + + /** + * OpenGL texture on video card. + */ + /*@CheckForNull*/ + private Texture2D texture = null; + + /** + * Area in image not pushed to texture. + */ + /*@CheckForNull*/ + private Rectangle dirtyRegion = null; + + /** + * True to interpolate samples. + */ + private boolean smooth; + + /** + * True if interpolation has changed. + */ + private boolean smoothChanged = false; + + /** + * Constructs a {@link TextureBackingStore}. + * + * @param width Width of backing store + * @param height Height of backing store + * @param font Style of text + * @param antialias True to render smooth edges + * @param subpixel True to use subpixel accuracy + * @param smooth True to interpolate samples + * @param mipmap True for quality texturing + * @throws IllegalArgumentException if width or height is negative + * @throws NullPointerException if font is null + */ + TextureBackingStore(/*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height, + /*@Nonnull*/ final Font font, + final boolean antialias, + final boolean subpixel, + final boolean smooth, + final boolean mipmap) { + + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + Check.notNull(font, "Font cannot be null"); + + this.width = width; + this.height = height; + this.image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + this.g2d = createGraphics(image, font, antialias, subpixel); + this.pixels = getPixels(image); + this.mipmap = mipmap; + this.smooth = smooth; + } + + /** + * Binds the underlying OpenGL texture on a texture unit. + * + * @param gl Current OpenGL context + * @param unit OpenGL enumeration for a texture unit (e.g., {@code GL_TEXTURE0}) + * @throws NullPointerException if context is null + * @throws IllegalArgumentException if unit is invalid + */ + void bind(/*@Nonnull*/ final GL gl, final int unit) { + + Check.notNull(gl, "GL cannot be null"); + Check.argument(unit >= GL.GL_TEXTURE0, "Unit is invalid"); + + ensureTexture(gl); + texture.bind(gl, unit); + } + + /** + * Clears out an area in the backing store. + * + * @param x Position of area's left edge + * @param y Position of area's top edge + * @param width Width of area + * @param height Height of area + * @throws IllegalArgumentException if x, y, width, or height is negative + */ + void clear(/*@Nonnegative*/ final int x, + /*@Nonnegative*/ final int y, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height) { + + Check.argument(x >= 0, "X cannot be negative"); + Check.argument(y >= 0, "Y cannot be negative"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + g2d.setComposite(AlphaComposite.Clear); + g2d.fillRect(x, y, width, height); + g2d.setComposite(AlphaComposite.Src); + } + + /** + * Creates a graphics for a backing store. + * + * @param image Backing store's local copy of data, assumed not null + * @param font Style of text, assumed not null + * @param antialias True to smooth edges + * @param subpixel True to use subpixel accuracy + * @return Graphics2D for rendering into image, not null + */ + /*@Nonnull*/ + private static Graphics2D createGraphics(/*@Nonnull*/ final BufferedImage image, + /*@Nonnull*/ final Font font, + final boolean antialias, + final boolean subpixel) { + + final Graphics2D g2d = image.createGraphics(); + + g2d.setComposite(AlphaComposite.Src); + g2d.setColor(Color.WHITE); + g2d.setFont(font); + g2d.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, + antialias ? + RenderingHints.VALUE_TEXT_ANTIALIAS_ON : + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g2d.setRenderingHint( + RenderingHints.KEY_FRACTIONALMETRICS, + subpixel ? + RenderingHints.VALUE_FRACTIONALMETRICS_ON : + RenderingHints.VALUE_FRACTIONALMETRICS_OFF); + return g2d; + } + + /** + * Releases resources used by the backing store. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + void dispose(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + // Dispose of image + if (image != null) { + image.flush(); + } + + // Dispose of texture + if (texture != null) { + texture.dispose(gl); + } + } + + /** + * Makes sure the texture has been created. + * + * @param gl Current OpenGL context, assumed not null + */ + private void ensureTexture(/*@Nonnull*/ final GL gl) { + if (texture == null) { + texture = new GrayTexture2D(gl, width, height, smooth, mipmap); + } + } + + /** + * Returns Java2D Graphics2D object for drawing into this store. + * + * @return Java2D graphics for drawing into this store, not null + */ + /*@Nonnull*/ + final Graphics2D getGraphics() { + return g2d; + } + + /** + * Returns height of the underlying image and texture. + * + * @return Height of the underlying image, not negative + */ + /*@Nonnegative*/ + final int getHeight() { + return height; + } + + /** + * Returns local copy of texture. + * + * @return Local copy of texture, not null + */ + /*@Nonnull*/ + final BufferedImage getImage() { + return image; + } + + /** + * Retrieves the underlying pixels of a buffered image. + * + * @param image Image with underlying pixel buffer, assumed not null + * @return Pixel data of the image as a byte buffer, not null + * @throws IllegalStateException if image is not stored as bytes + */ + /*@Nonnull*/ + private static ByteBuffer getPixels(/*@Nonnull*/ final BufferedImage image) { + + final DataBuffer db = image.getRaster().getDataBuffer(); + final byte[] arr; + + if (db instanceof DataBufferByte) { + arr = ((DataBufferByte) db).getData(); + } else { + throw new IllegalStateException("Unexpected format in image."); + } + return ByteBuffer.wrap(arr); + } + + /** + * Returns true if texture is interpolating samples. + * + * @return True if texture is interpolating samples + */ + final boolean getUseSmoothing() { + return smooth; + } + + /** + * Returns width of the underlying image and texture. + * + * @return Width of the underlying image, not negative + */ + /*@Nonnegative*/ + final int getWidth() { + return width; + } + + /** + * Marks an area of the backing store to be updated. + * + *

+ * The next time the backing store is updated, the area will be pushed to the texture. + * + * @param x Position of area's left edge + * @param y Position of area's top edge + * @param width Width of area + * @param height Height of area + * @throws IllegalArgumentException if x, y, width, or height is negative + */ + void mark(/*@Nonnegative*/ final int x, + /*@Nonnegative*/ final int y, + /*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height) { + + Check.argument(x >= 0, "X cannot be negative"); + Check.argument(y >= 0, "Y cannot be negative"); + Check.argument(width >= 0, "Width cannot be negative"); + Check.argument(height >= 0, "Height cannot be negative"); + + final Rectangle region = new Rectangle(x, y, width, height); + if (dirtyRegion == null) { + dirtyRegion = region; + } else { + dirtyRegion.add(region); + } + } + + /** + * Specifies whether the texture should interpolate samples. + */ + final void setUseSmoothing(final boolean useSmoothing) { + smoothChanged = (this.smooth != useSmoothing); + this.smooth = useSmoothing; + } + + /** + * Uploads any recently drawn data to the texture. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + void update(/*@Nonnull*/ final GL gl) { + + Check.notNull(gl, "GL cannot be null"); + + // Make sure texture is created + ensureTexture(gl); + + // Check smoothing + if (smoothChanged) { + texture.setFiltering(gl, smooth); + smoothChanged = false; + } + + // Check texture + if (dirtyRegion != null) { + texture.update(gl, pixels, dirtyRegion); + dirtyRegion = null; + } + } + + /** + * Observer of texture backing store events. + */ + interface EventListener { + + /** + * Responds to an event from a texture backing store. + * + * @param type Type of event + * @throws NullPointerException if event type is null (optional) + */ + public void onBackingStoreEvent(/*@Nonnull*/ EventType type); + } + + /** + * Type of event fired from the backing store. + */ + enum EventType { + + /** + * Backing store being resized. + */ + REALLOCATE, + + /** + * Backing store could not be resized. + */ + FAILURE; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/TextureBackingStoreManager.java b/src/jogl/classes/jogamp/opengl/util/awt/text/TextureBackingStoreManager.java new file mode 100644 index 0000000000..f08b09a8de --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/TextureBackingStoreManager.java @@ -0,0 +1,369 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLContext; +import com.jogamp.opengl.util.packrect.BackingStoreManager; +import com.jogamp.opengl.util.packrect.Rect; + +import java.awt.Font; +import java.util.ArrayList; +import java.util.List; + +import jogamp.opengl.util.awt.text.TextureBackingStore.EventListener; +import jogamp.opengl.util.awt.text.TextureBackingStore.EventType; + + +/** + * Handler for allocating and reallocating texture backing stores. + * + *

+ * When a backing store is no longer big enough a new backing store needs to be created to replace + * it. Accordingly, the data from the old backing store should be copied to the new one. + * + *

+ * Throughout this process decisions may need to be made about getting rid of old entries and + * handling failures. {@code TextureBackingStoreManager} handles these issues, although it + * delegates some actions to observers by firing backing store events. + */ +final class TextureBackingStoreManager implements BackingStoreManager { + + /** + * Whether or not texture backing store manager should print debugging information. + */ + private static final boolean DEBUG = false; + + /** + * Observers of backing store events. + */ + /*@Nonnull*/ + private final List listeners = new ArrayList(); + + /** + * Style of text. + */ + /*@Nonnull*/ + private final Font font; + + /** + * True to render smooth edges. + */ + private final boolean antialias; + + /** + * True to use subpixel accuracy. + */ + private final boolean subpixel; + + /** + * True for high quality texturing. + */ + private final boolean mipmap; + + /** + * True to interpolate samples. + */ + private boolean smooth = false; + + /** + * Constructs a {@link TextureBackingStoreManager}. + * + * @param font Style of text + * @param antialias True to render smooth edges + * @param subpixel True to use subpixel accuracy + * @param mipmap True for high quality texturing + * @throws NullPointerException if font is null + */ + TextureBackingStoreManager(/*@Nonnull*/ final Font font, + final boolean antialias, + final boolean subpixel, + final boolean mipmap) { + + Check.notNull(font, "Font cannot be null"); + + this.font = font; + this.antialias = antialias; + this.subpixel = subpixel; + this.mipmap = mipmap; + } + + /** + * Performs an action when a rectangle cannot be added. + * + *

+ * Will happen if the backing store ever reaches its maximum size, which in this case is + * dictated by the maximum texture size supported by the video card. Fires an event of type + * {@link EventType.FAILURE} so that an observer of the backing store can decide what to + * actually do. + * + * @param cause Rectangle that could not be added + * @param attempt Number of times it has been tried so far + * @return False if can do nothing more to free space + * @throws NullPointerException if cause is null + */ + @Override + public boolean additionFailed(/*@Nonnull*/ final Rect cause, final int attempt) { + + Check.notNull(cause, "Cause cannot be null"); + + // Print debugging information + if (DEBUG) { + System.err.println("*** Addition failed! ***"); + } + + // Pass event to observers + fireEvent(EventType.FAILURE); + if (attempt == 0) { + return true; + } + return false; + } + + /** + * Adds an object that wants to be notified of events. + * + *

The observer will be notified when: + * + *

+ * + * @param listener Observer of backing store events + * @throws NullPointerException if listener is null + */ + void addListener(/*@Nonnull*/ final EventListener listener) { + + Check.notNull(listener, "Listener cannot be null"); + + listeners.add(listener); + } + + /** + * Creates a new backing store for the packer. + * + * @param width Width of new backing store + * @param height Height of new backing store + * @return New backing store, not null + * @throws IllegalArgumentException if width or height is negative + */ + /*@Nonnull*/ + @Override + public Object allocateBackingStore(/*@Nonnegative*/ final int width, + /*@Nonnegative*/ final int height) { + + Check.argument(width >= 0, "Width is negative"); + Check.argument(height >= 0, "Height is negative"); + + // Print debugging information + if (DEBUG) { + System.err.printf("Make back store %d x %d\n", width, height); + } + + // Make a new backing store + return new TextureBackingStore( + width, height, + font, + antialias, subpixel, + smooth, mipmap); + } + + /** + * Starts a copy from an old backing store to a new one. + * + * @param obs Backing store being copied from + * @param nbs Backing store being copied to + */ + @Override + public void beginMovement(final Object obs, final Object nbs) { + // empty + } + + /** + * Determines if a backing store can be compacted. + * + * @return True if backing store can be compacted + */ + @Override + public boolean canCompact() { + return true; + } + + /** + * Disposes of a backing store. + * + *

+ * Happens immediately before a backing store needs to be expanded, since the manager will + * actually make a new one. + * + * @param bs Backing store being deleted + * @throws NullPointerException if backing store is null + * @throws ClassCastException if backing store is not a {@code TextureBackingStore} + */ + @Override + public void deleteBackingStore(/*@Nonnull*/ final Object bs) { + + Check.notNull(bs, "Backing store cannot be null"); + + // Dispose the backing store + final GL gl = GLContext.getCurrentGL(); + final TextureBackingStore tbs = (TextureBackingStore) bs; + tbs.dispose(gl); + } + + /** + * Finishes a copy from an old backing store to a new one. + * + *

+ * Marks all of the new backing store dirty. The next time it is updated all of the new data + * will be copied to the texture. + * + * @param obs Backing store being copied from + * @param nbs Backing store being copied to + * @throws NullPointerException if new backing store is null + * @throws ClassCastException if new backing store is not a {@code TextureBackingStore} + */ + @Override + public void endMovement(final Object obs, /*@Nonnull*/ final Object nbs) { + + Check.notNull(nbs, "Backing store cannot be null"); + + // Mark the entire backing store as dirty + final TextureBackingStore ntbs = (TextureBackingStore) nbs; + final int width = ntbs.getWidth(); + final int height = ntbs.getHeight(); + ntbs.mark(0, 0, width, height); + } + + /** + * Sends an event to all listeners. + * + * @param type Type of event to send, assumed not null + */ + private void fireEvent(/*@Nonnull*/ final EventType type) { + for (final EventListener listener : listeners) { + assert listener != null : "addListener rejects null"; + listener.onBackingStoreEvent(type); + } + } + + /** + * Returns true if is interpolating samples. + */ + final boolean getUseSmoothing() { + return smooth; + } + + /** + * Copies part of an old backing store to a new one. + * + *

+ * This method is normally called when a backing store runs out of room and needs to be + * resized, but it can also be called when a backing store is compacted. In that case {@code + * obs} will be equal to {@code nbs}. This situation may need to be handled differently. + * + * @param obs Old backing store being copied from + * @param ol Area of old backing store to copy + * @param nbs New backing store being copied to + * @param nl Area of new backing store to copy to + * @throws NullPointerException if either backing store or area is null + * @throws ClassCastException if either backing store is not the right type + */ + @Override + public void move(/*@Nonnull*/ final Object obs, + /*@Nonnull*/ final Rect ol, + /*@Nonnull*/ final Object nbs, + /*@Nonnull*/ final Rect nl) { + + Check.notNull(obs, "Old backing store cannot be null"); + Check.notNull(ol, "Old location cannot be null"); + Check.notNull(nbs, "New backing store cannot be null"); + Check.notNull(nl, "New location cannot be null"); + + final TextureBackingStore otbs = (TextureBackingStore) obs; + final TextureBackingStore ntbs = (TextureBackingStore) nbs; + + if (otbs == ntbs) { + otbs.getGraphics().copyArea( + ol.x(), ol.y(), + ol.w(), ol.h(), + nl.x() - ol.x(), + nl.y() - ol.y()); + } else { + ntbs.getGraphics().drawImage( + otbs.getImage(), + nl.x(), nl.y(), + nl.x() + nl.w(), + nl.y() + nl.h(), + ol.x(), ol.y(), + ol.x() + ol.w(), + ol.y() + ol.h(), + null); + } + } + + /** + * Performs an action when a store needs to be expanded. + * + *

+ * Fires an event of type {@link EventType.REALLOCATE} so that an observer of the backing store + * can decide what to actually do. This will only happen on the first attempt. + * + * @param cause Rectangle that is being added + * @param attempt Number of times it has been tried so far + * @return True if packer should retry addition + * @throws NullPointerException if cause is null + */ + @Override + public boolean preExpand(/*@Nonnull*/ final Rect cause, final int attempt) { + + Check.notNull(cause, "Cause cannot be null"); + + // Print debugging information + if (DEBUG) { + System.err.println("In preExpand: attempt number " + attempt); + } + + // Pass event to observers + if (attempt == 0) { + fireEvent(EventType.REALLOCATE); + return true; + } + return false; + } + + /** + * Changes whether texture should interpolate samples. + * + * @param useSmoothing True if texture should interpolate + */ + final void setUseSmoothing(final boolean useSmoothing) { + this.smooth = useSmoothing; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/UnicodeGlyphProducer.java b/src/jogl/classes/jogamp/opengl/util/awt/text/UnicodeGlyphProducer.java new file mode 100644 index 0000000000..e1a0e29fcf --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/UnicodeGlyphProducer.java @@ -0,0 +1,166 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.util.awt.TextRenderer.RenderDelegate; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.util.List; + + +/** + * {@link GlyphProducer} for creating glyphs of all characters in the basic unicode block. + */ +/*@NotThreadSafe*/ +final class UnicodeGlyphProducer extends AbstractGlyphProducer { + + /** + * Storage for glyphs. + */ + /*@Nonnull*/ + private final GlyphMap glyphMap = new GlyphMap(); + + /** + * Constructs a {@link UnicodeGlyphProducer}. + * + * @param font Font glyphs will be made of + * @param rd Object for controlling rendering + * @param frc Details on how to render fonts + * @throws NullPointerException if font, render delegate, or font render context is null + */ + UnicodeGlyphProducer(/*@Nonnull*/ final Font font, + /*@Nonnull*/ final RenderDelegate rd, + /*@Nonnull*/ final FontRenderContext frc) { + super(font, rd, frc); + } + + @Override + public void clearGlyphs() { + glyphMap.clear(); + } + + /** + * Creates a single glyph from text with a complex layout. + * + * @param str Text with a complex layout + * @param gv Glyph vector of entire text + * @return Read-only pointer to list of glyphs valid until next call + * @throws NullPointerException if string is null + * @throws NullPointerException if glyph vector is null + */ + /*@Nonnull*/ + private List createComplexGlyph(/*@Nonnull*/ final String str, + /*@Nonnull*/ final GlyphVector gv) { + + Check.notNull(str, "String cannot be null"); + Check.notNull(gv, "Glyph vector be null"); + + clearOutput(); + + // Create the glyph and add it to output + Glyph glyph = glyphMap.get(str); + if (glyph == null) { + glyph = new Glyph(str, gv); + measure(glyph); + glyphMap.put(str, glyph); + } + addToOutput(glyph); + + return getOutput(); + } + + /*@Nonnull*/ + @Override + public Glyph createGlyph(final char c) { + Glyph glyph = glyphMap.get(c); + if (glyph == null) { + glyph = createGlyphImpl(c); + } + return glyph; + } + + /*@Nonnull*/ + private Glyph createGlyphImpl(final char c) { + + // Create a glyph from the glyph vector + final GlyphVector gv = createGlyphVector(c); + final Glyph glyph = new Glyph(c, gv); + + // Measure and store it + measure(glyph); + glyphMap.put(c, glyph); + + return glyph; + } + + /*@Nonnull*/ + @Override + public List createGlyphs(/*@Nonnull*/ final String str) { + + Check.notNull(str, "String cannot be null"); + + if (!hasComplexCharacters(str)) { + return createSimpleGlyphs(str); + } else { + final GlyphVector gv = createGlyphVector(str); + return isComplex(gv) ? createComplexGlyph(str, gv) : createSimpleGlyphs(str); + } + } + + /** + * Creates multiple glyphs from text with a simple layout. + * + * @param str Text with a simple layout + * @return Read-only pointer to list of glyphs valid until next call + * @throws NullPointerException if string is null + */ + /*@Nonnull*/ + private List createSimpleGlyphs(/*@Nonnull*/ final String str) { + + Check.notNull(str, "String cannot be null"); + + clearOutput(); + + // Create the glyphs and add them to the output + final int len = str.length(); + for (int i = 0; i < len; ++i) { + final char c = str.charAt(i); + final Glyph glyph = createGlyph(c); + addToOutput(glyph); + } + + return getOutput(); + } + + @Override + public void removeGlyph(/*@CheckForNull*/ final Glyph glyph) { + glyphMap.remove(glyph); + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Uniform.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Uniform.java new file mode 100644 index 0000000000..2c47d7256a --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Uniform.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL2GL3; + + +/** + * Uniform variable in a shader. + */ +abstract class Uniform { + + /** + * Index of uniform in shader. + */ + /*@Nonnegative*/ + final int location; + + /** + * True if local value should be pushed. + */ + boolean dirty; + + /** + * Constructs a {@link Uniform}. + * + * @param gl Current OpenGL context + * @param program OpenGL handle to shader program + * @param name Name of the uniform in shader source code + * @throws NullPointerException if context or name is null + * @throws IllegalArgumentException if program is negative + */ + Uniform(/*@Nonnull*/ final GL2GL3 gl, + /*@Nonnegative*/ final int program, + /*@Nonnull*/ final String name) { + + Check.notNull(gl, "GL cannot be null"); + Check.notNull(name, "Name cannot be null"); + Check.argument(program >= 0, "Program cannot be negative"); + + location = gl.glGetUniformLocation(program, name); + if (location == -1) { + throw new IllegalStateException("Could not find uniform in program"); + } + } + + /** + * Pushes the local value to the shader program. + * + * @param gl Current OpenGL context + * @throws NullPointerException if context is null + */ + abstract void update(/*@Nonnull*/ GL2GL3 gl); +} diff --git a/src/jogl/classes/jogamp/opengl/util/awt/text/Vec4Uniform.java b/src/jogl/classes/jogamp/opengl/util/awt/text/Vec4Uniform.java new file mode 100644 index 0000000000..2e06cd3e62 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/awt/text/Vec4Uniform.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.util.awt.text; + +import com.jogamp.opengl.GL2GL3; + + +/** + * Uniform for a {@code vec4}. + */ +/*@NotThreadSafe*/ +final class Vec4Uniform extends Uniform { + + /** + * Local copy of vector values. + */ + /*@Nonnull*/ + final float[] value = new float[4]; + + /** + * Constructs a uniform vector. + * + * @param gl2gl3 Current OpenGL context + * @param program OpenGL handle to shader program + * @param name Name of the uniform in shader source code + * @throws NullPointerException if context is null + */ + Vec4Uniform(/*@Nonnull*/ final GL2GL3 gl, + /*@Nonnegative*/ final int program, + /*@Nonnull*/ final String name) { + super(gl, program, name); + } + + @Override + void update(/*@Nonnull*/ final GL2GL3 gl) { + Check.notNull(gl, "GL cannot be null"); + gl.glUniform4fv(location, 1, value, 0); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/AbstractGL2EventAdapter.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/AbstractGL2EventAdapter.java new file mode 100644 index 0000000000..a742e5ebf2 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/AbstractGL2EventAdapter.java @@ -0,0 +1,121 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GLAutoDrawable; + + +/** + * Skeletal implementation of {@link GLEventListener} for OpenGL 2. + */ +abstract class AbstractGL2EventAdapter extends GLEventAdapter { + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void display(final GLAutoDrawable drawable) { + final GL2 gl = drawable.getGL().getGL2(); + doDisplay(gl); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void dispose(final GLAutoDrawable drawable) { + final GL2 gl = drawable.getGL().getGL2(); + doDispose(gl); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void reshape(final GLAutoDrawable drawable, + final int x, final int y, + final int width, final int height) { + final GL2 gl = drawable.getGL().getGL2(); + doReshape(gl, x, y, width, height); + } + + //----------------------------------------------------------------- + // Hooks + // + + /** + * Handles initialize events from a drawable. + * + * @param gl OpenGL context from drawable + * @throws NullPointerException if context is null + */ + protected void doInit(GL2 gl) { + // pass + } + + /** + * Handles display events from a drawable. + * + * @param gl OpenGL context from drawable + * @throws NullPointerException if context is null + */ + protected void doDisplay(GL2 gl) { + // pass + } + + /** + * Handles dispose events from a drawable. + * + * @param gl OpenGL context from drawable + * @throws NullPointerException if context is null + */ + protected void doDispose(GL2 gl) { + // pass + } + + /** + * Handles reshape events from a drawable. + * + * @param gl OpenGL context from drawable + * @param x Left side of viewport + * @param y Top of viewport + * @param width Width of viewport + * @param height Height of viewport + * @throws NullPointerException if context is null + */ + protected void doReshape(GL2 gl, int x, int y, int width, int height) { + // pass + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/AbstractGL3EventAdapter.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/AbstractGL3EventAdapter.java new file mode 100644 index 0000000000..7b29a6d48a --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/AbstractGL3EventAdapter.java @@ -0,0 +1,121 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.GLAutoDrawable; + + +/** + * Skeletal implementation of {@link GLEventListener} for OpenGL 3. + */ +abstract class AbstractGL3EventAdapter extends GLEventAdapter { + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void display(final GLAutoDrawable drawable) { + final GL3 gl = drawable.getGL().getGL3(); + doDisplay(gl); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void dispose(final GLAutoDrawable drawable) { + final GL3 gl = drawable.getGL().getGL3(); + doDispose(gl); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void reshape(final GLAutoDrawable drawable, + final int x, final int y, + final int width, final int height) { + final GL3 gl = drawable.getGL().getGL3(); + doReshape(gl, x, y, width, height); + } + + //----------------------------------------------------------------- + // Hooks + // + + /** + * Handles initialize events from a drawable. + * + * @param gl OpenGL context from drawable + * @throws NullPointerException if context is null + */ + protected void doInit(GL3 gl) { + // pass + } + + /** + * Handles display events from a drawable. + * + * @param gl OpenGL context from drawable + * @throws NullPointerException if context is null + */ + protected void doDisplay(GL3 gl) { + // pass + } + + /** + * Handles dispose events from a drawable. + * + * @param gl OpenGL context from drawable + * @throws NullPointerException if context is null + */ + protected void doDispose(GL3 gl) { + // pass + } + + /** + * Handles reshape events from a drawable. + * + * @param gl OpenGL context from drawable + * @param x Left side of viewport + * @param y Top of viewport + * @param width Width of viewport + * @param height Height of viewport + * @throws NullPointerException if context is null + */ + protected void doReshape(GL3 gl, int x, int y, int width, int height) { + // pass + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/DebugGL2EventAdapter.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/DebugGL2EventAdapter.java new file mode 100644 index 0000000000..ead1529efb --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/DebugGL2EventAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.DebugGL2; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GLAutoDrawable; + + +/** + * Skeletal implementation of {@link GLEventListener} for OpenGL 2 with debugging enabled. + */ +abstract class DebugGL2EventAdapter extends AbstractGL2EventAdapter { + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void init(final GLAutoDrawable drawable) { + final GL2 gl = drawable.getGL().getGL2(); + final DebugGL2 dgl = new DebugGL2(gl); + drawable.setGL(dgl); + doInit(dgl); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/DebugGL3EventAdapter.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/DebugGL3EventAdapter.java new file mode 100644 index 0000000000..8dfe06175d --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/DebugGL3EventAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.DebugGL3; +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.GLAutoDrawable; + + +/** + * Skeletal implementation of {@link GLEventListener} for OpenGL 3 with debugging enabled. + */ +abstract class DebugGL3EventAdapter extends AbstractGL3EventAdapter { + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void init(final GLAutoDrawable drawable) { + final GL3 gl = drawable.getGL().getGL3(); + final DebugGL3 dgl = new DebugGL3(gl); + drawable.setGL(dgl); + doInit(dgl); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GL2EventAdapter.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GL2EventAdapter.java new file mode 100644 index 0000000000..fff6218590 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GL2EventAdapter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GLAutoDrawable; + + +/** + * Skeletal implementation of {@link GLEventListener} for OpenGL 2 with debugging. + */ +abstract class GL2EventAdapter extends AbstractGL2EventAdapter { + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void init(final GLAutoDrawable drawable) { + final GL2 gl = drawable.getGL().getGL2(); + doInit(gl); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GL3EventAdapter.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GL3EventAdapter.java new file mode 100644 index 0000000000..f73ae53fea --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GL3EventAdapter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.GLAutoDrawable; + + +/** + * Skeletal implementation of {@link GLEventListener} for OpenGL 3 with debugging enabled. + */ +abstract class GL3EventAdapter extends AbstractGL3EventAdapter { + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + */ + @Override + public final void init(final GLAutoDrawable drawable) { + final GL3 gl = drawable.getGL().getGL3(); + doInit(gl); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GLCanvasFactory.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GLCanvasFactory.java new file mode 100644 index 0000000000..6b254b43c5 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GLCanvasFactory.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.awt.GLCanvas; + +import java.awt.Dimension; + + +/** + * Utility for making OpenGL canvases. + */ +class GLCanvasFactory { + + // Whether or not to make double-buffered canvases + private boolean doubleBuffered = true; + + // Width of canvases that are made + private int width = 512; + + // Height of canvases that are made + private int height = 512; + + /** + * Changes whether to make double-buffered canvases. + * + * @param doubleBuffered true to make double-buffered canvases + */ + public void setDoubleBuffered(final boolean doubleBuffered) { + this.doubleBuffered = doubleBuffered; + } + + /** + * Changes the width of canvases that are made. + * + * @param width Width of canvases that are made + * @throws IllegalArgumentException if width is zero or negative + */ + public void setWidth(final int width) { + if (width <= 0) { + throw new IllegalArgumentException("Width is zero or negative!"); + } + this.width = width; + } + + /** + * Changes the height of canvases that are made. + * + * @param height Height of canvases that are made + * @throws IllegalArgumentException if height is zero or negative + */ + public void setHeight(final int height) { + if (height <= 0) { + throw new IllegalArgumentException("Height is zero or negative!"); + } + this.height = height; + } + + /** + * Creates a canvas with profile options. + * + * @param profile GLProfile to use, e.g. "GL2" or "GL3" + * @return Canvas supporting requested profile + * @throws NullPointerException if profile is null + */ + public GLCanvas createGLCanvas(final String profile) { + + // Make capabilities + final GLProfile glProfile = GLProfile.get(profile); + final GLCapabilities glCapabilities = new GLCapabilities(glProfile); + glCapabilities.setDoubleBuffered(doubleBuffered); + + // Make canvas + final GLCanvas canvas = new GLCanvas(glCapabilities); + canvas.setPreferredSize(new Dimension(width, height)); + return canvas; + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GLEventAdapter.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GLEventAdapter.java new file mode 100644 index 0000000000..0d854f9b35 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/GLEventAdapter.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLEventListener; + + +/** + * Skeletal implementation of {@link GLEventListener}. + */ +abstract class GLEventAdapter implements GLEventListener { + + /** + * Handles initialize events. + * + * @param drawable Surface being initialized + * @throws NullPointerException if drawable is null + */ + @Override + public void init(final GLAutoDrawable drawable) { + // pass + } + + /** + * Handles display events. + * + * @param drawable Surface being drawn to + * @throws NullPointerException if drawable is null + */ + @Override + public void display(final GLAutoDrawable drawable) { + // pass + } + + /** + * Handles dispose events. + * + * @param drawable Surface being disposed of + * @throws NullPointerException if drawable is null + */ + @Override + public void dispose(final GLAutoDrawable drawable) { + // pass + } + + /** + * Handles window resizing events and invokes {@link #doReshape}. + * + * @param drawable Surface being reshaped + * @param x Left side of viewport + * @param y Top of viewport + * @param width Width of viewport + * @param height Height of viewport + * @throws NullPointerException if drawable is null + */ + @Override + public void reshape(final GLAutoDrawable drawable, final int x, int y, final int width, int height) { + // pass + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestGlyphRendererAWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestGlyphRendererAWT.java new file mode 100644 index 0000000000..9f4ad408f5 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestGlyphRendererAWT.java @@ -0,0 +1,327 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.awt.GLCanvas; +import com.jogamp.opengl.util.texture.TextureCoords; + +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.nio.ByteBuffer; + +import jogamp.opengl.util.awt.text.Glyph; +import jogamp.opengl.util.awt.text.GlyphRenderer; +import jogamp.opengl.util.awt.text.GlyphRendererGL2; +import jogamp.opengl.util.awt.text.GlyphRendererGL3; + +import javax.swing.JFrame; + +import org.junit.Test; + + +/** + * Test for {@link GlyphRenderer}. + */ +public class TestGlyphRendererAWT { + + // Amount of time to wait before closing window + private static final int WAIT_TIME = 1000; + + // Font to render + private static Font FONT = new Font("Sans-serif", Font.PLAIN, 256); + + // Glyph to render + private final Glyph glyph = createGlyph('G'); + + // Glyph renderer implementation + private GlyphRenderer glyphRenderer; + + // Utility for making canvases + private final GLCanvasFactory canvasFactory = new GLCanvasFactory(); + + /** + * Test for {@link GlyphRendererGL2}. + * + *

Performs the following: + *

+ * + *

Results: + *

+ * + *

Notes: + *

+ */ + @Test + public void testGlyphRendererGL2() throws Exception { + + final JFrame frame = new JFrame("testGlyphRendererGL2"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL2"); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL2EventAdapter() { + + @Override + public void doInit(final GL2 gl) { + + // Set up glyph renderer + glyphRenderer = new GlyphRendererGL2(); + glyphRenderer.addListener(new GlyphRenderer.EventListener() { + @Override + public void onGlyphRendererEvent(GlyphRenderer.EventType type) { + if (type == GlyphRenderer.EventType.AUTOMATIC_FLUSH) { + System.out.println("Automatically flushing!"); + } + } + }); + + // Set up texture + final GlyphTexture texture = new GlyphTexture(gl); + texture.bind(gl); + texture.upload(gl); + } + + @Override + public void doDisplay(final GL2 gl) { + + // View + gl.glClearColor(0, 1, 1, 1); + gl.glClear(GL.GL_COLOR_BUFFER_BIT); + + // Draw glyph + final TextureCoords coordinates = new TextureCoords(0, 1, 1, 0); + glyphRenderer.beginRendering(gl, true, 512, 512, true); + glyphRenderer.setColor(1, 0, 1, 1); // magenta + glyphRenderer.drawGlyph(gl, glyph, 40, 80, 0, 1.0f, coordinates); + glyphRenderer.setColor(1, 1, 0, 1); // yellow + glyphRenderer.drawGlyph(gl, glyph, 260, 80, 0, 1.0f, coordinates); + glyphRenderer.endRendering(gl); + } + }); + TestRunner.run(frame, WAIT_TIME); + } + + /** + * Test case for {@link GlyphRendererGL3}. + * + *

Performs the following: + *

+ * + *

Results: + *

+ * + *

Notes: + *

+ */ + @Test + public void testGlyphRendererGL3() throws Exception { + + final JFrame frame = new JFrame("testGlyphRendererGL3"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL3"); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL3EventAdapter() { + + @Override + public void doInit(final GL3 gl) { + + // Set up glyph renderer + glyphRenderer = new GlyphRendererGL3(gl); + glyphRenderer.addListener(new GlyphRenderer.EventListener() { + public void onGlyphRendererEvent(GlyphRenderer.EventType type) { + if (type == GlyphRenderer.EventType.AUTOMATIC_FLUSH) { + System.out.println("Automatically flushing!"); + } + } + }); + + // Set up texture + final GlyphTexture texture = new GlyphTexture(gl); + texture.bind(gl); + texture.upload(gl); + } + + @Override + public void doDisplay(final GL3 gl) { + + // Clear + gl.glClearColor(0, 1, 1, 1); + gl.glClear(GL3.GL_COLOR_BUFFER_BIT); + + // Draw glyph + final TextureCoords coordinates = new TextureCoords(0, 1, 1, 0); + glyphRenderer.beginRendering(gl, true, 512, 512, true); + glyphRenderer.setColor(1, 0, 1, 1); // magenta + glyphRenderer.drawGlyph(gl, glyph, 40, 80, 0, 1.0f, coordinates); + glyphRenderer.setColor(1, 1, 0, 1); // yellow + glyphRenderer.drawGlyph(gl, glyph, 260, 80, 0, 1.0f, coordinates); + glyphRenderer.endRendering(gl); + } + }); + TestRunner.run(frame, WAIT_TIME); + } + + //----------------------------------------------------------------- + // Helpers + // + + /** + * Returns a glyph for a character. + */ + private static Glyph createGlyph(final char c) { + final GlyphVector gv = createGlyphVector(c); + final Glyph glyph = new Glyph(c, gv); + glyph.width = 54; + glyph.height = 72; + return glyph; + } + + /** + * Returns a glyph vector for a character. + */ + private static GlyphVector createGlyphVector(final char c) { + final FontRenderContext frc = createFontRenderContext(); + final char[] chars = new char[] { c }; + return FONT.createGlyphVector(frc, chars); + } + + /** + * Returns a blank font render context. + */ + private static FontRenderContext createFontRenderContext() { + return new FontRenderContext(new AffineTransform(), false, false); + } + + //----------------------------------------------------------------- + // Nested classes + // + + /** + * Texture with a giant 'G' in it. + */ + private static class GlyphTexture { + + // Width and height of the texture + private static final int SIZE = 256; + + // Internal OpenGL identifier for texture + private int handle; + + GlyphTexture(final GL gl) { + handle = createHandle(gl); + } + + void bind(final GL gl) { + gl.glActiveTexture(GL.GL_TEXTURE0); + gl.glBindTexture(GL.GL_TEXTURE_2D, handle); + } + + void upload(final GL gl) { + + final BufferedImage image = createBufferedImage(); + final Graphics2D graphics = image.createGraphics(); + final ByteBuffer buffer = createByteBuffer(image); + + // Draw character into image + graphics.setFont(FONT); + graphics.drawString("G", 0.09f * SIZE, 0.80f * SIZE); + + // Upload it to the texture + final GLProfile profile = gl.getGLProfile(); + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); + gl.glTexImage2D( + GL.GL_TEXTURE_2D, + 0, + profile.isGL3() ? GL3.GL_RED : GL2.GL_INTENSITY, + SIZE, SIZE, + 0, + profile.isGL3() ? GL3.GL_RED : GL2.GL_LUMINANCE, + GL.GL_UNSIGNED_BYTE, + buffer); + setParameter(gl, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); + setParameter(gl, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); + } + + private static BufferedImage createBufferedImage() { + return new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_BYTE_GRAY); + } + + private static ByteBuffer createByteBuffer(final BufferedImage image) { + final Raster raster = image.getRaster(); + final DataBufferByte dbb = (DataBufferByte) raster.getDataBuffer(); + final byte[] array = dbb.getData(); + return ByteBuffer.wrap(array); + } + + private static int createHandle(final GL gl) { + final int[] handles = new int[1]; + gl.glGenTextures(1, handles, 0); + return handles[0]; + } + + private static void setParameter(final GL gl, final int name, final int value) { + gl.glTexParameteri(GL.GL_TEXTURE_2D, name, value); + } + } +} + diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestQuadPipelineAWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestQuadPipelineAWT.java new file mode 100644 index 0000000000..10b61716b3 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestQuadPipelineAWT.java @@ -0,0 +1,321 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import static org.junit.Assert.*; + +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.awt.GLCanvas; + +import javax.swing.JFrame; + +import jogamp.opengl.util.awt.text.Quad; +import jogamp.opengl.util.awt.text.QuadPipeline; +import jogamp.opengl.util.awt.text.QuadPipelineGL10; +import jogamp.opengl.util.awt.text.QuadPipelineGL11; +import jogamp.opengl.util.awt.text.QuadPipelineGL15; +import jogamp.opengl.util.awt.text.QuadPipelineGL30; +import jogamp.opengl.util.awt.text.ShaderLoader; + +import org.junit.Test; + + +/** + * Test for {@link QuadPipeline}. + */ +public class TestQuadPipelineAWT { + + // Amount of time to wait before closing the window + private static final int WAIT_TIME = 1000; + + // Shader code for OpenGL 3 tests + private static final String VERT_SOURCE; + private static final String FRAG_SOURCE; + + // Shader program for OpenGL 3 test + private int program; + + // Pipeline to render with + private QuadPipeline pipeline; + + // Quad to render + private Quad quad; + + // Utility for making canvases + private final GLCanvasFactory canvasFactory = new GLCanvasFactory(); + + /** + * Initializes static fields. + */ + static { + VERT_SOURCE = + "#version 140\n" + + "uniform mat4 MVPMatrix=mat4(1);\n" + + "in vec4 MCVertex;\n" + + "void main() {\n" + + " gl_Position = MVPMatrix * MCVertex;\n" + + "}\n"; + FRAG_SOURCE = + "#version 140\n" + + "uniform vec4 Color=vec4(1,1,1,1);\n" + + "out vec4 FragColor;\n" + + "void main() {\n" + + " FragColor = Color;\n" + + "}\n"; + } + + /** + * Test case for {@link QuadPipelineGL10}. + * + *

Performs the following: + *

+ * + *

Results: + *

+ */ + @Test + public void testQuadPipelineGL10() throws Exception { + + final JFrame frame = new JFrame("testQuadPipelineGL10"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL2"); + + frame.add(canvas); + canvas.addGLEventListener(new GL2EventAdapter() { + + @Override + public void doInit(final GL2 gl) { + pipeline = new QuadPipelineGL10(); + quad = createQuad(); + } + + @Override + public void doDisplay(final GL2 gl) { + + // View + gl.glViewport(0, 0, 512, 512); + gl.glClearColor(0, 1, 1, 1); + gl.glClear(GL2.GL_COLOR_BUFFER_BIT); + + // Create geometry + pipeline.beginRendering(gl); + pipeline.addQuad(gl, quad); + pipeline.endRendering(gl); + } + }); + TestRunner.run(frame, WAIT_TIME); + } + + /** + * Test case for {@link QuadPipelineGL11}. + * + *

Performs the following: + *

+ * + *

Results: + *

+ */ + @Test + public void testQuadPipelineGL11() throws Exception { + + final JFrame frame = new JFrame("testQuadPipelineGL11"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL2"); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL2EventAdapter() { + + @Override + public void doInit(final GL2 gl) { + pipeline = new QuadPipelineGL11(); + quad = createQuad(); + } + + @Override + public void doDisplay(final GL2 gl) { + + // View + gl.glViewport(0, 0, 512, 512); + gl.glClearColor(0, 1, 1, 1); + gl.glClear(GL2.GL_COLOR_BUFFER_BIT); + + // Create geometry + pipeline.beginRendering(gl); + pipeline.addQuad(gl, quad); + pipeline.endRendering(gl); + } + }); + TestRunner.run(frame, WAIT_TIME); + } + + /** + * Test case for {@link QuadPipelineGL15}. + * + *

Performs the following: + *

+ * + *

Results: + *

+ */ + @Test + public void testQuadPipelineGL15() throws Exception { + + final JFrame frame = new JFrame("testQuadPipelineGL15"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL2"); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL2EventAdapter() { + + @Override + public void doInit(final GL2 gl) { + pipeline = new QuadPipelineGL15(gl); + quad = createQuad(); + } + + @Override + public void doDisplay(final GL2 gl) { + + // View + gl.glViewport(0, 0, 512, 512); + gl.glClearColor(0, 1, 1, 1); + gl.glClear(GL2.GL_COLOR_BUFFER_BIT); + + // Create geometry + pipeline.beginRendering(gl); + pipeline.addQuad(gl, quad); + pipeline.endRendering(gl); + } + }); + TestRunner.run(frame, WAIT_TIME); + } + + /** + * Test case for {@link QuadPipelineGL30}. + * + *

Performs the following: + *

+ * + *

Results: + *

+ */ + @Test + public void testQuadPipelineGL30() throws Exception { + + final JFrame frame = new JFrame("testQuadPipelineGL30"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL3"); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL3EventAdapter() { + + @Override + public void doInit(final GL3 gl) { + program = createProgram(gl); + pipeline = new QuadPipelineGL30(gl, program); + quad = createQuad(); + } + + @Override + public void doDisplay(final GL3 gl) { + + // View + gl.glViewport(0, 0, 512, 512); + gl.glClearColor(0, 1, 1, 1); + gl.glClear(GL3.GL_COLOR_BUFFER_BIT); + + // Create geometry + gl.glUseProgram(program); + pipeline.beginRendering(gl); + pipeline.addQuad(gl, quad); + pipeline.endRendering(gl); + } + }); + TestRunner.run(frame, WAIT_TIME); + } + + //----------------------------------------------------------------- + // Helpers + // + + /** + * Returns a shader program for use with pipeline. + */ + private static int createProgram(final GL2ES2 gl) { + int program = -1; + try { + program = ShaderLoader.loadProgram(gl, VERT_SOURCE, FRAG_SOURCE); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + return program; + } + + /** + * Returns a quad structure for use with pipeline. + */ + private static Quad createQuad() { + final Quad q = new Quad(); + q.xl = -0.5f; + q.xr = +0.5f; + q.yb = -0.5f; + q.yt = +0.5f; + q.z = 0; + q.sl = 0; + q.sr = 1; + q.tb = 0; + q.tt = 1; + return q; + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestRunner.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestRunner.java new file mode 100644 index 0000000000..f8b1c4d3fd --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestRunner.java @@ -0,0 +1,243 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + + +/** + * Utility for using windows in JUnit tests. + * + *

TestRunner makes using windows in unit tests easy by performing + * several tasks for the developer. + * + *

Most importantly, it allows the developer to open a window and have JUnit + * wait for it to close. Normally this is not a concern with a normal + * main method because the JVM waits for the Swing thread to finish + * before exiting. However, the JUnit runner exits as soon it gets to the end + * of the test methods. Therefore windows in JUnit tests are closed + * immediately, or never show up in the first place. + * + *

TestRunner solves the problem by providing a static run + * method that shows the frame and then calls + * wait on it. When a test launches the window in this manner, the JUnit + * thread pauses. Then when the frame is closed, instead of exiting, it + * hides itself and calls {@link notifyAll}. The paused JUnit thread therefore + * restarts, disposes of the frame, and continues onto the next test. + * + *

Besides this important functionality, JTestFrame also + *

+ */ +class TestRunner { + + // Default amount of time to wait before closing window + private static final long DEFAULT_WAIT_TIME = 1000; + + // Initial location on screen + private static final int DEFAULT_LOCATION_X = 50; + private static final int DEFAULT_LOCATION_Y = 50; + + // Key that closes the window + private static final int CLOSE_KEY = KeyEvent.VK_ESCAPE; + + /** + * Prevents instantiation. + */ + private TestRunner() { + ; + } + + /** + * Shows frame, waits for it to close, then disposes of it. + * + * @param frame Window to use + * @throws NullPointerException if frame is null + */ + public static void run(final JFrame frame) { + run(frame, DEFAULT_WAIT_TIME); + } + + /** + * Shows frame, waits for it a certain amount of time, then disposes of it. + * + * @param frame Window to use + * @param timeout Amount of milliseconds to wait (<= 0 for indefinitely) + * @throws NullPointerException if frame is null + */ + public static void run(final JFrame frame, final long timeout) { + + // Change behavior + frame.setLocation(DEFAULT_LOCATION_X, DEFAULT_LOCATION_Y); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + // Add listeners + frame.addKeyListener(new KeyObserver(frame)); + frame.addWindowListener(new WindowObserver(frame)); + + // Show the frame + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (!hasExplicitSize(frame)) { + frame.pack(); + } + frame.setVisible(true); + } + }); + + // Wait for it to close + synchronized (frame) { + try { + if (timeout < 0) { + frame.wait(); + } else { + frame.wait(timeout); + } + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while waiting!"); + } + } + + // Hide and dispose of it + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + frame.setVisible(false); + frame.dispose(); + } + }); + } + + //----------------------------------------------------------------- + // Helpers + // + + /** + * Checks if a frame has an explicit size. + * + * @param frame Frame to check + * @return true if an explicit size has been set + * @throws NullPointerException if frame is null + */ + private static boolean hasExplicitSize(final JFrame frame) { + final int width = frame.getWidth(); + final int height = frame.getHeight(); + return (width > 0) || (height > 0); + } + + /** + * Wakes any threads waiting on object. + * + * @param object Object that other objects are waiting on + * @throws NullPointerException if object is null + */ + private static void wakeWaitingThreads(final Object object) { + synchronized (object) { + object.notifyAll(); + } + } + + //----------------------------------------------------------------- + // Nested classes + // + + /** + * Listener for key events. + */ + private static class KeyObserver extends KeyAdapter { + + // Frame to listen to + private final JFrame frame; + + /** + * Constructs a key observer from a frame. + * + * @param frame Frame to listen to + * @throws AssertionError if frame is null + */ + public KeyObserver(final JFrame frame) { + assert (frame != null); + this.frame = frame; + } + + /** + * Wakes threads when {@link #CLOSE_KEY} key is released. + * + * @param event Event with key status + * @throws NullPointerException if event is null + */ + public void keyReleased(final KeyEvent event) { + switch (event.getKeyCode()) { + case CLOSE_KEY: + wakeWaitingThreads(frame); + break; + } + } + } + + /** + * Listener for window events. + */ + private static class WindowObserver extends WindowAdapter { + + // Frame to listen to + private final JFrame frame; + + /** + * Constructs a window observer from a frame. + * + * @param frame Frame to listen to + * @throws AssertionError if frame is null + */ + public WindowObserver(final JFrame frame) { + assert (frame != null); + this.frame = frame; + } + + /** + * Wakes threads when window is closing. + * + * @param event Event with window status + * @throws NullPointerException if event is null + */ + @Override + public void windowClosing(final WindowEvent event) { + wakeWaitingThreads(frame); + } + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestTextRendererAWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestTextRendererAWT.java new file mode 100644 index 0000000000..a6553e3e24 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/text/TestTextRendererAWT.java @@ -0,0 +1,293 @@ +/* + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.jogl.awt.text; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.awt.GLCanvas; +import com.jogamp.opengl.util.FPSAnimator; +import com.jogamp.opengl.util.awt.TextRenderer; + +import java.awt.Color; +import java.awt.Font; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import javax.swing.JFrame; + +import org.junit.Assert; +import org.junit.Test; + + +/** + * Test for {@link TextRenderer}. + */ +public class TestTextRendererAWT { + + // Font to render with + private static final Font FONT = new Font("Monospace", Font.PLAIN, 110); + + // Time to wait before closing window + private static final int LONG_WAIT_TIME = 4000; + private static final int SHORT_WAIT_TIME = 1000; + + // Random collection of words to render + private final WordBank wordBank = new WordBank(); + + // Instance to render with + private TextRenderer textRenderer = null; + + // Utility for making canvases + private final GLCanvasFactory canvasFactory = new GLCanvasFactory(); + + /** + * Ensures the text renderer can draw text properly with an OpenGL 2 context. + * + *

Performs the following: + *

+ */ + @Test + public void testWithGL2() { + + final JFrame frame = new JFrame("testWithGL2"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL2"); + final FPSAnimator animator = new FPSAnimator(canvas, 1); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL2EventAdapter() { + + @Override + public void doInit(final GL2 gl) { + textRenderer = new TextRenderer(FONT); + } + + @Override + public void doDisplay(final GL2 gl) { + + // Clear + gl.glClearColor(0.85f, 0.85f, 0.85f, 1); + gl.glClear(GL.GL_COLOR_BUFFER_BIT); + + // Draw + final int width = canvas.getWidth(); + final int height = canvas.getHeight(); + textRenderer.beginRendering(width, height); + textRenderer.setColor(Color.RED); + textRenderer.draw(wordBank.next(), getX(width), getY(height)); + textRenderer.setColor(Color.GREEN); + textRenderer.draw(wordBank.next(), getX(width), getY(height)); + textRenderer.endRendering(); + } + + @Override + public void doDispose(final GL2 gl) { + textRenderer.dispose(); + } + }); + animator.start(); + TestRunner.run(frame, LONG_WAIT_TIME); + animator.stop(); + } + + /** + * Ensures the text renderer can draw text properly with an OpenGL 3 context. + * + *

Performs the following: + *

+ */ + @Test + public void testWithGL3() { + + final JFrame frame = new JFrame("testWithGL3"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL3"); + final FPSAnimator animator = new FPSAnimator(canvas, 1); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL3EventAdapter() { + + @Override + public void doInit(final GL3 gl) { + textRenderer = new TextRenderer(FONT); + } + + @Override + public void doDisplay(final GL3 gl) { + + // Clear + gl.glClearColor(0.85f, 0.85f, 0.85f, 1); + gl.glClear(GL.GL_COLOR_BUFFER_BIT); + + // Draw + final int width = canvas.getWidth(); + final int height = canvas.getHeight(); + textRenderer.beginRendering(width, height); + textRenderer.setColor(Color.RED); + textRenderer.draw(wordBank.next(), getX(width), getY(height)); + textRenderer.setColor(Color.GREEN); + textRenderer.draw(wordBank.next(), getX(width), getY(height)); + textRenderer.endRendering(); + } + + @Override + public void doDispose(final GL3 gl) { + textRenderer.dispose(); + } + }); + animator.start(); + TestRunner.run(frame, LONG_WAIT_TIME); + animator.stop(); + } + + /** + * Ensures the user can request whether vertex arrays are used before beginRendering is called. + */ + @Test + public void testSetUseVertexArraysBeforeBeginRendering() { + + final JFrame frame = new JFrame("testSetUseVertexArraysBeforeBeginRendering"); + final GLCanvas canvas = canvasFactory.createGLCanvas("GL2"); + + frame.add(canvas); + canvas.addGLEventListener(new DebugGL2EventAdapter() { + + @Override + public void doInit(final GL2 gl) { + textRenderer = new TextRenderer(FONT); + Assert.assertTrue(textRenderer.getUseVertexArrays()); + textRenderer.setUseVertexArrays(false); + Assert.assertFalse(textRenderer.getUseVertexArrays()); + } + + @Override + public void doDisplay(final GL2 gl) { + final int width = canvas.getWidth(); + final int height = canvas.getHeight(); + textRenderer.beginRendering(width, height); + textRenderer.endRendering(); + Assert.assertFalse(textRenderer.getUseVertexArrays()); + } + + @Override + public void doDispose(final GL2 gl) { + textRenderer.dispose(); + } + }); + TestRunner.run(frame, SHORT_WAIT_TIME); + } + + //----------------------------------------------------------------- + // Helpers + // + + /** + * Returns random X coordinate in left-hand side of window. + */ + private static int getX(final int width) { + return (int) (Math.random() * width / 2); + } + + /** + * Returns random Y coordinate in window. + */ + private static int getY(final int height) { + return (int) (Math.random() * height); + } +} + + +/** + * Random collection of words. + */ +class WordBank { + + // Available words + private List words; + + // Random number generator + private Random random; + + // Number of words in bank + private int size; + + /** + * Constructs a word bank. + */ + public WordBank() { + this.words = createWords(); + this.random = new Random(37); + this.size = words.size(); + } + + /** + * Returns next random word from the word bank. + */ + public String next() { + return words.get((int) (random.nextDouble() * size)); + } + + /** + * Makes a collection of words. + */ + private static List createWords() { + final List words = new ArrayList(); + words.add("Lüké"); + words.add("Anàkin"); + words.add("Kit"); + words.add("Plô"); + words.add("Qüi-Gônn"); + words.add("Obi-Wàn"); + words.add("Lüminàrà"); + words.add("Mäcé"); + words.add("Yôdà"); + words.add("Ahsôkà"); + words.add("Ki Adi"); + words.add("Bàrriss"); + words.add("Ackbàr"); + words.add("Chéwbàccà"); + words.add("Jàbbà"); + words.add("C-3PO"); + words.add("Bôbà"); + words.add("R2-D2"); + words.add("Hàns"); + words.add("Léià"); + words.add("Shààk Ti"); + return words; + } +}