1/*
2 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#ifndef HEADLESS
27
28#include <stdlib.h>
29#include <math.h>
30#include <jlong.h>
31
32#include "sun_java2d_opengl_OGLTextRenderer.h"
33
34#include "SurfaceData.h"
35#include "OGLContext.h"
36#include "OGLSurfaceData.h"
37#include "OGLRenderQueue.h"
38#include "OGLTextRenderer.h"
39#include "OGLVertexCache.h"
40#include "AccelGlyphCache.h"
41#include "fontscalerdefs.h"
42
43/**
44 * The following constants define the inner and outer bounds of the
45 * accelerated glyph cache.
46 */
47#define OGLTR_CACHE_WIDTH 512
48#define OGLTR_CACHE_HEIGHT 512
49#define OGLTR_CACHE_CELL_WIDTH 32
50#define OGLTR_CACHE_CELL_HEIGHT 32
51
52/**
53 * The current "glyph mode" state. This variable is used to track the
54 * codepath used to render a particular glyph. This variable is reset to
55 * MODE_NOT_INITED at the beginning of every call to OGLTR_DrawGlyphList().
56 * As each glyph is rendered, the glyphMode variable is updated to reflect
57 * the current mode, so if the current mode is the same as the mode used
58 * to render the previous glyph, we can avoid doing costly setup operations
59 * each time.
60 */
61typedef enum {
62 MODE_NOT_INITED,
63 MODE_USE_CACHE_GRAY,
64 MODE_USE_CACHE_LCD,
65 MODE_NO_CACHE_GRAY,
66 MODE_NO_CACHE_LCD
67} GlyphMode;
68static GlyphMode glyphMode = MODE_NOT_INITED;
69
70/**
71 * There are two separate glyph caches: for AA and for LCD.
72 * Once one of them is initialized as either GRAY or LCD, it
73 * stays in that mode for the duration of the application. It should
74 * be safe to use this one glyph cache for all screens in a multimon
75 * environment, since the glyph cache texture is shared between all contexts,
76 * and (in theory) OpenGL drivers should be smart enough to manage that
77 * texture across all screens.
78 */
79
80static GlyphCacheInfo *glyphCacheLCD = NULL;
81static GlyphCacheInfo *glyphCacheAA = NULL;
82
83/**
84 * The handle to the LCD text fragment program object.
85 */
86static GLhandleARB lcdTextProgram = 0;
87
88/**
89 * This value tracks the previous LCD contrast setting, so if the contrast
90 * value hasn't changed since the last time the gamma uniforms were
91 * updated (not very common), then we can skip updating the unforms.
92 */
93static jint lastLCDContrast = -1;
94
95/**
96 * This value tracks the previous LCD rgbOrder setting, so if the rgbOrder
97 * value has changed since the last time, it indicates that we need to
98 * invalidate the cache, which may already store glyph images in the reverse
99 * order. Note that in most real world applications this value will not
100 * change over the course of the application, but tests like Font2DTest
101 * allow for changing the ordering at runtime, so we need to handle that case.
102 */
103static jboolean lastRGBOrder = JNI_TRUE;
104
105/**
106 * This constant defines the size of the tile to use in the
107 * OGLTR_DrawLCDGlyphNoCache() method. See below for more on why we
108 * restrict this value to a particular size.
109 */
110#define OGLTR_NOCACHE_TILE_SIZE 32
111
112/**
113 * These constants define the size of the "cached destination" texture.
114 * This texture is only used when rendering LCD-optimized text, as that
115 * codepath needs direct access to the destination. There is no way to
116 * access the framebuffer directly from an OpenGL shader, so we need to first
117 * copy the destination region corresponding to a particular glyph into
118 * this cached texture, and then that texture will be accessed inside the
119 * shader. Copying the destination into this cached texture can be a very
120 * expensive operation (accounting for about half the rendering time for
121 * LCD text), so to mitigate this cost we try to bulk read a horizontal
122 * region of the destination at a time. (These values are empirically
123 * derived for the common case where text runs horizontally.)
124 *
125 * Note: It is assumed in various calculations below that:
126 * (OGLTR_CACHED_DEST_WIDTH >= OGLTR_CACHE_CELL_WIDTH) &&
127 * (OGLTR_CACHED_DEST_WIDTH >= OGLTR_NOCACHE_TILE_SIZE) &&
128 * (OGLTR_CACHED_DEST_HEIGHT >= OGLTR_CACHE_CELL_HEIGHT) &&
129 * (OGLTR_CACHED_DEST_HEIGHT >= OGLTR_NOCACHE_TILE_SIZE)
130 */
131#define OGLTR_CACHED_DEST_WIDTH 512
132#define OGLTR_CACHED_DEST_HEIGHT (OGLTR_CACHE_CELL_HEIGHT * 2)
133
134/**
135 * The handle to the "cached destination" texture object.
136 */
137static GLuint cachedDestTextureID = 0;
138
139/**
140 * The current bounds of the "cached destination" texture, in destination
141 * coordinate space. The width/height of these bounds will not exceed the
142 * OGLTR_CACHED_DEST_WIDTH/HEIGHT values defined above. These bounds are
143 * only considered valid when the isCachedDestValid flag is JNI_TRUE.
144 */
145static SurfaceDataBounds cachedDestBounds;
146
147/**
148 * This flag indicates whether the "cached destination" texture contains
149 * valid data. This flag is reset to JNI_FALSE at the beginning of every
150 * call to OGLTR_DrawGlyphList(). Once we copy valid destination data
151 * into the cached texture, this flag is set to JNI_TRUE. This way, we can
152 * limit the number of times we need to copy destination data, which is a
153 * very costly operation.
154 */
155static jboolean isCachedDestValid = JNI_FALSE;
156
157/**
158 * The bounds of the previously rendered LCD glyph, in destination
159 * coordinate space. We use these bounds to determine whether the glyph
160 * currently being rendered overlaps the previously rendered glyph (i.e.
161 * its bounding box intersects that of the previously rendered glyph). If
162 * so, we need to re-read the destination area associated with that previous
163 * glyph so that we can correctly blend with the actual destination data.
164 */
165static SurfaceDataBounds previousGlyphBounds;
166
167/**
168 * Initializes the one glyph cache (texture and data structure).
169 * If lcdCache is JNI_TRUE, the texture will contain RGB data,
170 * otherwise we will simply store the grayscale/monochrome glyph images
171 * as intensity values (which work well with the GL_MODULATE function).
172 */
173static jboolean
174OGLTR_InitGlyphCache(jboolean lcdCache)
175{
176 GlyphCacheInfo *gcinfo;
177 GLclampf priority = 1.0f;
178 GLenum internalFormat = lcdCache ? GL_RGB8 : GL_INTENSITY8;
179 GLenum pixelFormat = lcdCache ? GL_RGB : GL_LUMINANCE;
180
181 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_InitGlyphCache");
182
183 // init glyph cache data structure
184 gcinfo = AccelGlyphCache_Init(OGLTR_CACHE_WIDTH,
185 OGLTR_CACHE_HEIGHT,
186 OGLTR_CACHE_CELL_WIDTH,
187 OGLTR_CACHE_CELL_HEIGHT,
188 OGLVertexCache_FlushVertexCache);
189 if (gcinfo == NULL) {
190 J2dRlsTraceLn(J2D_TRACE_ERROR,
191 "OGLTR_InitGlyphCache: could not init OGL glyph cache");
192 return JNI_FALSE;
193 }
194
195 // init cache texture object
196 j2d_glGenTextures(1, &gcinfo->cacheID);
197 j2d_glBindTexture(GL_TEXTURE_2D, gcinfo->cacheID);
198 j2d_glPrioritizeTextures(1, &gcinfo->cacheID, &priority);
199 j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
200 j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
201
202 j2d_glTexImage2D(GL_TEXTURE_2D, 0, internalFormat,
203 OGLTR_CACHE_WIDTH, OGLTR_CACHE_HEIGHT, 0,
204 pixelFormat, GL_UNSIGNED_BYTE, NULL);
205
206 if (lcdCache) {
207 glyphCacheLCD = gcinfo;
208 } else {
209 glyphCacheAA = gcinfo;
210 }
211
212 return JNI_TRUE;
213}
214
215/**
216 * Adds the given glyph to the glyph cache (texture and data structure)
217 * associated with the given OGLContext.
218 */
219static void
220OGLTR_AddToGlyphCache(GlyphInfo *glyph, GLenum pixelFormat)
221{
222 CacheCellInfo *ccinfo;
223 GlyphCacheInfo *gcinfo;
224
225 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_AddToGlyphCache");
226
227 if (pixelFormat == GL_LUMINANCE) {
228 gcinfo = glyphCacheAA;
229 } else {
230 gcinfo = glyphCacheLCD;
231 }
232
233 if ((gcinfo == NULL) || (glyph->image == NULL)) {
234 return;
235 }
236
237 AccelGlyphCache_AddGlyph(gcinfo, glyph);
238 ccinfo = (CacheCellInfo *) glyph->cellInfo;
239
240 if (ccinfo != NULL) {
241 // store glyph image in texture cell
242 j2d_glTexSubImage2D(GL_TEXTURE_2D, 0,
243 ccinfo->x, ccinfo->y,
244 glyph->width, glyph->height,
245 pixelFormat, GL_UNSIGNED_BYTE, glyph->image);
246 }
247}
248
249/**
250 * This is the GLSL fragment shader source code for rendering LCD-optimized
251 * text. Do not be frightened; it is much easier to understand than the
252 * equivalent ASM-like fragment program!
253 *
254 * The "uniform" variables at the top are initialized once the program is
255 * linked, and are updated at runtime as needed (e.g. when the source color
256 * changes, we will modify the "src_adj" value in OGLTR_UpdateLCDTextColor()).
257 *
258 * The "main" function is executed for each "fragment" (or pixel) in the
259 * glyph image. The pow() routine operates on vectors, gives precise results,
260 * and provides acceptable level of performance, so we use it to perform
261 * the gamma adjustment.
262 *
263 * The variables involved in the equation can be expressed as follows:
264 *
265 * Cs = Color component of the source (foreground color) [0.0, 1.0]
266 * Cd = Color component of the destination (background color) [0.0, 1.0]
267 * Cr = Color component to be written to the destination [0.0, 1.0]
268 * Ag = Glyph alpha (aka intensity or coverage) [0.0, 1.0]
269 * Ga = Gamma adjustment in the range [1.0, 2.5]
270 * (^ means raised to the power)
271 *
272 * And here is the theoretical equation approximated by this shader:
273 *
274 * Cr = (Ag*(Cs^Ga) + (1-Ag)*(Cd^Ga)) ^ (1/Ga)
275 */
276static const char *lcdTextShaderSource =
277 "uniform vec3 src_adj;"
278 "uniform sampler2D glyph_tex;"
279 "uniform sampler2D dst_tex;"
280 "uniform vec3 gamma;"
281 "uniform vec3 invgamma;"
282 ""
283 "void main(void)"
284 "{"
285 // load the RGB value from the glyph image at the current texcoord
286 " vec3 glyph_clr = vec3(texture2D(glyph_tex, gl_TexCoord[0].st));"
287 " if (glyph_clr == vec3(0.0)) {"
288 // zero coverage, so skip this fragment
289 " discard;"
290 " }"
291 // load the RGB value from the corresponding destination pixel
292 " vec3 dst_clr = vec3(texture2D(dst_tex, gl_TexCoord[1].st));"
293 // gamma adjust the dest color
294 " vec3 dst_adj = pow(dst_clr.rgb, gamma);"
295 // linearly interpolate the three color values
296 " vec3 result = mix(dst_adj, src_adj, glyph_clr);"
297 // gamma re-adjust the resulting color (alpha is always set to 1.0)
298 " gl_FragColor = vec4(pow(result.rgb, invgamma), 1.0);"
299 "}";
300
301/**
302 * Compiles and links the LCD text shader program. If successful, this
303 * function returns a handle to the newly created shader program; otherwise
304 * returns 0.
305 */
306static GLhandleARB
307OGLTR_CreateLCDTextProgram()
308{
309 GLhandleARB lcdTextProgram;
310 GLint loc;
311
312 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_CreateLCDTextProgram");
313
314 lcdTextProgram = OGLContext_CreateFragmentProgram(lcdTextShaderSource);
315 if (lcdTextProgram == 0) {
316 J2dRlsTraceLn(J2D_TRACE_ERROR,
317 "OGLTR_CreateLCDTextProgram: error creating program");
318 return 0;
319 }
320
321 // "use" the program object temporarily so that we can set the uniforms
322 j2d_glUseProgramObjectARB(lcdTextProgram);
323
324 // set the "uniform" values
325 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "glyph_tex");
326 j2d_glUniform1iARB(loc, 0); // texture unit 0
327 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "dst_tex");
328 j2d_glUniform1iARB(loc, 1); // texture unit 1
329
330 // "unuse" the program object; it will be re-bound later as needed
331 j2d_glUseProgramObjectARB(0);
332
333 return lcdTextProgram;
334}
335
336/**
337 * (Re)Initializes the gamma related uniforms.
338 *
339 * The given contrast value is an int in the range [100, 250] which we will
340 * then scale to fit in the range [1.0, 2.5].
341 */
342static jboolean
343OGLTR_UpdateLCDTextContrast(jint contrast)
344{
345 double g = ((double)contrast) / 100.0;
346 double ig = 1.0 / g;
347 GLint loc;
348
349 J2dTraceLn1(J2D_TRACE_INFO,
350 "OGLTR_UpdateLCDTextContrast: contrast=%d", contrast);
351
352 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "gamma");
353 j2d_glUniform3fARB(loc, g, g, g);
354
355 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "invgamma");
356 j2d_glUniform3fARB(loc, ig, ig, ig);
357
358 return JNI_TRUE;
359}
360
361/**
362 * Updates the current gamma-adjusted source color ("src_adj") of the LCD
363 * text shader program. Note that we could calculate this value in the
364 * shader (e.g. just as we do for "dst_adj"), but would be unnecessary work
365 * (and a measurable performance hit, maybe around 5%) since this value is
366 * constant over the entire glyph list. So instead we just calculate the
367 * gamma-adjusted value once and update the uniform parameter of the LCD
368 * shader as needed.
369 */
370static jboolean
371OGLTR_UpdateLCDTextColor(jint contrast)
372{
373 double gamma = ((double)contrast) / 100.0;
374 GLfloat radj, gadj, badj;
375 GLfloat clr[4];
376 GLint loc;
377
378 J2dTraceLn1(J2D_TRACE_INFO,
379 "OGLTR_UpdateLCDTextColor: contrast=%d", contrast);
380
381 /*
382 * Note: Ideally we would update the "src_adj" uniform parameter only
383 * when there is a change in the source color. Fortunately, the cost
384 * of querying the current OpenGL color state and updating the uniform
385 * value is quite small, and in the common case we only need to do this
386 * once per GlyphList, so we gain little from trying to optimize too
387 * eagerly here.
388 */
389
390 // get the current OpenGL primary color state
391 j2d_glGetFloatv(GL_CURRENT_COLOR, clr);
392
393 // gamma adjust the primary color
394 radj = (GLfloat)pow(clr[0], gamma);
395 gadj = (GLfloat)pow(clr[1], gamma);
396 badj = (GLfloat)pow(clr[2], gamma);
397
398 // update the "src_adj" parameter of the shader program with this value
399 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "src_adj");
400 j2d_glUniform3fARB(loc, radj, gadj, badj);
401
402 return JNI_TRUE;
403}
404
405/**
406 * Enables the LCD text shader and updates any related state, such as the
407 * gamma lookup table textures.
408 */
409static jboolean
410OGLTR_EnableLCDGlyphModeState(GLuint glyphTextureID,
411 GLuint dstTextureID,
412 jint contrast)
413{
414 // bind the texture containing glyph data to texture unit 0
415 j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
416 j2d_glBindTexture(GL_TEXTURE_2D, glyphTextureID);
417 j2d_glEnable(GL_TEXTURE_2D);
418
419 // bind the texture tile containing destination data to texture unit 1
420 j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
421 if (dstTextureID != 0) {
422 j2d_glBindTexture(GL_TEXTURE_2D, dstTextureID);
423 } else {
424 if (cachedDestTextureID == 0) {
425 cachedDestTextureID =
426 OGLContext_CreateBlitTexture(GL_RGB8, GL_RGB,
427 OGLTR_CACHED_DEST_WIDTH,
428 OGLTR_CACHED_DEST_HEIGHT);
429 if (cachedDestTextureID == 0) {
430 return JNI_FALSE;
431 }
432 }
433 j2d_glBindTexture(GL_TEXTURE_2D, cachedDestTextureID);
434 }
435
436 // note that GL_TEXTURE_2D was already enabled for texture unit 0,
437 // but we need to explicitly enable it for texture unit 1
438 j2d_glEnable(GL_TEXTURE_2D);
439
440 // create the LCD text shader, if necessary
441 if (lcdTextProgram == 0) {
442 lcdTextProgram = OGLTR_CreateLCDTextProgram();
443 if (lcdTextProgram == 0) {
444 return JNI_FALSE;
445 }
446 }
447
448 // enable the LCD text shader
449 j2d_glUseProgramObjectARB(lcdTextProgram);
450
451 // update the current contrast settings, if necessary
452 if (lastLCDContrast != contrast) {
453 if (!OGLTR_UpdateLCDTextContrast(contrast)) {
454 return JNI_FALSE;
455 }
456 lastLCDContrast = contrast;
457 }
458
459 // update the current color settings
460 if (!OGLTR_UpdateLCDTextColor(contrast)) {
461 return JNI_FALSE;
462 }
463
464 return JNI_TRUE;
465}
466
467void
468OGLTR_EnableGlyphVertexCache(OGLContext *oglc)
469{
470 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_EnableGlyphVertexCache");
471
472 if (!OGLVertexCache_InitVertexCache(oglc)) {
473 return;
474 }
475
476 if (glyphCacheAA == NULL) {
477 if (!OGLTR_InitGlyphCache(JNI_FALSE)) {
478 return;
479 }
480 }
481
482 j2d_glEnable(GL_TEXTURE_2D);
483 j2d_glBindTexture(GL_TEXTURE_2D, glyphCacheAA->cacheID);
484 j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
485
486 // for grayscale/monochrome text, the current OpenGL source color
487 // is modulated with the glyph image as part of the texture
488 // application stage, so we use GL_MODULATE here
489 OGLC_UPDATE_TEXTURE_FUNCTION(oglc, GL_MODULATE);
490}
491
492void
493OGLTR_DisableGlyphVertexCache(OGLContext *oglc)
494{
495 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DisableGlyphVertexCache");
496
497 OGLVertexCache_FlushVertexCache();
498 OGLVertexCache_RestoreColorState(oglc);
499
500 j2d_glDisable(GL_TEXTURE_2D);
501 j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
502 j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
503 j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
504 j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
505}
506
507/**
508 * Disables any pending state associated with the current "glyph mode".
509 */
510static void
511OGLTR_DisableGlyphModeState()
512{
513 switch (glyphMode) {
514 case MODE_NO_CACHE_LCD:
515 j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
516 j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
517 /* FALLTHROUGH */
518
519 case MODE_USE_CACHE_LCD:
520 j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
521 j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
522 j2d_glUseProgramObjectARB(0);
523 j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
524 j2d_glDisable(GL_TEXTURE_2D);
525 j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
526 j2d_glDisable(GL_TEXTURE_2D);
527 break;
528
529 case MODE_NO_CACHE_GRAY:
530 case MODE_USE_CACHE_GRAY:
531 case MODE_NOT_INITED:
532 default:
533 break;
534 }
535}
536
537static jboolean
538OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc,
539 GlyphInfo *ginfo, jint x, jint y)
540{
541 CacheCellInfo *cell;
542 jfloat x1, y1, x2, y2;
543
544 if (glyphMode != MODE_USE_CACHE_GRAY) {
545 OGLTR_DisableGlyphModeState();
546 CHECK_PREVIOUS_OP(OGL_STATE_GLYPH_OP);
547 glyphMode = MODE_USE_CACHE_GRAY;
548 }
549
550 if (ginfo->cellInfo == NULL) {
551 // attempt to add glyph to accelerated glyph cache
552 OGLTR_AddToGlyphCache(ginfo, GL_LUMINANCE);
553
554 if (ginfo->cellInfo == NULL) {
555 // we'll just no-op in the rare case that the cell is NULL
556 return JNI_TRUE;
557 }
558 }
559
560 cell = (CacheCellInfo *) (ginfo->cellInfo);
561 cell->timesRendered++;
562
563 x1 = (jfloat)x;
564 y1 = (jfloat)y;
565 x2 = x1 + ginfo->width;
566 y2 = y1 + ginfo->height;
567
568 OGLVertexCache_AddGlyphQuad(oglc,
569 cell->tx1, cell->ty1,
570 cell->tx2, cell->ty2,
571 x1, y1, x2, y2);
572
573 return JNI_TRUE;
574}
575
576/**
577 * Evaluates to true if the rectangle defined by gx1/gy1/gx2/gy2 is
578 * inside outerBounds.
579 */
580#define INSIDE(gx1, gy1, gx2, gy2, outerBounds) \
581 (((gx1) >= outerBounds.x1) && ((gy1) >= outerBounds.y1) && \
582 ((gx2) <= outerBounds.x2) && ((gy2) <= outerBounds.y2))
583
584/**
585 * Evaluates to true if the rectangle defined by gx1/gy1/gx2/gy2 intersects
586 * the rectangle defined by bounds.
587 */
588#define INTERSECTS(gx1, gy1, gx2, gy2, bounds) \
589 ((bounds.x2 > (gx1)) && (bounds.y2 > (gy1)) && \
590 (bounds.x1 < (gx2)) && (bounds.y1 < (gy2)))
591
592/**
593 * This method checks to see if the given LCD glyph bounds fall within the
594 * cached destination texture bounds. If so, this method can return
595 * immediately. If not, this method will copy a chunk of framebuffer data
596 * into the cached destination texture and then update the current cached
597 * destination bounds before returning.
598 */
599static void
600OGLTR_UpdateCachedDestination(OGLSDOps *dstOps, GlyphInfo *ginfo,
601 jint gx1, jint gy1, jint gx2, jint gy2,
602 jint glyphIndex, jint totalGlyphs)
603{
604 jint dx1, dy1, dx2, dy2;
605 jint dx1adj, dy1adj;
606
607 if (isCachedDestValid && INSIDE(gx1, gy1, gx2, gy2, cachedDestBounds)) {
608 // glyph is already within the cached destination bounds; no need
609 // to read back the entire destination region again, but we do
610 // need to see if the current glyph overlaps the previous glyph...
611
612 if (INTERSECTS(gx1, gy1, gx2, gy2, previousGlyphBounds)) {
613 // the current glyph overlaps the destination region touched
614 // by the previous glyph, so now we need to read back the part
615 // of the destination corresponding to the previous glyph
616 dx1 = previousGlyphBounds.x1;
617 dy1 = previousGlyphBounds.y1;
618 dx2 = previousGlyphBounds.x2;
619 dy2 = previousGlyphBounds.y2;
620
621 // this accounts for lower-left origin of the destination region
622 dx1adj = dstOps->xOffset + dx1;
623 dy1adj = dstOps->yOffset + dstOps->height - dy2;
624
625 // copy destination into subregion of cached texture tile:
626 // dx1-cachedDestBounds.x1 == +xoffset from left side of texture
627 // cachedDestBounds.y2-dy2 == +yoffset from bottom of texture
628 j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
629 j2d_glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
630 dx1 - cachedDestBounds.x1,
631 cachedDestBounds.y2 - dy2,
632 dx1adj, dy1adj,
633 dx2-dx1, dy2-dy1);
634 }
635 } else {
636 jint remainingWidth;
637
638 // destination region is not valid, so we need to read back a
639 // chunk of the destination into our cached texture
640
641 // position the upper-left corner of the destination region on the
642 // "top" line of glyph list
643 // REMIND: this isn't ideal; it would be better if we had some idea
644 // of the bounding box of the whole glyph list (this is
645 // do-able, but would require iterating through the whole
646 // list up front, which may present its own problems)
647 dx1 = gx1;
648 dy1 = gy1;
649
650 if (ginfo->advanceX > 0) {
651 // estimate the width based on our current position in the glyph
652 // list and using the x advance of the current glyph (this is just
653 // a quick and dirty heuristic; if this is a "thin" glyph image,
654 // then we're likely to underestimate, and if it's "thick" then we
655 // may end up reading back more than we need to)
656 remainingWidth =
657 (jint)(ginfo->advanceX * (totalGlyphs - glyphIndex));
658 if (remainingWidth > OGLTR_CACHED_DEST_WIDTH) {
659 remainingWidth = OGLTR_CACHED_DEST_WIDTH;
660 } else if (remainingWidth < ginfo->width) {
661 // in some cases, the x-advance may be slightly smaller
662 // than the actual width of the glyph; if so, adjust our
663 // estimate so that we can accommodate the entire glyph
664 remainingWidth = ginfo->width;
665 }
666 } else {
667 // a negative advance is possible when rendering rotated text,
668 // in which case it is difficult to estimate an appropriate
669 // region for readback, so we will pick a region that
670 // encompasses just the current glyph
671 remainingWidth = ginfo->width;
672 }
673 dx2 = dx1 + remainingWidth;
674
675 // estimate the height (this is another sloppy heuristic; we'll
676 // make the cached destination region tall enough to encompass most
677 // glyphs that are small enough to fit in the glyph cache, and then
678 // we add a little something extra to account for descenders
679 dy2 = dy1 + OGLTR_CACHE_CELL_HEIGHT + 2;
680
681 // this accounts for lower-left origin of the destination region
682 dx1adj = dstOps->xOffset + dx1;
683 dy1adj = dstOps->yOffset + dstOps->height - dy2;
684
685 // copy destination into cached texture tile (the lower-left corner
686 // of the destination region will be positioned at the lower-left
687 // corner (0,0) of the texture)
688 j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
689 j2d_glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
690 0, 0, dx1adj, dy1adj,
691 dx2-dx1, dy2-dy1);
692
693 // update the cached bounds and mark it valid
694 cachedDestBounds.x1 = dx1;
695 cachedDestBounds.y1 = dy1;
696 cachedDestBounds.x2 = dx2;
697 cachedDestBounds.y2 = dy2;
698 isCachedDestValid = JNI_TRUE;
699 }
700
701 // always update the previous glyph bounds
702 previousGlyphBounds.x1 = gx1;
703 previousGlyphBounds.y1 = gy1;
704 previousGlyphBounds.x2 = gx2;
705 previousGlyphBounds.y2 = gy2;
706}
707
708static jboolean
709OGLTR_DrawLCDGlyphViaCache(OGLContext *oglc, OGLSDOps *dstOps,
710 GlyphInfo *ginfo, jint x, jint y,
711 jint glyphIndex, jint totalGlyphs,
712 jboolean rgbOrder, jint contrast,
713 GLuint dstTextureID)
714{
715 CacheCellInfo *cell;
716 jint dx1, dy1, dx2, dy2;
717 jfloat dtx1, dty1, dtx2, dty2;
718
719 if (glyphMode != MODE_USE_CACHE_LCD) {
720 OGLTR_DisableGlyphModeState();
721 CHECK_PREVIOUS_OP(GL_TEXTURE_2D);
722 j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
723
724 if (glyphCacheLCD == NULL) {
725 if (!OGLTR_InitGlyphCache(JNI_TRUE)) {
726 return JNI_FALSE;
727 }
728 }
729
730 if (rgbOrder != lastRGBOrder) {
731 // need to invalidate the cache in this case; see comments
732 // for lastRGBOrder above
733 AccelGlyphCache_Invalidate(glyphCacheLCD);
734 lastRGBOrder = rgbOrder;
735 }
736
737 if (!OGLTR_EnableLCDGlyphModeState(glyphCacheLCD->cacheID,
738 dstTextureID, contrast))
739 {
740 return JNI_FALSE;
741 }
742
743 // when a fragment shader is enabled, the texture function state is
744 // ignored, so the following line is not needed...
745 // OGLC_UPDATE_TEXTURE_FUNCTION(oglc, GL_MODULATE);
746
747 glyphMode = MODE_USE_CACHE_LCD;
748 }
749
750 if (ginfo->cellInfo == NULL) {
751 // rowBytes will always be a multiple of 3, so the following is safe
752 j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, ginfo->rowBytes / 3);
753
754 // make sure the glyph cache texture is bound to texture unit 0
755 j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
756
757 // attempt to add glyph to accelerated glyph cache
758 OGLTR_AddToGlyphCache(ginfo, rgbOrder ? GL_RGB : GL_BGR);
759
760 if (ginfo->cellInfo == NULL) {
761 // we'll just no-op in the rare case that the cell is NULL
762 return JNI_TRUE;
763 }
764 }
765
766 cell = (CacheCellInfo *) (ginfo->cellInfo);
767 cell->timesRendered++;
768
769 // location of the glyph in the destination's coordinate space
770 dx1 = x;
771 dy1 = y;
772 dx2 = dx1 + ginfo->width;
773 dy2 = dy1 + ginfo->height;
774
775 if (dstTextureID == 0) {
776 // copy destination into second cached texture, if necessary
777 OGLTR_UpdateCachedDestination(dstOps, ginfo,
778 dx1, dy1, dx2, dy2,
779 glyphIndex, totalGlyphs);
780
781 // texture coordinates of the destination tile
782 dtx1 = ((jfloat)(dx1 - cachedDestBounds.x1)) / OGLTR_CACHED_DEST_WIDTH;
783 dty1 = ((jfloat)(cachedDestBounds.y2 - dy1)) / OGLTR_CACHED_DEST_HEIGHT;
784 dtx2 = ((jfloat)(dx2 - cachedDestBounds.x1)) / OGLTR_CACHED_DEST_WIDTH;
785 dty2 = ((jfloat)(cachedDestBounds.y2 - dy2)) / OGLTR_CACHED_DEST_HEIGHT;
786 } else {
787 jint gw = ginfo->width;
788 jint gh = ginfo->height;
789
790 // this accounts for lower-left origin of the destination region
791 jint dxadj = dstOps->xOffset + x;
792 jint dyadj = dstOps->yOffset + dstOps->height - (y + gh);
793
794 // update the remaining destination texture coordinates
795 dtx1 =((GLfloat)dxadj) / dstOps->textureWidth;
796 dtx2 = ((GLfloat)dxadj + gw) / dstOps->textureWidth;
797
798 dty1 = ((GLfloat)dyadj + gh) / dstOps->textureHeight;
799 dty2 = ((GLfloat)dyadj) / dstOps->textureHeight;
800
801 j2d_glTextureBarrierNV();
802 }
803
804 // render composed texture to the destination surface
805 j2d_glBegin(GL_QUADS);
806 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx1, cell->ty1);
807 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty1);
808 j2d_glVertex2i(dx1, dy1);
809 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx2, cell->ty1);
810 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty1);
811 j2d_glVertex2i(dx2, dy1);
812 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx2, cell->ty2);
813 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty2);
814 j2d_glVertex2i(dx2, dy2);
815 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx1, cell->ty2);
816 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty2);
817 j2d_glVertex2i(dx1, dy2);
818 j2d_glEnd();
819
820 return JNI_TRUE;
821}
822
823static jboolean
824OGLTR_DrawGrayscaleGlyphNoCache(OGLContext *oglc,
825 GlyphInfo *ginfo, jint x, jint y)
826{
827 jint tw, th;
828 jint sx, sy, sw, sh;
829 jint x0;
830 jint w = ginfo->width;
831 jint h = ginfo->height;
832
833 if (glyphMode != MODE_NO_CACHE_GRAY) {
834 OGLTR_DisableGlyphModeState();
835 CHECK_PREVIOUS_OP(OGL_STATE_MASK_OP);
836 glyphMode = MODE_NO_CACHE_GRAY;
837 }
838
839 x0 = x;
840 tw = OGLVC_MASK_CACHE_TILE_WIDTH;
841 th = OGLVC_MASK_CACHE_TILE_HEIGHT;
842
843 for (sy = 0; sy < h; sy += th, y += th) {
844 x = x0;
845 sh = ((sy + th) > h) ? (h - sy) : th;
846
847 for (sx = 0; sx < w; sx += tw, x += tw) {
848 sw = ((sx + tw) > w) ? (w - sx) : tw;
849
850 OGLVertexCache_AddMaskQuad(oglc,
851 sx, sy, x, y, sw, sh,
852 w, ginfo->image);
853 }
854 }
855
856 return JNI_TRUE;
857}
858
859static jboolean
860OGLTR_DrawLCDGlyphNoCache(OGLContext *oglc, OGLSDOps *dstOps,
861 GlyphInfo *ginfo, jint x, jint y,
862 jint rowBytesOffset,
863 jboolean rgbOrder, jint contrast,
864 GLuint dstTextureID)
865{
866 GLfloat tx1, ty1, tx2, ty2;
867 GLfloat dtx1, dty1, dtx2, dty2;
868 jint tw, th;
869 jint sx, sy, sw, sh, dxadj, dyadj;
870 jint x0;
871 jint w = ginfo->width;
872 jint h = ginfo->height;
873 GLenum pixelFormat = rgbOrder ? GL_RGB : GL_BGR;
874
875 if (glyphMode != MODE_NO_CACHE_LCD) {
876 OGLTR_DisableGlyphModeState();
877 CHECK_PREVIOUS_OP(GL_TEXTURE_2D);
878 j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
879
880 if (oglc->blitTextureID == 0) {
881 if (!OGLContext_InitBlitTileTexture(oglc)) {
882 return JNI_FALSE;
883 }
884 }
885
886 if (!OGLTR_EnableLCDGlyphModeState(oglc->blitTextureID,
887 dstTextureID, contrast))
888 {
889 return JNI_FALSE;
890 }
891
892 // when a fragment shader is enabled, the texture function state is
893 // ignored, so the following line is not needed...
894 // OGLC_UPDATE_TEXTURE_FUNCTION(oglc, GL_MODULATE);
895
896 glyphMode = MODE_NO_CACHE_LCD;
897 }
898
899 // rowBytes will always be a multiple of 3, so the following is safe
900 j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, ginfo->rowBytes / 3);
901
902 x0 = x;
903 tx1 = 0.0f;
904 ty1 = 0.0f;
905 dtx1 = 0.0f;
906 dty2 = 0.0f;
907 tw = OGLTR_NOCACHE_TILE_SIZE;
908 th = OGLTR_NOCACHE_TILE_SIZE;
909
910 for (sy = 0; sy < h; sy += th, y += th) {
911 x = x0;
912 sh = ((sy + th) > h) ? (h - sy) : th;
913
914 for (sx = 0; sx < w; sx += tw, x += tw) {
915 sw = ((sx + tw) > w) ? (w - sx) : tw;
916
917 // update the source pointer offsets
918 j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, sx);
919 j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, sy);
920
921 // copy LCD mask into glyph texture tile
922 j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
923 j2d_glTexSubImage2D(GL_TEXTURE_2D, 0,
924 0, 0, sw, sh,
925 pixelFormat, GL_UNSIGNED_BYTE,
926 ginfo->image + rowBytesOffset);
927
928 // update the lower-right glyph texture coordinates
929 tx2 = ((GLfloat)sw) / OGLC_BLIT_TILE_SIZE;
930 ty2 = ((GLfloat)sh) / OGLC_BLIT_TILE_SIZE;
931
932 // this accounts for lower-left origin of the destination region
933 dxadj = dstOps->xOffset + x;
934 dyadj = dstOps->yOffset + dstOps->height - (y + sh);
935
936 if (dstTextureID == 0) {
937 // copy destination into cached texture tile (the lower-left
938 // corner of the destination region will be positioned at the
939 // lower-left corner (0,0) of the texture)
940 j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
941 j2d_glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
942 0, 0,
943 dxadj, dyadj,
944 sw, sh);
945 // update the remaining destination texture coordinates
946 dtx2 = ((GLfloat)sw) / OGLTR_CACHED_DEST_WIDTH;
947 dty1 = ((GLfloat)sh) / OGLTR_CACHED_DEST_HEIGHT;
948 } else {
949 // use the destination texture directly
950 // update the remaining destination texture coordinates
951 dtx1 =((GLfloat)dxadj) / dstOps->textureWidth;
952 dtx2 = ((GLfloat)dxadj + sw) / dstOps->textureWidth;
953
954 dty1 = ((GLfloat)dyadj + sh) / dstOps->textureHeight;
955 dty2 = ((GLfloat)dyadj) / dstOps->textureHeight;
956
957 j2d_glTextureBarrierNV();
958 }
959
960 // render composed texture to the destination surface
961 j2d_glBegin(GL_QUADS);
962 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx1, ty1);
963 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty1);
964 j2d_glVertex2i(x, y);
965 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx2, ty1);
966 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty1);
967 j2d_glVertex2i(x + sw, y);
968 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx2, ty2);
969 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty2);
970 j2d_glVertex2i(x + sw, y + sh);
971 j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx1, ty2);
972 j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty2);
973 j2d_glVertex2i(x, y + sh);
974 j2d_glEnd();
975 }
976 }
977
978 return JNI_TRUE;
979}
980
981// see DrawGlyphList.c for more on this macro...
982#define FLOOR_ASSIGN(l, r) \
983 if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r))
984
985void
986OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
987 jint totalGlyphs, jboolean usePositions,
988 jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
989 jfloat glyphListOrigX, jfloat glyphListOrigY,
990 unsigned char *images, unsigned char *positions)
991{
992 int glyphCounter;
993 GLuint dstTextureID = 0;
994
995 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DrawGlyphList");
996
997 RETURN_IF_NULL(oglc);
998 RETURN_IF_NULL(dstOps);
999 RETURN_IF_NULL(images);
1000 if (usePositions) {
1001 RETURN_IF_NULL(positions);
1002 }
1003
1004 glyphMode = MODE_NOT_INITED;
1005 isCachedDestValid = JNI_FALSE;
1006
1007 // We have to obtain an information about destination content
1008 // in order to render lcd glyphs. It could be done by copying
1009 // a part of desitination buffer into an intermediate texture
1010 // using glCopyTexSubImage2D(). However, on macosx this path is
1011 // slow, and it dramatically reduces the overall speed of lcd
1012 // text rendering.
1013 //
1014 // In some cases, we can use a texture from the destination
1015 // surface data in oredr to avoid this slow reading routine.
1016 // It requires:
1017 // * An appropriate textureTarget for the destination SD.
1018 // In particular, we need GL_TEXTURE_2D
1019 // * Means to prevent read-after-write problem.
1020 // At the moment, a GL_NV_texture_barrier extension is used
1021 // to achieve this.
1022 if (OGLC_IS_CAP_PRESENT(oglc, CAPS_EXT_TEXBARRIER) &&
1023 dstOps->textureTarget == GL_TEXTURE_2D)
1024 {
1025 dstTextureID = dstOps->textureID;
1026 }
1027
1028 for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
1029 jint x, y;
1030 jfloat glyphx, glyphy;
1031 jboolean grayscale, ok;
1032 GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images));
1033
1034 if (ginfo == NULL) {
1035 // this shouldn't happen, but if it does we'll just break out...
1036 J2dRlsTraceLn(J2D_TRACE_ERROR,
1037 "OGLTR_DrawGlyphList: glyph info is null");
1038 break;
1039 }
1040
1041 grayscale = (ginfo->rowBytes == ginfo->width);
1042
1043 if (usePositions) {
1044 jfloat posx = NEXT_FLOAT(positions);
1045 jfloat posy = NEXT_FLOAT(positions);
1046 glyphx = glyphListOrigX + posx + ginfo->topLeftX;
1047 glyphy = glyphListOrigY + posy + ginfo->topLeftY;
1048 FLOOR_ASSIGN(x, glyphx);
1049 FLOOR_ASSIGN(y, glyphy);
1050 } else {
1051 glyphx = glyphListOrigX + ginfo->topLeftX;
1052 glyphy = glyphListOrigY + ginfo->topLeftY;
1053 FLOOR_ASSIGN(x, glyphx);
1054 FLOOR_ASSIGN(y, glyphy);
1055 glyphListOrigX += ginfo->advanceX;
1056 glyphListOrigY += ginfo->advanceY;
1057 }
1058
1059 if (ginfo->image == NULL) {
1060 continue;
1061 }
1062
1063 if (grayscale) {
1064 // grayscale or monochrome glyph data
1065 if (ginfo->width <= OGLTR_CACHE_CELL_WIDTH &&
1066 ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
1067 {
1068 ok = OGLTR_DrawGrayscaleGlyphViaCache(oglc, ginfo, x, y);
1069 } else {
1070 ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y);
1071 }
1072 } else {
1073 // LCD-optimized glyph data
1074 jint rowBytesOffset = 0;
1075
1076 if (subPixPos) {
1077 jint frac = (jint)((glyphx - x) * 3);
1078 if (frac != 0) {
1079 rowBytesOffset = 3 - frac;
1080 x += 1;
1081 }
1082 }
1083
1084 if (rowBytesOffset == 0 &&
1085 ginfo->width <= OGLTR_CACHE_CELL_WIDTH &&
1086 ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
1087 {
1088 ok = OGLTR_DrawLCDGlyphViaCache(oglc, dstOps,
1089 ginfo, x, y,
1090 glyphCounter, totalGlyphs,
1091 rgbOrder, lcdContrast,
1092 dstTextureID);
1093 } else {
1094 ok = OGLTR_DrawLCDGlyphNoCache(oglc, dstOps,
1095 ginfo, x, y,
1096 rowBytesOffset,
1097 rgbOrder, lcdContrast,
1098 dstTextureID);
1099 }
1100 }
1101
1102 if (!ok) {
1103 break;
1104 }
1105 }
1106
1107 OGLTR_DisableGlyphModeState();
1108}
1109
1110JNIEXPORT void JNICALL
1111Java_sun_java2d_opengl_OGLTextRenderer_drawGlyphList
1112 (JNIEnv *env, jobject self,
1113 jint numGlyphs, jboolean usePositions,
1114 jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
1115 jfloat glyphListOrigX, jfloat glyphListOrigY,
1116 jlongArray imgArray, jfloatArray posArray)
1117{
1118 unsigned char *images;
1119
1120 J2dTraceLn(J2D_TRACE_INFO, "OGLTextRenderer_drawGlyphList");
1121
1122 images = (unsigned char *)
1123 (*env)->GetPrimitiveArrayCritical(env, imgArray, NULL);
1124 if (images != NULL) {
1125 OGLContext *oglc = OGLRenderQueue_GetCurrentContext();
1126 OGLSDOps *dstOps = OGLRenderQueue_GetCurrentDestination();
1127
1128 if (usePositions) {
1129 unsigned char *positions = (unsigned char *)
1130 (*env)->GetPrimitiveArrayCritical(env, posArray, NULL);
1131 if (positions != NULL) {
1132 OGLTR_DrawGlyphList(env, oglc, dstOps,
1133 numGlyphs, usePositions,
1134 subPixPos, rgbOrder, lcdContrast,
1135 glyphListOrigX, glyphListOrigY,
1136 images, positions);
1137 (*env)->ReleasePrimitiveArrayCritical(env, posArray,
1138 positions, JNI_ABORT);
1139 }
1140 } else {
1141 OGLTR_DrawGlyphList(env, oglc, dstOps,
1142 numGlyphs, usePositions,
1143 subPixPos, rgbOrder, lcdContrast,
1144 glyphListOrigX, glyphListOrigY,
1145 images, NULL);
1146 }
1147
1148 // 6358147: reset current state, and ensure rendering is
1149 // flushed to dest
1150 if (oglc != NULL) {
1151 RESET_PREVIOUS_OP();
1152 j2d_glFlush();
1153 }
1154
1155 (*env)->ReleasePrimitiveArrayCritical(env, imgArray,
1156 images, JNI_ABORT);
1157 }
1158}
1159
1160#endif /* !HEADLESS */
1161