JOGL v2.6.0-rc-20250706
JOGL, High-Performance Graphics Binding for Java™ (public API).
TextRenderer.java
Go to the documentation of this file.
1/*
2 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
3 * Copyright (c) 2010 JogAmp Community. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * - Redistribution of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * - Redistribution in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * Neither the name of Sun Microsystems, Inc. or the names of
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * This software is provided "AS IS," without a warranty of any kind. ALL
21 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
22 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
23 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
24 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
25 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
26 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
27 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
28 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
29 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
30 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
31 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32 *
33 * You acknowledge that this software is not designed or intended for use
34 * in the design, construction, operation or maintenance of any nuclear
35 * facility.
36 *
37 * Sun gratefully acknowledges that this software was originally authored
38 * and developed by Kenneth Bradley Russell and Christopher John Kline.
39 */
40package com.jogamp.opengl.util.awt;
41
42import com.jogamp.common.nio.Buffers;
43import com.jogamp.common.util.InterruptSource;
44import com.jogamp.common.util.PropertyAccess;
45import com.jogamp.nativewindow.NativeSurface;
46import com.jogamp.nativewindow.ScalableSurface;
47import com.jogamp.opengl.util.*;
48import com.jogamp.opengl.util.packrect.*;
49import com.jogamp.opengl.util.texture.*;
50
51import java.awt.AlphaComposite;
52import java.awt.Color;
53
54// For debugging purposes
55import java.awt.EventQueue;
56import java.awt.Font;
57import java.awt.Frame;
58import java.awt.Graphics2D;
59import java.awt.Image;
60import java.awt.Point;
61import java.awt.RenderingHints;
62import java.awt.event.*;
63import java.awt.font.*;
64import java.awt.geom.*;
65import java.nio.*;
66import java.text.*;
67import java.util.*;
68
69import com.jogamp.opengl.*;
70import com.jogamp.opengl.fixedfunc.GLPointerFunc;
71import com.jogamp.opengl.glu.*;
72import com.jogamp.opengl.awt.*;
73
74import jogamp.opengl.Debug;
75
76
77/** Renders bitmapped Java 2D text into an OpenGL window with high
78 performance, full Unicode support, and a simple API. Performs
79 appropriate caching of text rendering results in an OpenGL texture
80 internally to avoid repeated font rasterization. The caching is
81 completely automatic, does not require any user intervention, and
82 has no visible controls in the public API. <P>
83
84 Using the {@link TextRenderer TextRenderer} is simple. Add a
85 "<code>TextRenderer renderer;</code>" field to your {@link
86 com.jogamp.opengl.GLEventListener GLEventListener}. In your {@link
87 com.jogamp.opengl.GLEventListener#init init} method, add:
88
89 <PRE>
90 renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36));
91 </PRE>
92
93 <P> In the {@link com.jogamp.opengl.GLEventListener#display display} method of your
94 {@link com.jogamp.opengl.GLEventListener GLEventListener}, add:
95 <PRE>
96 renderer.beginRendering(drawable.getWidth(), drawable.getHeight());
97 // optionally set the color
98 renderer.setColor(1.0f, 0.2f, 0.2f, 0.8f);
99 renderer.draw("Text to draw", xPosition, yPosition);
100 // ... more draw commands, color changes, etc.
101 renderer.endRendering();
102 </PRE>
103
104 Unless you are sharing textures and display lists between OpenGL
105 contexts, you do not need to call the {@link #dispose dispose}
106 method of the TextRenderer; the OpenGL resources it uses
107 internally will be cleaned up automatically when the OpenGL
108 context is destroyed. <P>
109
110 <b>Note</b> that the TextRenderer may cause the vertex and texture
111 coordinate array buffer bindings to change, or to be unbound. This
112 is important to note if you are using Vertex Buffer Objects (VBOs)
113 in your application. <P>
114
115 Internally, the renderer uses a rectangle packing algorithm to
116 pack both glyphs and full Strings' rendering results (which are
117 variable size) onto a larger OpenGL texture. The internal backing
118 store is maintained using a {@link
119 com.jogamp.opengl.util.awt.TextureRenderer TextureRenderer}. A least
120 recently used (LRU) algorithm is used to discard previously
121 rendered strings; the specific algorithm is undefined, but is
122 currently implemented by flushing unused Strings' rendering
123 results every few hundred rendering cycles, where a rendering
124 cycle is defined as a pair of calls to {@link #beginRendering
125 beginRendering} / {@link #endRendering endRendering}.
126
127 @author John Burkey
128 @author Kenneth Russell
129*/
130public class TextRenderer {
131 private static final boolean DEBUG;
132
133 static {
134 Debug.initSingleton();
135 DEBUG = PropertyAccess.isPropertyDefined("jogl.debug.TextRenderer", true);
136 }
137
138 // These are occasionally useful for more in-depth debugging
139 private static final boolean DISABLE_GLYPH_CACHE = false;
140 private static final boolean DRAW_BBOXES = false;
141
142 static final int kSize = 256;
143
144 // Every certain number of render cycles, flush the strings which
145 // haven't been used recently
146 private static final int CYCLES_PER_FLUSH = 100;
147
148 // The amount of vertical dead space on the backing store before we
149 // force a compaction
150 private static final float MAX_VERTICAL_FRAGMENTATION = 0.7f;
151 static final int kQuadsPerBuffer = 100;
152 static final int kCoordsPerVertVerts = 3;
153 static final int kCoordsPerVertTex = 2;
154 static final int kVertsPerQuad = 4;
155 static final int kTotalBufferSizeVerts = kQuadsPerBuffer * kVertsPerQuad;
156 static final int kTotalBufferSizeCoordsVerts = kQuadsPerBuffer * kVertsPerQuad * kCoordsPerVertVerts;
157 static final int kTotalBufferSizeCoordsTex = kQuadsPerBuffer * kVertsPerQuad * kCoordsPerVertTex;
158 static final int kTotalBufferSizeBytesVerts = kTotalBufferSizeCoordsVerts * 4;
159 static final int kTotalBufferSizeBytesTex = kTotalBufferSizeCoordsTex * 4;
160 static final int kSizeInBytes_OneVertices_VertexData = kCoordsPerVertVerts * 4;
161 static final int kSizeInBytes_OneVertices_TexData = kCoordsPerVertTex * 4;
162 private final Font font;
163 private final boolean antialiased;
164 private final boolean useFractionalMetrics;
165
166 // Whether we're attempting to use automatic mipmap generation support
167 private boolean mipmap;
168 private RectanglePacker packer;
169 private boolean haveMaxSize;
170 private final RenderDelegate renderDelegate;
171 private TextureRenderer cachedBackingStore;
172 private Graphics2D cachedGraphics;
173 private FontRenderContext cachedFontRenderContext;
174 private final Map<String, Rect> stringLocations = new HashMap<String, Rect>();
175 private final GlyphProducer mGlyphProducer;
176
177 private int numRenderCycles;
178
179 // Need to keep track of whether we're in a beginRendering() /
180 // endRendering() cycle so we can re-enter the exact same state if
181 // we have to reallocate the backing store
182 private boolean inBeginEndPair;
183 private boolean isOrthoMode;
184 private int beginRenderingWidth;
185 private int beginRenderingHeight;
186 private boolean beginRenderingDepthTestDisabled;
187
188 // For resetting the color after disposal of the old backing store
189 private boolean haveCachedColor;
190 private float cachedR;
191 private float cachedG;
192 private float cachedB;
193 private float cachedA;
194 private Color cachedColor;
195 private boolean needToResetColor;
196
197 // For debugging only
198 private Frame dbgFrame;
199
200 // Debugging purposes only
201 private boolean debugged;
202 Pipelined_QuadRenderer mPipelinedQuadRenderer;
203
204 //emzic: added boolean flag
205 private boolean useVertexArrays = true;
206
207 //emzic: added boolean flag
208 private boolean isExtensionAvailable_GL_VERSION_1_5;
209 private boolean checkFor_isExtensionAvailable_GL_VERSION_1_5;
210
211 // Whether GL_LINEAR filtering is enabled for the backing store
212 private boolean smoothing = true;
213
214 /** Creates a new TextRenderer with the given font, using no
215 antialiasing or fractional metrics, and the default
216 RenderDelegate. Equivalent to <code>TextRenderer(font, false,
217 false)</code>.
218
219 @param font the font to render with
220 */
221 public TextRenderer(final Font font) {
222 this(font, false, false, null, false);
223 }
224
225 /** Creates a new TextRenderer with the given font, using no
226 antialiasing or fractional metrics, and the default
227 RenderDelegate. If <CODE>mipmap</CODE> is true, attempts to use
228 OpenGL's automatic mipmap generation for better smoothing when
229 rendering the TextureRenderer's contents at a distance.
230 Equivalent to <code>TextRenderer(font, false, false)</code>.
231
232 @param font the font to render with
233 @param mipmap whether to attempt use of automatic mipmap generation
234 */
235 public TextRenderer(final Font font, final boolean mipmap) {
236 this(font, false, false, null, mipmap);
237 }
238
239 /** Creates a new TextRenderer with the given Font, specified font
240 properties, and default RenderDelegate. The
241 <code>antialiased</code> and <code>useFractionalMetrics</code>
242 flags provide control over the same properties at the Java 2D
243 level. No mipmap support is requested. Equivalent to
244 <code>TextRenderer(font, antialiased, useFractionalMetrics,
245 null)</code>.
246
247 @param font the font to render with
248 @param antialiased whether to use antialiased fonts
249 @param useFractionalMetrics whether to use fractional font
250 metrics at the Java 2D level
251 */
252 public TextRenderer(final Font font, final boolean antialiased,
253 final boolean useFractionalMetrics) {
254 this(font, antialiased, useFractionalMetrics, null, false);
255 }
256
257 /** Creates a new TextRenderer with the given Font, specified font
258 properties, and given RenderDelegate. The
259 <code>antialiased</code> and <code>useFractionalMetrics</code>
260 flags provide control over the same properties at the Java 2D
261 level. The <code>renderDelegate</code> provides more control
262 over the text rendered. No mipmap support is requested.
263
264 @param font the font to render with
265 @param antialiased whether to use antialiased fonts
266 @param useFractionalMetrics whether to use fractional font
267 metrics at the Java 2D level
268 @param renderDelegate the render delegate to use to draw the
269 text's bitmap, or null to use the default one
270 */
271 public TextRenderer(final Font font, final boolean antialiased,
272 final boolean useFractionalMetrics, final RenderDelegate renderDelegate) {
273 this(font, antialiased, useFractionalMetrics, renderDelegate, false);
274 }
275
276 /** Creates a new TextRenderer with the given Font, specified font
277 properties, and given RenderDelegate. The
278 <code>antialiased</code> and <code>useFractionalMetrics</code>
279 flags provide control over the same properties at the Java 2D
280 level. The <code>renderDelegate</code> provides more control
281 over the text rendered. If <CODE>mipmap</CODE> is true, attempts
282 to use OpenGL's automatic mipmap generation for better smoothing
283 when rendering the TextureRenderer's contents at a distance.
284
285 @param font the font to render with
286 @param antialiased whether to use antialiased fonts
287 @param useFractionalMetrics whether to use fractional font
288 metrics at the Java 2D level
289 @param renderDelegate the render delegate to use to draw the
290 text's bitmap, or null to use the default one
291 @param mipmap whether to attempt use of automatic mipmap generation
292 */
293 public TextRenderer(final Font font, final boolean antialiased,
294 final boolean useFractionalMetrics, RenderDelegate renderDelegate,
295 final boolean mipmap) {
296
298 if (surface instanceof ScalableSurface) {
299 // DPI scaling for surface
300 float[] surfaceScale = new float[2];
301 ((ScalableSurface) surface).getCurrentSurfaceScale(surfaceScale);
302 float newSize = font.getSize() * surfaceScale[0];
303 this.font = font.deriveFont(newSize);
304 } else {
305 this.font = font;
306 }
307
308 this.antialiased = antialiased;
309 this.useFractionalMetrics = useFractionalMetrics;
310 this.mipmap = mipmap;
311
312 // FIXME: consider adjusting the size based on font size
313 // (it will already automatically resize if necessary)
314 packer = new RectanglePacker(new Manager(), kSize, kSize);
315
316 if (renderDelegate == null) {
317 renderDelegate = new DefaultRenderDelegate();
318 }
319
320 this.renderDelegate = renderDelegate;
321
322 mGlyphProducer = new GlyphProducer(font.getNumGlyphs());
323 }
324
325 /** Returns the bounding rectangle of the given String, assuming it
326 was rendered at the origin. See {@link #getBounds(CharSequence)
327 getBounds(CharSequence)}. */
328 public Rectangle2D getBounds(final String str) {
329 return getBounds((CharSequence) str);
330 }
331
332 /** Returns the bounding rectangle of the given CharSequence,
333 assuming it was rendered at the origin. The coordinate system of
334 the returned rectangle is Java 2D's, with increasing Y
335 coordinates in the downward direction. The relative coordinate
336 (0, 0) in the returned rectangle corresponds to the baseline of
337 the leftmost character of the rendered string, in similar
338 fashion to the results returned by, for example, {@link
339 java.awt.font.GlyphVector#getVisualBounds}. Most applications
340 will use only the width and height of the returned Rectangle for
341 the purposes of centering or justifying the String. It is not
342 specified which Java 2D bounds ({@link
343 java.awt.font.GlyphVector#getVisualBounds getVisualBounds},
344 {@link java.awt.font.GlyphVector#getPixelBounds getPixelBounds},
345 etc.) the returned bounds correspond to, although every effort
346 is made to ensure an accurate bound. */
347 public Rectangle2D getBounds(final CharSequence str) {
348 // FIXME: this should be more optimized and use the glyph cache
349 final Rect r = stringLocations.get(str);
350
351 if (r != null) {
352 final TextData data = (TextData) r.getUserData();
353
354 // Reconstitute the Java 2D results based on the cached values
355 return new Rectangle2D.Double(-data.origin().x, -data.origin().y,
356 r.w(), r.h());
357 }
358
359 // Must return a Rectangle compatible with the layout algorithm --
360 // must be idempotent
361 return normalize(renderDelegate.getBounds(str, font,
363 }
364
365 /** Returns the Font this renderer is using. */
366 public Font getFont() {
367 return font;
368 }
369
370 /** Returns a FontRenderContext which can be used for external
371 text-related size computations. This object should be considered
372 transient and may become invalidated between {@link
373 #beginRendering beginRendering} / {@link #endRendering
374 endRendering} pairs. */
375 public FontRenderContext getFontRenderContext() {
376 if (cachedFontRenderContext == null) {
377 cachedFontRenderContext = getGraphics2D().getFontRenderContext();
378 }
379
380 return cachedFontRenderContext;
381 }
382
383 /** Begins rendering with this {@link TextRenderer TextRenderer}
384 into the current OpenGL drawable, pushing the projection and
385 modelview matrices and some state bits and setting up a
386 two-dimensional orthographic projection with (0, 0) as the
387 lower-left coordinate and (width, height) as the upper-right
388 coordinate. Binds and enables the internal OpenGL texture
389 object, sets the texture environment mode to GL_MODULATE, and
390 changes the current color to the last color set with this
391 TextRenderer via {@link #setColor setColor}. This method
392 disables the depth test and is equivalent to
393 beginRendering(width, height, true).
394
395 @param width the width of the current on-screen OpenGL drawable
396 @param height the height of the current on-screen OpenGL drawable
397 @throws com.jogamp.opengl.GLException If an OpenGL context is not current when this method is called
398 */
399 public void beginRendering(final int width, final int height) throws GLException {
400 beginRendering(width, height, true);
401 }
402
403 /** Begins rendering with this {@link TextRenderer TextRenderer}
404 into the current OpenGL drawable, pushing the projection and
405 modelview matrices and some state bits and setting up a
406 two-dimensional orthographic projection with (0, 0) as the
407 lower-left coordinate and (width, height) as the upper-right
408 coordinate. Binds and enables the internal OpenGL texture
409 object, sets the texture environment mode to GL_MODULATE, and
410 changes the current color to the last color set with this
411 TextRenderer via {@link #setColor setColor}. Disables the depth
412 test if the disableDepthTest argument is true.
413
414 @param width the width of the current on-screen OpenGL drawable
415 @param height the height of the current on-screen OpenGL drawable
416 @param disableDepthTest whether to disable the depth test
417 @throws GLException If an OpenGL context is not current when this method is called
418 */
419 public void beginRendering(final int width, final int height, final boolean disableDepthTest)
420 throws GLException {
421 beginRendering(true, width, height, disableDepthTest);
422 }
423
424 /** Begins rendering of 2D text in 3D with this {@link TextRenderer
425 TextRenderer} into the current OpenGL drawable. Assumes the end
426 user is responsible for setting up the modelview and projection
427 matrices, and will render text using the {@link #draw3D draw3D}
428 method. This method pushes some OpenGL state bits, binds and
429 enables the internal OpenGL texture object, sets the texture
430 environment mode to GL_MODULATE, and changes the current color
431 to the last color set with this TextRenderer via {@link
432 #setColor setColor}.
433
434 @throws GLException If an OpenGL context is not current when this method is called
435 */
436 public void begin3DRendering() throws GLException {
437 beginRendering(false, 0, 0, false);
438 }
439
440 /** Changes the current color of this TextRenderer to the supplied
441 one. The default color is opaque white.
442
443 @param color the new color to use for rendering text
444 @throws GLException If an OpenGL context is not current when this method is called
445 */
446 public void setColor(final Color color) throws GLException {
447 final boolean noNeedForFlush = (haveCachedColor && (cachedColor != null) &&
448 color.equals(cachedColor));
449
450 if (!noNeedForFlush) {
451 flushGlyphPipeline();
452 }
453
454 getBackingStore().setColor(color);
455 haveCachedColor = true;
456 cachedColor = color;
457 }
458
459 /** Changes the current color of this TextRenderer to the supplied
460 one, where each component ranges from 0.0f - 1.0f. The alpha
461 component, if used, does not need to be premultiplied into the
462 color channels as described in the documentation for {@link
463 com.jogamp.opengl.util.texture.Texture Texture}, although
464 premultiplied colors are used internally. The default color is
465 opaque white.
466
467 @param r the red component of the new color
468 @param g the green component of the new color
469 @param b the blue component of the new color
470 @param a the alpha component of the new color, 0.0f = completely
471 transparent, 1.0f = completely opaque
472 @throws GLException If an OpenGL context is not current when this method is called
473 */
474 public void setColor(final float r, final float g, final float b, final float a)
475 throws GLException {
476 final boolean noNeedForFlush = (haveCachedColor && (cachedColor == null) &&
477 (r == cachedR) && (g == cachedG) && (b == cachedB) &&
478 (a == cachedA));
479
480 if (!noNeedForFlush) {
481 flushGlyphPipeline();
482 }
483
484 getBackingStore().setColor(r, g, b, a);
485 haveCachedColor = true;
486 cachedR = r;
487 cachedG = g;
488 cachedB = b;
489 cachedA = a;
490 cachedColor = null;
491 }
492
493 /** Draws the supplied CharSequence at the desired location using
494 the renderer's current color. The baseline of the leftmost
495 character is at position (x, y) specified in OpenGL coordinates,
496 where the origin is at the lower-left of the drawable and the Y
497 coordinate increases in the upward direction.
498
499 @param str the string to draw
500 @param x the x coordinate at which to draw
501 @param y the y coordinate at which to draw
502 @throws GLException If an OpenGL context is not current when this method is called
503 */
504 public void draw(final CharSequence str, final int x, final int y) throws GLException {
505 draw3D(str, x, y, 0, 1);
506 }
507
508 /** Draws the supplied String at the desired location using the
509 renderer's current color. See {@link #draw(CharSequence, int,
510 int) draw(CharSequence, int, int)}. */
511 public void draw(final String str, final int x, final int y) throws GLException {
512 draw3D(str, x, y, 0, 1);
513 }
514
515 /** Draws the supplied CharSequence at the desired 3D location using
516 the renderer's current color. The baseline of the leftmost
517 character is placed at position (x, y, z) in the current
518 coordinate system.
519
520 @param str the string to draw
521 @param x the x coordinate at which to draw
522 @param y the y coordinate at which to draw
523 @param z the z coordinate at which to draw
524 @param scaleFactor a uniform scale factor applied to the width and height of the drawn rectangle
525 @throws GLException If an OpenGL context is not current when this method is called
526 */
527 public void draw3D(final CharSequence str, final float x, final float y, final float z,
528 final float scaleFactor) {
529 internal_draw3D(str, x, y, z, scaleFactor);
530 }
531
532 /** Draws the supplied String at the desired 3D location using the
533 renderer's current color. See {@link #draw3D(CharSequence,
534 float, float, float, float) draw3D(CharSequence, float, float,
535 float, float)}. */
536 public void draw3D(final String str, final float x, final float y, final float z, final float scaleFactor) {
537 internal_draw3D(str, x, y, z, scaleFactor);
538 }
539
540 /** Returns the pixel width of the given character. */
541 public float getCharWidth(final char inChar) {
542 return mGlyphProducer.getGlyphPixelWidth(inChar);
543 }
544
545 /** Causes the TextRenderer to flush any internal caches it may be
546 maintaining and draw its rendering results to the screen. This
547 should be called after each call to draw() if you are setting
548 OpenGL state such as the modelview matrix between calls to
549 draw(). */
550 public void flush() {
551 flushGlyphPipeline();
552 }
553
554 /** Ends a render cycle with this {@link TextRenderer TextRenderer}.
555 Restores the projection and modelview matrices as well as
556 several OpenGL state bits. Should be paired with {@link
557 #beginRendering beginRendering}.
558
559 @throws GLException If an OpenGL context is not current when this method is called
560 */
561 public void endRendering() throws GLException {
562 endRendering(true);
563 }
564
565 /** Ends a 3D render cycle with this {@link TextRenderer TextRenderer}.
566 Restores several OpenGL state bits. Should be paired with {@link
567 #begin3DRendering begin3DRendering}.
568
569 @throws GLException If an OpenGL context is not current when this method is called
570 */
571 public void end3DRendering() throws GLException {
572 endRendering(false);
573 }
574
575 /** Disposes of all resources this TextRenderer is using. It is not
576 valid to use the TextRenderer after this method is called.
577
578 @throws GLException If an OpenGL context is not current when this method is called
579 */
580 public void dispose() throws GLException {
581 if( null != mPipelinedQuadRenderer ) {
582 mPipelinedQuadRenderer.dispose();
583 }
584 packer.dispose();
585 packer = null;
586 cachedBackingStore = null;
587 cachedGraphics = null;
588 cachedFontRenderContext = null;
589
590 if (dbgFrame != null) {
591 dbgFrame.dispose();
592 }
593 }
594
595 //----------------------------------------------------------------------
596 // Internals only below this point
597 //
598
599 private static Rectangle2D preNormalize(final Rectangle2D src) {
600 // Need to round to integer coordinates
601 // Also give ourselves a little slop around the reported
602 // bounds of glyphs because it looks like neither the visual
603 // nor the pixel bounds works perfectly well
604 final int minX = (int) Math.floor(src.getMinX()) - 1;
605 final int minY = (int) Math.floor(src.getMinY()) - 1;
606 final int maxX = (int) Math.ceil(src.getMaxX()) + 1;
607 final int maxY = (int) Math.ceil(src.getMaxY()) + 1;
608 return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
609 }
610
611
612 private Rectangle2D normalize(final Rectangle2D src) {
613 // Give ourselves a boundary around each entity on the backing
614 // store in order to prevent bleeding of nearby Strings due to
615 // the fact that we use linear filtering
616
617 // NOTE that this boundary is quite heuristic and is related
618 // to how far away in 3D we may view the text --
619 // heuristically, 1.5% of the font's height
620 final int boundary = (int) Math.max(1, 0.015 * font.getSize());
621
622 return new Rectangle2D.Double((int) Math.floor(src.getMinX() - boundary),
623 (int) Math.floor(src.getMinY() - boundary),
624 (int) Math.ceil(src.getWidth() + 2 * boundary),
625 (int) Math.ceil(src.getHeight()) + 2 * boundary);
626 }
627
628 private TextureRenderer getBackingStore() {
629 final TextureRenderer renderer = (TextureRenderer) packer.getBackingStore();
630
631 if (renderer != cachedBackingStore) {
632 // Backing store changed since last time; discard any cached Graphics2D
633 if (cachedGraphics != null) {
634 cachedGraphics.dispose();
635 cachedGraphics = null;
636 cachedFontRenderContext = null;
637 }
638
639 cachedBackingStore = renderer;
640 }
641
642 return cachedBackingStore;
643 }
644
645 private Graphics2D getGraphics2D() {
646 final TextureRenderer renderer = getBackingStore();
647
648 if (cachedGraphics == null) {
649 cachedGraphics = renderer.createGraphics();
650
651 // Set up composite, font and rendering hints
652 cachedGraphics.setComposite(AlphaComposite.Src);
653 cachedGraphics.setColor(Color.WHITE);
654 cachedGraphics.setFont(font);
655 cachedGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
656 (antialiased ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON
657 : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
658 cachedGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
659 (useFractionalMetrics
660 ? RenderingHints.VALUE_FRACTIONALMETRICS_ON
661 : RenderingHints.VALUE_FRACTIONALMETRICS_OFF));
662 }
663
664 return cachedGraphics;
665 }
666
667 private void beginRendering(final boolean ortho, final int width, final int height,
668 final boolean disableDepthTestForOrtho) {
669 final GL2 gl = GLContext.getCurrentGL().getGL2();
670
671 if (DEBUG && !debugged) {
672 debug(gl);
673 }
674
675 inBeginEndPair = true;
676 isOrthoMode = ortho;
677 beginRenderingWidth = width;
678 beginRenderingHeight = height;
679 beginRenderingDepthTestDisabled = disableDepthTestForOrtho;
680
681 if (ortho) {
682 getBackingStore().beginOrthoRendering(width, height,
683 disableDepthTestForOrtho);
684 } else {
685 getBackingStore().begin3DRendering();
686 }
687
688 // Push client attrib bits used by the pipelined quad renderer
690
691 if (!haveMaxSize) {
692 // Query OpenGL for the maximum texture size and set it in the
693 // RectanglePacker to keep it from expanding too large
694 final int[] sz = new int[1];
696 packer.setMaxSize(sz[0], sz[0]);
697 haveMaxSize = true;
698 }
699
700 if (needToResetColor && haveCachedColor) {
701 if (cachedColor == null) {
702 getBackingStore().setColor(cachedR, cachedG, cachedB, cachedA);
703 } else {
704 getBackingStore().setColor(cachedColor);
705 }
706
707 needToResetColor = false;
708 }
709
710 // Disable future attempts to use mipmapping if TextureRenderer
711 // doesn't support it
712 if (mipmap && !getBackingStore().isUsingAutoMipmapGeneration()) {
713 if (DEBUG) {
714 System.err.println("Disabled mipmapping in TextRenderer");
715 }
716
717 mipmap = false;
718 }
719 }
720
721 /**
722 * emzic: here the call to glBindBuffer crashes on certain graphicscard/driver combinations
723 * this is why the ugly try-catch block has been added, which falls back to the old textrenderer
724 *
725 * @param ortho
726 * @throws GLException
727 */
728 private void endRendering(final boolean ortho) throws GLException {
729 flushGlyphPipeline();
730
731 inBeginEndPair = false;
732
733 final GL2 gl = GLContext.getCurrentGL().getGL2();
734
735 // Pop client attrib bits used by the pipelined quad renderer
737
738 // The OpenGL spec is unclear about whether this changes the
739 // buffer bindings, so preemptively zero out the GL_ARRAY_BUFFER
740 // binding
741 if (getUseVertexArrays() && is15Available(gl)) {
742 try {
744 } catch (final Exception e) {
745 isExtensionAvailable_GL_VERSION_1_5 = false;
746 }
747 }
748
749 if (ortho) {
750 getBackingStore().endOrthoRendering();
751 } else {
752 getBackingStore().end3DRendering();
753 }
754
755 if (++numRenderCycles >= CYCLES_PER_FLUSH) {
756 numRenderCycles = 0;
757
758 if (DEBUG) {
759 System.err.println("Clearing unused entries in endRendering()");
760 }
761
762 clearUnusedEntries();
763 }
764 }
765
766 private void clearUnusedEntries() {
767 final java.util.List<Rect> deadRects = new ArrayList<Rect>();
768
769 // Iterate through the contents of the backing store, removing
770 // text strings that haven't been used recently
771 packer.visit(new RectVisitor() {
772 @Override
773 public void visit(final Rect rect) {
774 final TextData data = (TextData) rect.getUserData();
775
776 if (data.used()) {
777 data.clearUsed();
778 } else {
779 deadRects.add(rect);
780 }
781 }
782 });
783
784 for (final Rect r : deadRects) {
785 packer.remove(r);
786 stringLocations.remove(((TextData) r.getUserData()).string());
787
788 final int unicodeToClearFromCache = ((TextData) r.getUserData()).unicodeID;
789
790 if (unicodeToClearFromCache > 0) {
791 mGlyphProducer.clearCacheEntry(unicodeToClearFromCache);
792 }
793
794 // if (DEBUG) {
795 // Graphics2D g = getGraphics2D();
796 // g.setComposite(AlphaComposite.Clear);
797 // g.fillRect(r.x(), r.y(), r.w(), r.h());
798 // g.setComposite(AlphaComposite.Src);
799 // }
800 }
801
802 // If we removed dead rectangles this cycle, try to do a compaction
803 final float frag = packer.verticalFragmentationRatio();
804
805 if (!deadRects.isEmpty() && (frag > MAX_VERTICAL_FRAGMENTATION)) {
806 if (DEBUG) {
807 System.err.println(
808 "Compacting TextRenderer backing store due to vertical fragmentation " +
809 frag);
810 }
811
812 packer.compact();
813 }
814
815 if (DEBUG) {
816 getBackingStore().markDirty(0, 0, getBackingStore().getWidth(),
817 getBackingStore().getHeight());
818 }
819 }
820
821 private void internal_draw3D(final CharSequence str, float x, final float y, final float z,
822 final float scaleFactor) {
823 for (final Glyph glyph : mGlyphProducer.getGlyphs(str)) {
824 final float advance = glyph.draw3D(x, y, z, scaleFactor);
825 x += advance * scaleFactor;
826 }
827 }
828
829 private void flushGlyphPipeline() {
830 if (mPipelinedQuadRenderer != null) {
831 mPipelinedQuadRenderer.draw();
832 }
833 }
834
835 private void draw3D_ROBUST(final CharSequence str, final float x, final float y, final float z,
836 final float scaleFactor) {
837 String curStr;
838 if (str instanceof String) {
839 curStr = (String) str;
840 } else {
841 curStr = str.toString();
842 }
843
844 // Look up the string on the backing store
845 Rect rect = stringLocations.get(curStr);
846
847 if (rect == null) {
848 // Rasterize this string and place it on the backing store
849 Graphics2D g = getGraphics2D();
850 final Rectangle2D origBBox = preNormalize(renderDelegate.getBounds(curStr, font, getFontRenderContext()));
851 final Rectangle2D bbox = normalize(origBBox);
852 final Point origin = new Point((int) -bbox.getMinX(),
853 (int) -bbox.getMinY());
854 rect = new Rect(0, 0, (int) bbox.getWidth(),
855 (int) bbox.getHeight(),
856 new TextData(curStr, origin, origBBox, -1));
857
858 packer.add(rect);
859 stringLocations.put(curStr, rect);
860
861 // Re-fetch the Graphics2D in case the addition of the rectangle
862 // caused the old backing store to be thrown away
863 g = getGraphics2D();
864
865 // OK, should now have an (x, y) for this rectangle; rasterize
866 // the String
867 final int strx = rect.x() + origin.x;
868 final int stry = rect.y() + origin.y;
869
870 // Clear out the area we're going to draw into
871 g.setComposite(AlphaComposite.Clear);
872 g.fillRect(rect.x(), rect.y(), rect.w(), rect.h());
873 g.setComposite(AlphaComposite.Src);
874
875 // Draw the string
876 renderDelegate.draw(g, curStr, strx, stry);
877
878 if (DRAW_BBOXES) {
879 final TextData data = (TextData) rect.getUserData();
880 // Draw a bounding box on the backing store
881 g.drawRect(strx - data.origOriginX(),
882 stry - data.origOriginY(),
883 (int) data.origRect().getWidth(),
884 (int) data.origRect().getHeight());
885 g.drawRect(strx - data.origin().x,
886 stry - data.origin().y,
887 rect.w(),
888 rect.h());
889 }
890
891 // Mark this region of the TextureRenderer as dirty
892 getBackingStore().markDirty(rect.x(), rect.y(), rect.w(),
893 rect.h());
894 }
895
896 // OK, now draw the portion of the backing store to the screen
897 final TextureRenderer renderer = getBackingStore();
898
899 // NOTE that the rectangles managed by the packer have their
900 // origin at the upper-left but the TextureRenderer's origin is
901 // at its lower left!!!
902 final TextData data = (TextData) rect.getUserData();
903 data.markUsed();
904
905 final Rectangle2D origRect = data.origRect();
906
907 // Align the leftmost point of the baseline to the (x, y, z) coordinate requested
908 renderer.draw3DRect(x - (scaleFactor * data.origOriginX()),
909 y - (scaleFactor * ((float) origRect.getHeight() - data.origOriginY())), z,
910 rect.x() + (data.origin().x - data.origOriginX()),
911 renderer.getHeight() - rect.y() - (int) origRect.getHeight() -
912 (data.origin().y - data.origOriginY()),
913 (int) origRect.getWidth(), (int) origRect.getHeight(), scaleFactor);
914 }
915
916 //----------------------------------------------------------------------
917 // Debugging functionality
918 //
919 private void debug(final GL gl) {
920 dbgFrame = new Frame("TextRenderer Debug Output");
921
922 final GLCanvas dbgCanvas = new GLCanvas(new GLCapabilities(gl.getGLProfile()));
924 dbgCanvas.addGLEventListener(new DebugListener(gl, dbgFrame));
925 dbgFrame.add(dbgCanvas);
926
927 final FPSAnimator anim = new FPSAnimator(dbgCanvas, 10);
928 dbgFrame.addWindowListener(new WindowAdapter() {
929 @Override
930 public void windowClosing(final WindowEvent e) {
931 // Run this on another thread than the AWT event queue to
932 // make sure the call to Animator.stop() completes before
933 // exiting
934 new InterruptSource.Thread(null, new Runnable() {
935 @Override
936 public void run() {
937 anim.stop();
938 }
939 }).start();
940 }
941 });
942 dbgFrame.setSize(kSize, kSize);
943 dbgFrame.setVisible(true);
944 anim.start();
945 debugged = true;
946 }
947
948 /** Class supporting more full control over the process of rendering
949 the bitmapped text. Allows customization of whether the backing
950 store text bitmap is full-color or intensity only, the size of
951 each individual rendered text rectangle, and the contents of
952 each individual rendered text string. The default implementation
953 of this interface uses an intensity-only texture, a
954 closely-cropped rectangle around the text, and renders text
955 using the color white, which is modulated by the set color
956 during the rendering process. */
957 public static interface RenderDelegate {
958 /** Indicates whether the backing store of this TextRenderer
959 should be intensity-only (the default) or full-color. */
960 public boolean intensityOnly();
961
962 /** Computes the bounds of the given String relative to the
963 origin. */
964 public Rectangle2D getBounds(String str, Font font,
965 FontRenderContext frc);
966
967 /** Computes the bounds of the given character sequence relative
968 to the origin. */
969 public Rectangle2D getBounds(CharSequence str, Font font,
970 FontRenderContext frc);
971
972 /** Computes the bounds of the given GlyphVector, already
973 assumed to have been created for a particular Font,
974 relative to the origin. */
975 public Rectangle2D getBounds(GlyphVector gv, FontRenderContext frc);
976
977 /** Render the passed character sequence at the designated
978 location using the supplied Graphics2D instance. The
979 surrounding region will already have been cleared to the RGB
980 color (0, 0, 0) with zero alpha. The initial drawing context
981 of the passed Graphics2D will be set to use
982 AlphaComposite.Src, the color white, the Font specified in the
983 TextRenderer's constructor, and the rendering hints specified
984 in the TextRenderer constructor. Changes made by the end user
985 may be visible in successive calls to this method, but are not
986 guaranteed to be preserved. Implementors of this method
987 should reset the Graphics2D's state to that desired each time
988 this method is called, in particular those states which are
989 not the defaults. */
990 public void draw(Graphics2D graphics, String str, int x, int y);
991
992 /** Render the passed GlyphVector at the designated location using
993 the supplied Graphics2D instance. The surrounding region will
994 already have been cleared to the RGB color (0, 0, 0) with zero
995 alpha. The initial drawing context of the passed Graphics2D
996 will be set to use AlphaComposite.Src, the color white, the
997 Font specified in the TextRenderer's constructor, and the
998 rendering hints specified in the TextRenderer constructor.
999 Changes made by the end user may be visible in successive
1000 calls to this method, but are not guaranteed to be preserved.
1001 Implementors of this method should reset the Graphics2D's
1002 state to that desired each time this method is called, in
1003 particular those states which are not the defaults. */
1004 public void drawGlyphVector(Graphics2D graphics, GlyphVector str,
1005 int x, int y);
1006 }
1007
1008 private static class CharSequenceIterator implements CharacterIterator {
1009 CharSequence mSequence;
1010 int mLength;
1011 int mCurrentIndex;
1012
1013 CharSequenceIterator() {
1014 }
1015
1016 CharSequenceIterator(final CharSequence sequence) {
1017 initFromCharSequence(sequence);
1018 }
1019
1020 public void initFromCharSequence(final CharSequence sequence) {
1021 mSequence = sequence;
1022 mLength = mSequence.length();
1023 mCurrentIndex = 0;
1024 }
1025
1026 @Override
1027 public char last() {
1028 mCurrentIndex = Math.max(0, mLength - 1);
1029
1030 return current();
1031 }
1032
1033 @Override
1034 public char current() {
1035 if ((mLength == 0) || (mCurrentIndex >= mLength)) {
1036 return CharacterIterator.DONE;
1037 }
1038
1039 return mSequence.charAt(mCurrentIndex);
1040 }
1041
1042 @Override
1043 public char next() {
1044 mCurrentIndex++;
1045
1046 return current();
1047 }
1048
1049 @Override
1050 public char previous() {
1051 mCurrentIndex = Math.max(mCurrentIndex - 1, 0);
1052
1053 return current();
1054 }
1055
1056 @Override
1057 public char setIndex(final int position) {
1058 mCurrentIndex = position;
1059
1060 return current();
1061 }
1062
1063 @Override
1064 public int getBeginIndex() {
1065 return 0;
1066 }
1067
1068 @Override
1069 public int getEndIndex() {
1070 return mLength;
1071 }
1072
1073 @Override
1074 public int getIndex() {
1075 return mCurrentIndex;
1076 }
1077
1078 @Override
1079 public Object clone() {
1080 final CharSequenceIterator iter = new CharSequenceIterator(mSequence);
1081 iter.mCurrentIndex = mCurrentIndex;
1082
1083 return iter;
1084 }
1085
1086 @Override
1087 public char first() {
1088 if (mLength == 0) {
1089 return CharacterIterator.DONE;
1090 }
1091
1092 mCurrentIndex = 0;
1093
1094 return current();
1095 }
1096 }
1097
1098 // Data associated with each rectangle of text
1099 static class TextData {
1100 // Back-pointer to String this TextData describes, if it
1101 // represents a String rather than a single glyph
1102 private final String str;
1103
1104 // If this TextData represents a single glyph, this is its
1105 // unicode ID
1106 int unicodeID;
1107
1108 // The following must be defined and used VERY precisely. This is
1109 // the offset from the upper-left corner of this rectangle (Java
1110 // 2D coordinate system) at which the string must be rasterized in
1111 // order to fit within the rectangle -- the leftmost point of the
1112 // baseline.
1113 private final Point origin;
1114
1115 // This represents the pre-normalized rectangle, which fits
1116 // within the rectangle on the backing store. We keep a
1117 // one-pixel border around entries on the backing store to
1118 // prevent bleeding of adjacent letters when using GL_LINEAR
1119 // filtering for rendering. The origin of this rectangle is
1120 // equivalent to the origin above.
1121 private final Rectangle2D origRect;
1122
1123 private boolean used; // Whether this text was used recently
1124
1125 TextData(final String str, final Point origin, final Rectangle2D origRect, final int unicodeID) {
1126 this.str = str;
1127 this.origin = origin;
1128 this.origRect = origRect;
1129 this.unicodeID = unicodeID;
1130 }
1131
1132 String string() {
1133 return str;
1134 }
1135
1136 Point origin() {
1137 return origin;
1138 }
1139
1140 // The following three methods are used to locate the glyph
1141 // within the expanded rectangle coming from normalize()
1142 int origOriginX() {
1143 return (int) -origRect.getMinX();
1144 }
1145
1146 int origOriginY() {
1147 return (int) -origRect.getMinY();
1148 }
1149
1150 Rectangle2D origRect() {
1151 return origRect;
1152 }
1153
1154 boolean used() {
1155 return used;
1156 }
1157
1158 void markUsed() {
1159 used = true;
1160 }
1161
1162 void clearUsed() {
1163 used = false;
1164 }
1165 }
1166
1167 class Manager implements BackingStoreManager {
1168 private Graphics2D g;
1169
1170 @Override
1171 public Object allocateBackingStore(final int w, final int h) {
1172 // FIXME: should consider checking Font's attributes to see
1173 // whether we're likely to need to support a full RGBA backing
1174 // store (i.e., non-default Paint, foreground color, etc.), but
1175 // for now, let's just be more efficient
1176 TextureRenderer renderer;
1177
1178 if (renderDelegate.intensityOnly()) {
1179 renderer = TextureRenderer.createAlphaOnlyRenderer(w, h, mipmap);
1180 } else {
1181 renderer = new TextureRenderer(w, h, true, mipmap);
1182 }
1183 renderer.setSmoothing(smoothing);
1184
1185 if (DEBUG) {
1186 System.err.println(" TextRenderer allocating backing store " +
1187 w + " x " + h);
1188 }
1189
1190 return renderer;
1191 }
1192
1193 @Override
1194 public void deleteBackingStore(final Object backingStore) {
1195 ((TextureRenderer) backingStore).dispose();
1196 }
1197
1198 @Override
1199 public boolean preExpand(final Rect cause, final int attemptNumber) {
1200 // Only try this one time; clear out potentially obsolete entries
1201 // NOTE: this heuristic and the fact that it clears the used bit
1202 // of all entries seems to cause cycling of entries in some
1203 // situations, where the backing store becomes small compared to
1204 // the amount of text on the screen (see the TextFlow demo) and
1205 // the entries continually cycle in and out of the backing
1206 // store, decreasing performance. If we added a little age
1207 // information to the entries, and only cleared out entries
1208 // above a certain age, this behavior would be eliminated.
1209 // However, it seems the system usually stabilizes itself, so
1210 // for now we'll just keep things simple. Note that if we don't
1211 // clear the used bit here, the backing store tends to increase
1212 // very quickly to its maximum size, at least with the TextFlow
1213 // demo when the text is being continually re-laid out.
1214 if (attemptNumber == 0) {
1215 if (DEBUG) {
1216 System.err.println(
1217 "Clearing unused entries in preExpand(): attempt number " +
1218 attemptNumber);
1219 }
1220
1221 if (inBeginEndPair) {
1222 // Draw any outstanding glyphs
1223 flush();
1224 }
1225
1226 clearUnusedEntries();
1227
1228 return true;
1229 }
1230
1231 return false;
1232 }
1233
1234 @Override
1235 public boolean additionFailed(final Rect cause, final int attemptNumber) {
1236 // Heavy hammer -- might consider doing something different
1237 packer.clear();
1238 stringLocations.clear();
1239 mGlyphProducer.clearAllCacheEntries();
1240
1241 if (DEBUG) {
1242 System.err.println(
1243 " *** Cleared all text because addition failed ***");
1244 }
1245
1246 if (attemptNumber == 0) {
1247 return true;
1248 }
1249
1250 return false;
1251 }
1252
1253 @Override
1254 public boolean canCompact() {
1255 return true;
1256 }
1257
1258 @Override
1259 public void beginMovement(final Object oldBackingStore, final Object newBackingStore) {
1260 // Exit the begin / end pair if necessary
1261 if (inBeginEndPair) {
1262 // Draw any outstanding glyphs
1263 flush();
1264
1265 final GL2 gl = GLContext.getCurrentGL().getGL2();
1266
1267 // Pop client attrib bits used by the pipelined quad renderer
1268 gl.glPopClientAttrib();
1269
1270 // The OpenGL spec is unclear about whether this changes the
1271 // buffer bindings, so preemptively zero out the GL_ARRAY_BUFFER
1272 // binding
1273 if (getUseVertexArrays() && is15Available(gl)) {
1274 try {
1276 } catch (final Exception e) {
1277 isExtensionAvailable_GL_VERSION_1_5 = false;
1278 }
1279 }
1280
1281 if (isOrthoMode) {
1282 ((TextureRenderer) oldBackingStore).endOrthoRendering();
1283 } else {
1284 ((TextureRenderer) oldBackingStore).end3DRendering();
1285 }
1286 }
1287
1288 final TextureRenderer newRenderer = (TextureRenderer) newBackingStore;
1289 g = newRenderer.createGraphics();
1290 }
1291
1292 @Override
1293 public void move(final Object oldBackingStore, final Rect oldLocation,
1294 final Object newBackingStore, final Rect newLocation) {
1295 final TextureRenderer oldRenderer = (TextureRenderer) oldBackingStore;
1296 final TextureRenderer newRenderer = (TextureRenderer) newBackingStore;
1297
1298 if (oldRenderer == newRenderer) {
1299 // Movement on the same backing store -- easy case
1300 g.copyArea(oldLocation.x(), oldLocation.y(), oldLocation.w(),
1301 oldLocation.h(), newLocation.x() - oldLocation.x(),
1302 newLocation.y() - oldLocation.y());
1303 } else {
1304 // Need to draw from the old renderer's image into the new one
1305 final Image img = oldRenderer.getImage();
1306 g.drawImage(img, newLocation.x(), newLocation.y(),
1307 newLocation.x() + newLocation.w(),
1308 newLocation.y() + newLocation.h(), oldLocation.x(),
1309 oldLocation.y(), oldLocation.x() + oldLocation.w(),
1310 oldLocation.y() + oldLocation.h(), null);
1311 }
1312 }
1313
1314 @Override
1315 public void endMovement(final Object oldBackingStore, final Object newBackingStore) {
1316 g.dispose();
1317
1318 // Sync the whole surface
1319 final TextureRenderer newRenderer = (TextureRenderer) newBackingStore;
1320 newRenderer.markDirty(0, 0, newRenderer.getWidth(),
1321 newRenderer.getHeight());
1322
1323 // Re-enter the begin / end pair if necessary
1324 if (inBeginEndPair) {
1325 if (isOrthoMode) {
1326 ((TextureRenderer) newBackingStore).beginOrthoRendering(beginRenderingWidth,
1327 beginRenderingHeight, beginRenderingDepthTestDisabled);
1328 } else {
1329 ((TextureRenderer) newBackingStore).begin3DRendering();
1330 }
1331
1332 // Push client attrib bits used by the pipelined quad renderer
1333 final GL2 gl = GLContext.getCurrentGL().getGL2();
1335
1336 if (haveCachedColor) {
1337 if (cachedColor == null) {
1338 ((TextureRenderer) newBackingStore).setColor(cachedR,
1339 cachedG, cachedB, cachedA);
1340 } else {
1341 ((TextureRenderer) newBackingStore).setColor(cachedColor);
1342 }
1343 }
1344 } else {
1345 needToResetColor = true;
1346 }
1347 }
1348 }
1349
1350 public static class DefaultRenderDelegate implements RenderDelegate {
1351 @Override
1352 public boolean intensityOnly() {
1353 return true;
1354 }
1355
1356 @Override
1357 public Rectangle2D getBounds(final CharSequence str, final Font font,
1358 final FontRenderContext frc) {
1359 return getBounds(font.createGlyphVector(frc,
1360 new CharSequenceIterator(str)),
1361 frc);
1362 }
1363
1364 @Override
1365 public Rectangle2D getBounds(final String str, final Font font,
1366 final FontRenderContext frc) {
1367 return getBounds(font.createGlyphVector(frc, str), frc);
1368 }
1369
1370 @Override
1371 public Rectangle2D getBounds(final GlyphVector gv, final FontRenderContext frc) {
1372 return gv.getVisualBounds();
1373 }
1374
1375 @Override
1376 public void drawGlyphVector(final Graphics2D graphics, final GlyphVector str,
1377 final int x, final int y) {
1378 graphics.drawGlyphVector(str, x, y);
1379 }
1380
1381 @Override
1382 public void draw(final Graphics2D graphics, final String str, final int x, final int y) {
1383 graphics.drawString(str, x, y);
1384 }
1385 }
1386
1387 //----------------------------------------------------------------------
1388 // Glyph-by-glyph rendering support
1389 //
1390
1391 // A temporary to prevent excessive garbage creation
1392 private final char[] singleUnicode = new char[1];
1393
1394 /** A Glyph represents either a single unicode glyph or a
1395 substring of characters to be drawn. The reason for the dual
1396 behavior is so that we can take in a sequence of unicode
1397 characters and partition them into runs of individual glyphs,
1398 but if we encounter complex text and/or unicode sequences we
1399 don't understand, we can render them using the
1400 string-by-string method. <P>
1401
1402 Glyphs need to be able to re-upload themselves to the backing
1403 store on demand as we go along in the render sequence.
1404 */
1405
1406 class Glyph {
1407 // If this Glyph represents an individual unicode glyph, this
1408 // is its unicode ID. If it represents a String, this is -1.
1409 private int unicodeID;
1410 // If the above field isn't -1, then these fields are used.
1411 // The glyph code in the font
1412 private int glyphCode;
1413 // The GlyphProducer which created us
1414 private GlyphProducer producer;
1415 // The advance of this glyph
1416 private float advance;
1417 // The GlyphVector for this single character; this is passed
1418 // in during construction but cleared during the upload
1419 // process
1420 private GlyphVector singleUnicodeGlyphVector;
1421 // The rectangle of this glyph on the backing store, or null
1422 // if it has been cleared due to space pressure
1423 private Rect glyphRectForTextureMapping;
1424 // If this Glyph represents a String, this is the sequence of
1425 // characters
1426 private String str;
1427 // Whether we need a valid advance when rendering this string
1428 // (i.e., whether it has other single glyphs coming after it)
1429 private boolean needAdvance;
1430
1431 // Creates a Glyph representing an individual Unicode character
1432 public Glyph(final int unicodeID,
1433 final int glyphCode,
1434 final float advance,
1435 final GlyphVector singleUnicodeGlyphVector,
1436 final GlyphProducer producer) {
1437 this.unicodeID = unicodeID;
1438 this.glyphCode = glyphCode;
1439 this.advance = advance;
1440 this.singleUnicodeGlyphVector = singleUnicodeGlyphVector;
1441 this.producer = producer;
1442 }
1443
1444 // Creates a Glyph representing a sequence of characters, with
1445 // an indication of whether additional single glyphs are being
1446 // rendered after it
1447 public Glyph(final String str, final boolean needAdvance) {
1448 this.str = str;
1449 this.needAdvance = needAdvance;
1450 }
1451
1452 /** Returns this glyph's unicode ID */
1453 public int getUnicodeID() {
1454 return unicodeID;
1455 }
1456
1457 /** Returns this glyph's (font-specific) glyph code */
1458 public int getGlyphCode() {
1459 return glyphCode;
1460 }
1461
1462 /** Returns the advance for this glyph */
1463 public float getAdvance() {
1464 return advance;
1465 }
1466
1467 /** Draws this glyph and returns the (x) advance for this glyph */
1468 public float draw3D(final float inX, final float inY, final float z, final float scaleFactor) {
1469 if (str != null) {
1470 draw3D_ROBUST(str, inX, inY, z, scaleFactor);
1471 if (!needAdvance) {
1472 return 0;
1473 }
1474 // Compute and return the advance for this string
1475 final GlyphVector gv = font.createGlyphVector(getFontRenderContext(), str);
1476 float totalAdvance = 0;
1477 for (int i = 0; i < gv.getNumGlyphs(); i++) {
1478 totalAdvance += gv.getGlyphMetrics(i).getAdvance();
1479 }
1480 return totalAdvance;
1481 }
1482
1483 // This is the code path taken for individual glyphs
1484 if (glyphRectForTextureMapping == null) {
1485 upload();
1486 }
1487
1488 try {
1489 if (mPipelinedQuadRenderer == null) {
1490 mPipelinedQuadRenderer = new Pipelined_QuadRenderer();
1491 }
1492
1493 final TextureRenderer renderer = getBackingStore();
1494 // Handles case where NPOT texture is used for backing store
1495 final TextureCoords wholeImageTexCoords = renderer.getTexture().getImageTexCoords();
1496 final float xScale = wholeImageTexCoords.right();
1497 final float yScale = wholeImageTexCoords.bottom();
1498
1499 final Rect rect = glyphRectForTextureMapping;
1500 final TextData data = (TextData) rect.getUserData();
1501 data.markUsed();
1502
1503 final Rectangle2D origRect = data.origRect();
1504
1505 final float x = inX - (scaleFactor * data.origOriginX());
1506 final float y = inY - (scaleFactor * ((float) origRect.getHeight() - data.origOriginY()));
1507
1508 final int texturex = rect.x() + (data.origin().x - data.origOriginX());
1509 final int texturey = renderer.getHeight() - rect.y() - (int) origRect.getHeight() -
1510 (data.origin().y - data.origOriginY());
1511 final int width = (int) origRect.getWidth();
1512 final int height = (int) origRect.getHeight();
1513
1514 final float tx1 = xScale * texturex / renderer.getWidth();
1515 final float ty1 = yScale * (1.0f -
1516 ((float) texturey / (float) renderer.getHeight()));
1517 final float tx2 = xScale * (texturex + width) / renderer.getWidth();
1518 final float ty2 = yScale * (1.0f -
1519 ((float) (texturey + height) / (float) renderer.getHeight()));
1520
1521 mPipelinedQuadRenderer.glTexCoord2f(tx1, ty1);
1522 mPipelinedQuadRenderer.glVertex3f(x, y, z);
1523 mPipelinedQuadRenderer.glTexCoord2f(tx2, ty1);
1524 mPipelinedQuadRenderer.glVertex3f(x + (width * scaleFactor), y,
1525 z);
1526 mPipelinedQuadRenderer.glTexCoord2f(tx2, ty2);
1527 mPipelinedQuadRenderer.glVertex3f(x + (width * scaleFactor),
1528 y + (height * scaleFactor), z);
1529 mPipelinedQuadRenderer.glTexCoord2f(tx1, ty2);
1530 mPipelinedQuadRenderer.glVertex3f(x,
1531 y + (height * scaleFactor), z);
1532 } catch (final Exception e) {
1533 e.printStackTrace();
1534 }
1535 return advance;
1536 }
1537
1538 /** Notifies this glyph that it's been cleared out of the cache */
1539 public void clear() {
1540 glyphRectForTextureMapping = null;
1541 }
1542
1543 private void upload() {
1544 final GlyphVector gv = getGlyphVector();
1545 final Rectangle2D origBBox = preNormalize(renderDelegate.getBounds(gv, getFontRenderContext()));
1546 final Rectangle2D bbox = normalize(origBBox);
1547 final Point origin = new Point((int) -bbox.getMinX(),
1548 (int) -bbox.getMinY());
1549 final Rect rect = new Rect(0, 0, (int) bbox.getWidth(),
1550 (int) bbox.getHeight(),
1551 new TextData(null, origin, origBBox, unicodeID));
1552 packer.add(rect);
1553 glyphRectForTextureMapping = rect;
1554 final Graphics2D g = getGraphics2D();
1555 // OK, should now have an (x, y) for this rectangle; rasterize
1556 // the glyph
1557 final int strx = rect.x() + origin.x;
1558 final int stry = rect.y() + origin.y;
1559
1560 // Clear out the area we're going to draw into
1561 g.setComposite(AlphaComposite.Clear);
1562 g.fillRect(rect.x(), rect.y(), rect.w(), rect.h());
1563 g.setComposite(AlphaComposite.Src);
1564
1565 // Draw the string
1566 renderDelegate.drawGlyphVector(g, gv, strx, stry);
1567
1568 if (DRAW_BBOXES) {
1569 final TextData data = (TextData) rect.getUserData();
1570 // Draw a bounding box on the backing store
1571 g.drawRect(strx - data.origOriginX(),
1572 stry - data.origOriginY(),
1573 (int) data.origRect().getWidth(),
1574 (int) data.origRect().getHeight());
1575 g.drawRect(strx - data.origin().x,
1576 stry - data.origin().y,
1577 rect.w(),
1578 rect.h());
1579 }
1580
1581 // Mark this region of the TextureRenderer as dirty
1582 getBackingStore().markDirty(rect.x(), rect.y(), rect.w(),
1583 rect.h());
1584 // Re-register ourselves with our producer
1585 producer.register(this);
1586 }
1587
1588 private GlyphVector getGlyphVector() {
1589 final GlyphVector gv = singleUnicodeGlyphVector;
1590 if (gv != null) {
1591 singleUnicodeGlyphVector = null; // Don't need this anymore
1592 return gv;
1593 }
1594 singleUnicode[0] = (char) unicodeID;
1595 return font.createGlyphVector(getFontRenderContext(), singleUnicode);
1596 }
1597 }
1598
1599 class GlyphProducer {
1600 static final int undefined = -2;
1601 final FontRenderContext fontRenderContext = null; // FIXME: Never initialized!
1602 List<Glyph> glyphsOutput = new ArrayList<Glyph>();
1603 HashMap<String, GlyphVector> fullGlyphVectorCache = new HashMap<String, GlyphVector>();
1604 HashMap<Character, GlyphMetrics> glyphMetricsCache = new HashMap<Character, GlyphMetrics>();
1605 // The mapping from unicode character to font-specific glyph ID
1606 int[] unicodes2Glyphs;
1607 // The mapping from glyph ID to Glyph
1608 Glyph[] glyphCache;
1609 // We re-use this for each incoming string
1610 CharSequenceIterator iter = new CharSequenceIterator();
1611
1612 GlyphProducer(final int fontLengthInGlyphs) {
1613 unicodes2Glyphs = new int[512];
1614 glyphCache = new Glyph[fontLengthInGlyphs];
1615 clearAllCacheEntries();
1616 }
1617
1618 public List<Glyph> getGlyphs(final CharSequence inString) {
1619 glyphsOutput.clear();
1620 GlyphVector fullRunGlyphVector;
1621 fullRunGlyphVector = fullGlyphVectorCache.get(inString.toString());
1622 if (fullRunGlyphVector == null) {
1623 iter.initFromCharSequence(inString);
1624 fullRunGlyphVector = font.createGlyphVector(getFontRenderContext(), iter);
1625 fullGlyphVectorCache.put(inString.toString(), fullRunGlyphVector);
1626 }
1627 final boolean complex = (fullRunGlyphVector.getLayoutFlags() != 0);
1628 if (complex || DISABLE_GLYPH_CACHE) {
1629 // Punt to the robust version of the renderer
1630 glyphsOutput.add(new Glyph(inString.toString(), false));
1631 return glyphsOutput;
1632 }
1633
1634 final int lengthInGlyphs = fullRunGlyphVector.getNumGlyphs();
1635 int i = 0;
1636 while (i < lengthInGlyphs) {
1637 final Character letter = CharacterCache.valueOf(inString.charAt(i));
1638 GlyphMetrics metrics = glyphMetricsCache.get(letter);
1639 if (metrics == null) {
1640 metrics = fullRunGlyphVector.getGlyphMetrics(i);
1641 glyphMetricsCache.put(letter, metrics);
1642 }
1643 final Glyph glyph = getGlyph(inString, metrics, i);
1644 if (glyph != null) {
1645 glyphsOutput.add(glyph);
1646 i++;
1647 } else {
1648 // Assemble a run of characters that don't fit in
1649 // the cache
1650 final StringBuilder buf = new StringBuilder();
1651 while (i < lengthInGlyphs &&
1652 getGlyph(inString, fullRunGlyphVector.getGlyphMetrics(i), i) == null) {
1653 buf.append(inString.charAt(i++));
1654 }
1655 glyphsOutput.add(new Glyph(buf.toString(),
1656 // Any more glyphs after this run?
1657 i < lengthInGlyphs));
1658 }
1659 }
1660 return glyphsOutput;
1661 }
1662
1663 public void clearCacheEntry(final int unicodeID) {
1664 final int glyphID = unicodes2Glyphs[unicodeID];
1665 if (glyphID != undefined) {
1666 final Glyph glyph = glyphCache[glyphID];
1667 if (glyph != null) {
1668 glyph.clear();
1669 }
1670 glyphCache[glyphID] = null;
1671 }
1672 unicodes2Glyphs[unicodeID] = undefined;
1673 }
1674
1675 public void clearAllCacheEntries() {
1676 for (int i = 0; i < unicodes2Glyphs.length; i++) {
1677 clearCacheEntry(i);
1678 }
1679 }
1680
1681 public void register(final Glyph glyph) {
1682 unicodes2Glyphs[glyph.getUnicodeID()] = glyph.getGlyphCode();
1683 glyphCache[glyph.getGlyphCode()] = glyph;
1684 }
1685
1686 public float getGlyphPixelWidth(final char unicodeID) {
1687 final Glyph glyph = getGlyph(unicodeID);
1688 if (glyph != null) {
1689 return glyph.getAdvance();
1690 }
1691
1692 // Have to do this the hard / uncached way
1693 singleUnicode[0] = unicodeID;
1694 if( null == fontRenderContext ) { // FIXME: Never initialized!
1695 throw new InternalError("fontRenderContext never initialized!");
1696 }
1697 final GlyphVector gv = font.createGlyphVector(fontRenderContext,
1698 singleUnicode);
1699 return gv.getGlyphMetrics(0).getAdvance();
1700 }
1701
1702 // Returns a glyph object for this single glyph. Returns null
1703 // if the unicode or glyph ID would be out of bounds of the
1704 // glyph cache.
1705 private Glyph getGlyph(final CharSequence inString,
1706 final GlyphMetrics glyphMetrics,
1707 final int index) {
1708 final char unicodeID = inString.charAt(index);
1709
1710 if (unicodeID >= unicodes2Glyphs.length) {
1711 return null;
1712 }
1713
1714 final int glyphID = unicodes2Glyphs[unicodeID];
1715 if (glyphID != undefined) {
1716 return glyphCache[glyphID];
1717 }
1718
1719 // Must fabricate the glyph
1720 singleUnicode[0] = unicodeID;
1721 final GlyphVector gv = font.createGlyphVector(getFontRenderContext(), singleUnicode);
1722 return getGlyph(unicodeID, gv, glyphMetrics);
1723 }
1724
1725 // It's unclear whether this variant might produce less
1726 // optimal results than if we can see the entire GlyphVector
1727 // for the incoming string
1728 private Glyph getGlyph(final int unicodeID) {
1729 if (unicodeID >= unicodes2Glyphs.length) {
1730 return null;
1731 }
1732
1733 final int glyphID = unicodes2Glyphs[unicodeID];
1734 if (glyphID != undefined) {
1735 return glyphCache[glyphID];
1736 }
1737 singleUnicode[0] = (char) unicodeID;
1738 final GlyphVector gv = font.createGlyphVector(getFontRenderContext(), singleUnicode);
1739 return getGlyph(unicodeID, gv, gv.getGlyphMetrics(0));
1740 }
1741
1742 private Glyph getGlyph(final int unicodeID,
1743 final GlyphVector singleUnicodeGlyphVector,
1744 final GlyphMetrics metrics) {
1745 final int glyphCode = singleUnicodeGlyphVector.getGlyphCode(0);
1746 // Have seen huge glyph codes (65536) coming out of some fonts in some Unicode situations
1747 if (glyphCode >= glyphCache.length) {
1748 return null;
1749 }
1750 final Glyph glyph = new Glyph(unicodeID,
1751 glyphCode,
1752 metrics.getAdvance(),
1753 singleUnicodeGlyphVector,
1754 this);
1755 register(glyph);
1756 return glyph;
1757 }
1758 }
1759
1760 private static class CharacterCache {
1761 private CharacterCache() {
1762 }
1763
1764 static final Character cache[] = new Character[127 + 1];
1765
1766 static {
1767 for (int i = 0; i < cache.length; i++) {
1768 cache[i] = Character.valueOf((char) i);
1769 }
1770 }
1771
1772 public static Character valueOf(final char c) {
1773 if (c <= 127) { // must cache
1774 return CharacterCache.cache[c];
1775 }
1776 return Character.valueOf(c);
1777 }
1778 }
1779
1780 class Pipelined_QuadRenderer {
1781 int mOutstandingGlyphsVerticesPipeline = 0;
1782 FloatBuffer mTexCoords;
1783 FloatBuffer mVertCoords;
1784 boolean usingVBOs;
1785 int mVBO_For_ResuableTileVertices;
1786 int mVBO_For_ResuableTileTexCoords;
1787
1788 Pipelined_QuadRenderer() {
1789 final GL2 gl = GLContext.getCurrentGL().getGL2();
1790 mVertCoords = Buffers.newDirectFloatBuffer(kTotalBufferSizeCoordsVerts);
1791 mTexCoords = Buffers.newDirectFloatBuffer(kTotalBufferSizeCoordsTex);
1792
1793 usingVBOs = getUseVertexArrays() && is15Available(gl);
1794
1795 if (usingVBOs) {
1796 try {
1797 final int[] vbos = new int[2];
1798 gl.glGenBuffers(2, IntBuffer.wrap(vbos));
1799
1800 mVBO_For_ResuableTileVertices = vbos[0];
1801 mVBO_For_ResuableTileTexCoords = vbos[1];
1802
1804 mVBO_For_ResuableTileVertices);
1805 gl.glBufferData(GL.GL_ARRAY_BUFFER, kTotalBufferSizeBytesVerts,
1806 null, GL2ES2.GL_STREAM_DRAW); // stream draw because this is a single quad use pipeline
1807
1809 mVBO_For_ResuableTileTexCoords);
1810 gl.glBufferData(GL.GL_ARRAY_BUFFER, kTotalBufferSizeBytesTex,
1811 null, GL2ES2.GL_STREAM_DRAW); // stream draw because this is a single quad use pipeline
1812 } catch (final Exception e) {
1813 isExtensionAvailable_GL_VERSION_1_5 = false;
1814 usingVBOs = false;
1815 }
1816 }
1817 }
1818
1819 public void glTexCoord2f(final float v, final float v1) {
1820 mTexCoords.put(v);
1821 mTexCoords.put(v1);
1822 }
1823
1824 public void glVertex3f(final float inX, final float inY, final float inZ) {
1825 mVertCoords.put(inX);
1826 mVertCoords.put(inY);
1827 mVertCoords.put(inZ);
1828
1829 mOutstandingGlyphsVerticesPipeline++;
1830
1831 if (mOutstandingGlyphsVerticesPipeline >= kTotalBufferSizeVerts) {
1832 this.draw();
1833 }
1834 }
1835
1836 private void draw() {
1837 if (useVertexArrays) {
1838 drawVertexArrays();
1839 } else {
1840 drawIMMEDIATE();
1841 }
1842 }
1843
1844 private void drawVertexArrays() {
1845 if (mOutstandingGlyphsVerticesPipeline > 0) {
1846 final GL2 gl = GLContext.getCurrentGL().getGL2();
1847
1848 final TextureRenderer renderer = getBackingStore();
1849 renderer.getTexture(); // triggers texture uploads. Maybe this should be more obvious?
1850
1851 mVertCoords.rewind();
1852 mTexCoords.rewind();
1853
1855
1856 if (usingVBOs) {
1858 mVBO_For_ResuableTileVertices);
1860 mOutstandingGlyphsVerticesPipeline * kSizeInBytes_OneVertices_VertexData,
1861 mVertCoords); // upload only the new stuff
1862 gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0);
1863 } else {
1864 gl.glVertexPointer(3, GL.GL_FLOAT, 0, mVertCoords);
1865 }
1866
1868
1869 if (usingVBOs) {
1871 mVBO_For_ResuableTileTexCoords);
1873 mOutstandingGlyphsVerticesPipeline * kSizeInBytes_OneVertices_TexData,
1874 mTexCoords); // upload only the new stuff
1875 gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, 0);
1876 } else {
1877 gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, mTexCoords);
1878 }
1879
1881 mOutstandingGlyphsVerticesPipeline);
1882
1883 mVertCoords.rewind();
1884 mTexCoords.rewind();
1885 mOutstandingGlyphsVerticesPipeline = 0;
1886 }
1887 }
1888
1889 private void drawIMMEDIATE() {
1890 if (mOutstandingGlyphsVerticesPipeline > 0) {
1891 final TextureRenderer renderer = getBackingStore();
1892 renderer.getTexture(); // triggers texture uploads. Maybe this should be more obvious?
1893
1894 final GL2 gl = GLContext.getCurrentGL().getGL2();
1896
1897 try {
1898 final int numberOfQuads = mOutstandingGlyphsVerticesPipeline / 4;
1899 mVertCoords.rewind();
1900 mTexCoords.rewind();
1901
1902 for (int i = 0; i < numberOfQuads; i++) {
1903 gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
1904 gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
1905 mVertCoords.get());
1906
1907 gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
1908 gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
1909 mVertCoords.get());
1910
1911 gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
1912 gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
1913 mVertCoords.get());
1914
1915 gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
1916 gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
1917 mVertCoords.get());
1918 }
1919 } catch (final Exception e) {
1920 e.printStackTrace();
1921 } finally {
1922 gl.glEnd();
1923 mVertCoords.rewind();
1924 mTexCoords.rewind();
1925 mOutstandingGlyphsVerticesPipeline = 0;
1926 }
1927 }
1928 }
1929
1930 public void dispose() {
1931 final GL2 gl = GLContext.getCurrentGL().getGL2();
1932 final int[] vbos = new int[2];
1933 vbos[0] = mVBO_For_ResuableTileVertices;
1934 vbos[1] = mVBO_For_ResuableTileTexCoords;
1935 gl.glDeleteBuffers(2, IntBuffer.wrap(vbos));
1936 }
1937 }
1938
1939 class DebugListener implements GLEventListener {
1940 private GLU glu;
1941 private Frame frame;
1942
1943 DebugListener(final GL gl, final Frame frame) {
1944 this.glu = GLU.createGLU(gl);
1945 this.frame = frame;
1946 }
1947
1948 @Override
1949 public void display(final GLAutoDrawable drawable) {
1950 final GL2 gl = GLContext.getCurrentGL().getGL2();
1952
1953 if (packer == null) {
1954 return;
1955 }
1956
1957 final TextureRenderer rend = getBackingStore();
1958 final int w = rend.getWidth();
1959 final int h = rend.getHeight();
1960 rend.beginOrthoRendering(w, h);
1961 rend.drawOrthoRect(0, 0);
1962 rend.endOrthoRendering();
1963
1964 if ((frame.getWidth() != w) || (frame.getHeight() != h)) {
1965 EventQueue.invokeLater(new Runnable() {
1966 @Override
1967 public void run() {
1968 frame.setSize(w, h);
1969 }
1970 });
1971 }
1972 }
1973
1974 @Override
1975 public void dispose(final GLAutoDrawable drawable) {
1976 mPipelinedQuadRenderer.dispose();
1977 // n/a glu.destroy(); ??
1978 glu=null;
1979 frame=null;
1980 }
1981
1982 // Unused methods
1983 @Override
1984 public void init(final GLAutoDrawable drawable) {
1985 }
1986
1987 @Override
1988 public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width,
1989 final int height) {
1990 }
1991
1992 public void displayChanged(final GLAutoDrawable drawable,
1993 final boolean modeChanged, final boolean deviceChanged) {
1994 }
1995 }
1996
1997 /**
1998 * Sets whether vertex arrays are being used internally for
1999 * rendering, or whether text is rendered using the OpenGL
2000 * immediate mode commands. This is provided as a concession for
2001 * certain graphics cards which have poor vertex array
2002 * performance. Defaults to true.
2003 */
2004 public void setUseVertexArrays(final boolean useVertexArrays) {
2005 this.useVertexArrays = useVertexArrays;
2006 }
2007
2008 /**
2009 * Indicates whether vertex arrays are being used internally for
2010 * rendering, or whether text is rendered using the OpenGL
2011 * immediate mode commands. Defaults to true.
2012 */
2013 public final boolean getUseVertexArrays() {
2014 return useVertexArrays;
2015 }
2016
2017 /**
2018 * Sets whether smoothing (i.e., GL_LINEAR filtering) is enabled
2019 * in the backing TextureRenderer of this TextRenderer. A few
2020 * graphics cards do not behave well when this is enabled,
2021 * resulting in fuzzy text. Defaults to true.
2022 */
2023 public void setSmoothing(final boolean smoothing) {
2024 this.smoothing = smoothing;
2025 getBackingStore().setSmoothing(smoothing);
2026 }
2027
2028 /**
2029 * Indicates whether smoothing is enabled in the backing
2030 * TextureRenderer of this TextRenderer. A few graphics cards do
2031 * not behave well when this is enabled, resulting in fuzzy text.
2032 * Defaults to true.
2033 */
2034 public boolean getSmoothing() {
2035 return smoothing;
2036 }
2037
2038 private final boolean is15Available(final GL gl) {
2039 if (!checkFor_isExtensionAvailable_GL_VERSION_1_5) {
2040 isExtensionAvailable_GL_VERSION_1_5 = gl.isExtensionAvailable(GLExtensions.VERSION_1_5);
2041 checkFor_isExtensionAvailable_GL_VERSION_1_5 = true;
2042 }
2043 return isExtensionAvailable_GL_VERSION_1_5;
2044 }
2045}
Specifies a set of OpenGL capabilities.
Abstraction for an OpenGL rendering context.
Definition: GLContext.java:74
abstract GLDrawable getGLDrawable()
Returns the write-drawable this context uses for framebuffer operations.
static GLContext getCurrent()
Returns this thread current context.
Definition: GLContext.java:515
static GL getCurrentGL()
Returns the GL object bound to this thread current context.
Definition: GLContext.java:500
A generic exception for OpenGL errors used throughout the binding as a substitute for RuntimeExceptio...
Class holding OpenGL extension strings, commonly used by JOGL's implementation.
static final String VERSION_1_5
A heavyweight AWT component which provides OpenGL rendering support.
Definition: GLCanvas.java:170
final void setSharedContext(final GLContext sharedContext)
Specifies an OpenGL context, which shall be shared by this GLAutoDrawable's GLContext.
Definition: GLCanvas.java:283
void addGLEventListener(final GLEventListener listener)
Adds the given listener to the end of this drawable queue.
Definition: GLCanvas.java:1065
Provides access to the OpenGL Utility Library (GLU).
Definition: GLU.java:43
static final GLU createGLU()
Instantiates a GLU implementation object in respect to the given GL profile of this thread current GL...
Definition: GLU.java:147
An Animator subclass which attempts to achieve a target frames-per-second rate to avoid using all CPU...
final synchronized boolean start()
Starts this animator, if not running.
final synchronized boolean stop()
Stops this FPSAnimator.
void draw(final Graphics2D graphics, final String str, final int x, final int y)
Render the passed character sequence at the designated location using the supplied Graphics2D instanc...
boolean intensityOnly()
Indicates whether the backing store of this TextRenderer should be intensity-only (the default) or fu...
Rectangle2D getBounds(final GlyphVector gv, final FontRenderContext frc)
Computes the bounds of the given GlyphVector, already assumed to have been created for a particular F...
Rectangle2D getBounds(final String str, final Font font, final FontRenderContext frc)
Computes the bounds of the given String relative to the origin.
Rectangle2D getBounds(final CharSequence str, final Font font, final FontRenderContext frc)
Computes the bounds of the given character sequence relative to the origin.
void drawGlyphVector(final Graphics2D graphics, final GlyphVector str, final int x, final int y)
Render the passed GlyphVector at the designated location using the supplied Graphics2D instance.
Renders bitmapped Java 2D text into an OpenGL window with high performance, full Unicode support,...
TextRenderer(final Font font, final boolean antialiased, final boolean useFractionalMetrics, final RenderDelegate renderDelegate)
Creates a new TextRenderer with the given Font, specified font properties, and given RenderDelegate.
void beginRendering(final int width, final int height, final boolean disableDepthTest)
Begins rendering with this TextRenderer into the current OpenGL drawable, pushing the projection and ...
void beginRendering(final int width, final int height)
Begins rendering with this TextRenderer into the current OpenGL drawable, pushing the projection and ...
void setColor(final float r, final float g, final float b, final float a)
Changes the current color of this TextRenderer to the supplied one, where each component ranges from ...
void endRendering()
Ends a render cycle with this TextRenderer.
void draw(final CharSequence str, final int x, final int y)
Draws the supplied CharSequence at the desired location using the renderer's current color.
void begin3DRendering()
Begins rendering of 2D text in 3D with this TextRenderer into the current OpenGL drawable.
void setColor(final Color color)
Changes the current color of this TextRenderer to the supplied one.
TextRenderer(final Font font)
Creates a new TextRenderer with the given font, using no antialiasing or fractional metrics,...
void draw3D(final CharSequence str, final float x, final float y, final float z, final float scaleFactor)
Draws the supplied CharSequence at the desired 3D location using the renderer's current color.
void setUseVertexArrays(final boolean useVertexArrays)
Sets whether vertex arrays are being used internally for rendering, or whether text is rendered using...
TextRenderer(final Font font, final boolean antialiased, final boolean useFractionalMetrics)
Creates a new TextRenderer with the given Font, specified font properties, and default RenderDelegate...
void end3DRendering()
Ends a 3D render cycle with this TextRenderer.
boolean getSmoothing()
Indicates whether smoothing is enabled in the backing TextureRenderer of this TextRenderer.
float getCharWidth(final char inChar)
Returns the pixel width of the given character.
Rectangle2D getBounds(final CharSequence str)
Returns the bounding rectangle of the given CharSequence, assuming it was rendered at the origin.
FontRenderContext getFontRenderContext()
Returns a FontRenderContext which can be used for external text-related size computations.
void setSmoothing(final boolean smoothing)
Sets whether smoothing (i.e., GL_LINEAR filtering) is enabled in the backing TextureRenderer of this ...
void draw3D(final String str, final float x, final float y, final float z, final float scaleFactor)
Draws the supplied String at the desired 3D location using the renderer's current color.
TextRenderer(final Font font, final boolean antialiased, final boolean useFractionalMetrics, RenderDelegate renderDelegate, final boolean mipmap)
Creates a new TextRenderer with the given Font, specified font properties, and given RenderDelegate.
TextRenderer(final Font font, final boolean mipmap)
Creates a new TextRenderer with the given font, using no antialiasing or fractional metrics,...
void flush()
Causes the TextRenderer to flush any internal caches it may be maintaining and draw its rendering res...
void dispose()
Disposes of all resources this TextRenderer is using.
final boolean getUseVertexArrays()
Indicates whether vertex arrays are being used internally for rendering, or whether text is rendered ...
Font getFont()
Returns the Font this renderer is using.
void draw(final String str, final int x, final int y)
Draws the supplied String at the desired location using the renderer's current color.
Rectangle2D getBounds(final String str)
Returns the bounding rectangle of the given String, assuming it was rendered at the origin.
Provides the ability to render into an OpenGL Texture using the Java 2D APIs.
void dispose()
Disposes all resources associated with this renderer.
void setSmoothing(final boolean smoothing)
Sets whether smoothing is enabled for the OpenGL texture; if so, uses GL_LINEAR interpolation for the...
void end3DRendering()
Convenience method which assists in rendering portions of the OpenGL texture to the screen as 2D quad...
void begin3DRendering()
Convenience method which assists in rendering portions of the OpenGL texture to the screen as 2D quad...
void markDirty(final int x, final int y, final int width, final int height)
Marks the given region of the TextureRenderer as dirty.
void setColor(final float r, final float g, final float b, final float a)
Changes the color of the polygons, and therefore the drawn images, this TextureRenderer produces.
void beginOrthoRendering(final int width, final int height)
Convenience method which assists in rendering portions of the OpenGL texture to the screen,...
void endOrthoRendering()
Convenience method which assists in rendering portions of the OpenGL texture to the screen,...
Represents a rectangular region on the backing store.
Definition: Rect.java:58
Packs rectangles supplied by the user (typically representing image regions) into a larger backing st...
void clear()
Clears all Rects contained in this RectanglePacker.
void dispose()
Disposes the backing store allocated by the BackingStoreManager.
void remove(final Rect rect)
Removes the given rectangle from this RectanglePacker.
void compact()
Forces a compaction cycle, which typically results in allocating a new backing store and copying all ...
void add(final Rect rect)
Decides upon an (x, y) position for the given rectangle (leaving its width and height unchanged) and ...
void setMaxSize(final int maxWidth, final int maxHeight)
Sets up a maximum width and height for the backing store.
float verticalFragmentationRatio()
Returns the vertical fragmentation ratio of this RectanglePacker.
void visit(final RectVisitor visitor)
Visits all Rects contained in this RectanglePacker.
Specifies texture coordinates for a rectangular area of a texture.
Provides low-level information required for hardware-accelerated rendering using a surface in a platf...
Adding mutable surface pixel scale property to implementing class, usually to a NativeSurface impleme...
static final int GL_STREAM_DRAW
GL_VERSION_1_5, GL_ES_VERSION_2_0, GL_ARB_vertex_buffer_object Alias for: GL_STREAM_DRAW_ARB Define ...
Definition: GL2ES2.java:153
static final int GL_QUADS
GL_ES_VERSION_3_2, GL_VERSION_1_1, GL_VERSION_1_0, GL_OES_tessellation_shader, GL_EXT_tessellation_sh...
Definition: GL2ES3.java:734
void glEnableClientState(int cap)
Entry point to C language function: void {@native glEnableClientState}(GLenum cap) Part of GL_NV_v...
void glTexCoord2f(float s, float t)
Entry point to C language function: void {@native glTexCoord2f}(GLfloat s, GLfloat t) Part of GL_V...
void glPopClientAttrib()
Entry point to C language function: void {@native glPopClientAttrib}() Part of GL_VERSION_1_1
void glBegin(int mode)
Entry point to C language function: void {@native glBegin}(GLenum mode) Part of GL_VERSION_1_0
void glVertex3f(float x, float y, float z)
Entry point to C language function: void {@native glVertex3f}(GLfloat x, GLfloat y,...
void glPushClientAttrib(int mask)
Entry point to C language function: void {@native glPushClientAttrib}(GLbitfield mask) Part of GL_...
void glEnd()
Entry point to C language function: void {@native glEnd}() Part of GL_VERSION_1_0
static final long GL_ALL_CLIENT_ATTRIB_BITS
GL_VERSION_1_1 Define "GL_ALL_CLIENT_ATTRIB_BITS" with expression '0xFFFFFFFF', CType: long
Definition: GL2.java:945
A higher-level abstraction than GLDrawable which supplies an event based mechanism (GLEventListener) ...
GLProfile getGLProfile()
Returns the GLProfile associated with this GL object.
boolean isExtensionAvailable(String glExtensionName)
Returns true if the specified OpenGL extension can be used successfully through this GL instance give...
GL2 getGL2()
Casts this object to the GL2 interface.
NativeSurface getNativeSurface()
Returns the associated NativeSurface of this NativeSurfaceHolder.
Declares events which client code can use to manage OpenGL rendering into a GLAutoDrawable.
void glGenBuffers(int n, IntBuffer buffers)
Entry point to C language function: void {@native glGenBuffers}(GLsizei n, GLuint * buffers) Part ...
void glGetIntegerv(int pname, IntBuffer data)
Entry point to C language function: void {@native glGetIntegerv}(GLenum pname, GLint * data) Part ...
void glDrawArrays(int mode, int first, int count)
Entry point to C language function: void {@native glDrawArrays}(GLenum mode, GLint first,...
static final int GL_FLOAT
GL_ES_VERSION_2_0, GL_VERSION_1_1, GL_VERSION_1_0, GL_VERSION_ES_1_0 Define "GL_FLOAT" with expressio...
Definition: GL.java:786
void glBufferSubData(int target, long offset, long size, Buffer data)
Entry point to C language function: void {@native glBufferSubData}(GLenum target,...
static final int GL_COLOR_BUFFER_BIT
GL_ES_VERSION_2_0, GL_VERSION_1_1, GL_VERSION_1_0, GL_VERSION_ES_1_0 Define "GL_COLOR_BUFFER_BIT" wit...
Definition: GL.java:390
void glClear(int mask)
Entry point to C language function: void {@native glClear}(GLbitfield mask) Part of GL_ES_VERSION_...
static final int GL_MAX_TEXTURE_SIZE
GL_ES_VERSION_2_0, GL_VERSION_1_1, GL_VERSION_1_0, GL_VERSION_ES_1_0 Define "GL_MAX_TEXTURE_SIZE" wit...
Definition: GL.java:244
static final int GL_DEPTH_BUFFER_BIT
GL_ES_VERSION_2_0, GL_VERSION_1_1, GL_VERSION_1_0, GL_VERSION_ES_1_0 Define "GL_DEPTH_BUFFER_BIT" wit...
Definition: GL.java:738
void glDeleteBuffers(int n, IntBuffer buffers)
Entry point to C language function: void {@native glDeleteBuffers}(GLsizei n, const GLuint * buffers...
void glBindBuffer(int target, int buffer)
Entry point to C language function: void {@native glBindBuffer}(GLenum target, GLuint buffer) Part...
void glBufferData(int target, long size, Buffer data, int usage)
Entry point to C language function: void {@native glBufferData}(GLenum target, GLsizeiptr size,...
static final int GL_ARRAY_BUFFER
GL_VERSION_1_5, GL_ES_VERSION_2_0, GL_VERSION_ES_1_0, GL_ARB_vertex_buffer_object Alias for: GL_ARRAY...
Definition: GL.java:633
void glTexCoordPointer(GLArrayData array)
void glVertexPointer(GLArrayData array)
Class supporting more full control over the process of rendering the bitmapped text.
void draw(Graphics2D graphics, String str, int x, int y)
Render the passed character sequence at the designated location using the supplied Graphics2D instanc...
void drawGlyphVector(Graphics2D graphics, GlyphVector str, int x, int y)
Render the passed GlyphVector at the designated location using the supplied Graphics2D instance.
Rectangle2D getBounds(CharSequence str, Font font, FontRenderContext frc)
Computes the bounds of the given character sequence relative to the origin.
Rectangle2D getBounds(String str, Font font, FontRenderContext frc)
Computes the bounds of the given String relative to the origin.
Rectangle2D getBounds(GlyphVector gv, FontRenderContext frc)
Computes the bounds of the given GlyphVector, already assumed to have been created for a particular F...
boolean intensityOnly()
Indicates whether the backing store of this TextRenderer should be intensity-only (the default) or fu...
This interface must be implemented by the end user and is called in response to events like addition ...
Iteration construct without exposing the internals of the RectanglePacker and without implementing a ...