src/java.desktop/share/native/common/java2d/opengl/OGLTextRenderer.c
changeset 47216 71c04702a3d5
parent 31882 0268245131a9
child 56230 489867818774
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/share/native/common/java2d/opengl/OGLTextRenderer.c	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1160 @@
+/*
+ * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef HEADLESS
+
+#include <stdlib.h>
+#include <math.h>
+#include <jlong.h>
+
+#include "sun_java2d_opengl_OGLTextRenderer.h"
+
+#include "SurfaceData.h"
+#include "OGLContext.h"
+#include "OGLSurfaceData.h"
+#include "OGLRenderQueue.h"
+#include "OGLTextRenderer.h"
+#include "OGLVertexCache.h"
+#include "AccelGlyphCache.h"
+#include "fontscalerdefs.h"
+
+/**
+ * The following constants define the inner and outer bounds of the
+ * accelerated glyph cache.
+ */
+#define OGLTR_CACHE_WIDTH       512
+#define OGLTR_CACHE_HEIGHT      512
+#define OGLTR_CACHE_CELL_WIDTH  32
+#define OGLTR_CACHE_CELL_HEIGHT 32
+
+/**
+ * The current "glyph mode" state.  This variable is used to track the
+ * codepath used to render a particular glyph.  This variable is reset to
+ * MODE_NOT_INITED at the beginning of every call to OGLTR_DrawGlyphList().
+ * As each glyph is rendered, the glyphMode variable is updated to reflect
+ * the current mode, so if the current mode is the same as the mode used
+ * to render the previous glyph, we can avoid doing costly setup operations
+ * each time.
+ */
+typedef enum {
+    MODE_NOT_INITED,
+    MODE_USE_CACHE_GRAY,
+    MODE_USE_CACHE_LCD,
+    MODE_NO_CACHE_GRAY,
+    MODE_NO_CACHE_LCD
+} GlyphMode;
+static GlyphMode glyphMode = MODE_NOT_INITED;
+
+/**
+ * There are two separate glyph caches: for AA and for LCD.
+ * Once one of them is initialized as either GRAY or LCD, it
+ * stays in that mode for the duration of the application.  It should
+ * be safe to use this one glyph cache for all screens in a multimon
+ * environment, since the glyph cache texture is shared between all contexts,
+ * and (in theory) OpenGL drivers should be smart enough to manage that
+ * texture across all screens.
+ */
+
+static GlyphCacheInfo *glyphCacheLCD = NULL;
+static GlyphCacheInfo *glyphCacheAA = NULL;
+
+/**
+ * The handle to the LCD text fragment program object.
+ */
+static GLhandleARB lcdTextProgram = 0;
+
+/**
+ * This value tracks the previous LCD contrast setting, so if the contrast
+ * value hasn't changed since the last time the gamma uniforms were
+ * updated (not very common), then we can skip updating the unforms.
+ */
+static jint lastLCDContrast = -1;
+
+/**
+ * This value tracks the previous LCD rgbOrder setting, so if the rgbOrder
+ * value has changed since the last time, it indicates that we need to
+ * invalidate the cache, which may already store glyph images in the reverse
+ * order.  Note that in most real world applications this value will not
+ * change over the course of the application, but tests like Font2DTest
+ * allow for changing the ordering at runtime, so we need to handle that case.
+ */
+static jboolean lastRGBOrder = JNI_TRUE;
+
+/**
+ * This constant defines the size of the tile to use in the
+ * OGLTR_DrawLCDGlyphNoCache() method.  See below for more on why we
+ * restrict this value to a particular size.
+ */
+#define OGLTR_NOCACHE_TILE_SIZE 32
+
+/**
+ * These constants define the size of the "cached destination" texture.
+ * This texture is only used when rendering LCD-optimized text, as that
+ * codepath needs direct access to the destination.  There is no way to
+ * access the framebuffer directly from an OpenGL shader, so we need to first
+ * copy the destination region corresponding to a particular glyph into
+ * this cached texture, and then that texture will be accessed inside the
+ * shader.  Copying the destination into this cached texture can be a very
+ * expensive operation (accounting for about half the rendering time for
+ * LCD text), so to mitigate this cost we try to bulk read a horizontal
+ * region of the destination at a time.  (These values are empirically
+ * derived for the common case where text runs horizontally.)
+ *
+ * Note: It is assumed in various calculations below that:
+ *     (OGLTR_CACHED_DEST_WIDTH  >= OGLTR_CACHE_CELL_WIDTH)  &&
+ *     (OGLTR_CACHED_DEST_WIDTH  >= OGLTR_NOCACHE_TILE_SIZE) &&
+ *     (OGLTR_CACHED_DEST_HEIGHT >= OGLTR_CACHE_CELL_HEIGHT) &&
+ *     (OGLTR_CACHED_DEST_HEIGHT >= OGLTR_NOCACHE_TILE_SIZE)
+ */
+#define OGLTR_CACHED_DEST_WIDTH  512
+#define OGLTR_CACHED_DEST_HEIGHT (OGLTR_CACHE_CELL_HEIGHT * 2)
+
+/**
+ * The handle to the "cached destination" texture object.
+ */
+static GLuint cachedDestTextureID = 0;
+
+/**
+ * The current bounds of the "cached destination" texture, in destination
+ * coordinate space.  The width/height of these bounds will not exceed the
+ * OGLTR_CACHED_DEST_WIDTH/HEIGHT values defined above.  These bounds are
+ * only considered valid when the isCachedDestValid flag is JNI_TRUE.
+ */
+static SurfaceDataBounds cachedDestBounds;
+
+/**
+ * This flag indicates whether the "cached destination" texture contains
+ * valid data.  This flag is reset to JNI_FALSE at the beginning of every
+ * call to OGLTR_DrawGlyphList().  Once we copy valid destination data
+ * into the cached texture, this flag is set to JNI_TRUE.  This way, we can
+ * limit the number of times we need to copy destination data, which is a
+ * very costly operation.
+ */
+static jboolean isCachedDestValid = JNI_FALSE;
+
+/**
+ * The bounds of the previously rendered LCD glyph, in destination
+ * coordinate space.  We use these bounds to determine whether the glyph
+ * currently being rendered overlaps the previously rendered glyph (i.e.
+ * its bounding box intersects that of the previously rendered glyph).  If
+ * so, we need to re-read the destination area associated with that previous
+ * glyph so that we can correctly blend with the actual destination data.
+ */
+static SurfaceDataBounds previousGlyphBounds;
+
+/**
+ * Initializes the one glyph cache (texture and data structure).
+ * If lcdCache is JNI_TRUE, the texture will contain RGB data,
+ * otherwise we will simply store the grayscale/monochrome glyph images
+ * as intensity values (which work well with the GL_MODULATE function).
+ */
+static jboolean
+OGLTR_InitGlyphCache(jboolean lcdCache)
+{
+    GlyphCacheInfo *gcinfo;
+    GLclampf priority = 1.0f;
+    GLenum internalFormat = lcdCache ? GL_RGB8 : GL_INTENSITY8;
+    GLenum pixelFormat = lcdCache ? GL_RGB : GL_LUMINANCE;
+
+    J2dTraceLn(J2D_TRACE_INFO, "OGLTR_InitGlyphCache");
+
+    // init glyph cache data structure
+    gcinfo = AccelGlyphCache_Init(OGLTR_CACHE_WIDTH,
+                                  OGLTR_CACHE_HEIGHT,
+                                  OGLTR_CACHE_CELL_WIDTH,
+                                  OGLTR_CACHE_CELL_HEIGHT,
+                                  OGLVertexCache_FlushVertexCache);
+    if (gcinfo == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+                      "OGLTR_InitGlyphCache: could not init OGL glyph cache");
+        return JNI_FALSE;
+    }
+
+    // init cache texture object
+    j2d_glGenTextures(1, &gcinfo->cacheID);
+    j2d_glBindTexture(GL_TEXTURE_2D, gcinfo->cacheID);
+    j2d_glPrioritizeTextures(1, &gcinfo->cacheID, &priority);
+    j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+    j2d_glTexImage2D(GL_TEXTURE_2D, 0, internalFormat,
+                     OGLTR_CACHE_WIDTH, OGLTR_CACHE_HEIGHT, 0,
+                     pixelFormat, GL_UNSIGNED_BYTE, NULL);
+
+    if (lcdCache) {
+        glyphCacheLCD = gcinfo;
+    } else {
+        glyphCacheAA = gcinfo;
+    }
+
+    return JNI_TRUE;
+}
+
+/**
+ * Adds the given glyph to the glyph cache (texture and data structure)
+ * associated with the given OGLContext.
+ */
+static void
+OGLTR_AddToGlyphCache(GlyphInfo *glyph, GLenum pixelFormat)
+{
+    CacheCellInfo *ccinfo;
+    GlyphCacheInfo *gcinfo;
+
+    J2dTraceLn(J2D_TRACE_INFO, "OGLTR_AddToGlyphCache");
+
+    if (pixelFormat == GL_LUMINANCE) {
+        gcinfo = glyphCacheAA;
+    } else {
+        gcinfo = glyphCacheLCD;
+    }
+
+    if ((gcinfo == NULL) || (glyph->image == NULL)) {
+        return;
+    }
+
+    AccelGlyphCache_AddGlyph(gcinfo, glyph);
+    ccinfo = (CacheCellInfo *) glyph->cellInfo;
+
+    if (ccinfo != NULL) {
+        // store glyph image in texture cell
+        j2d_glTexSubImage2D(GL_TEXTURE_2D, 0,
+                            ccinfo->x, ccinfo->y,
+                            glyph->width, glyph->height,
+                            pixelFormat, GL_UNSIGNED_BYTE, glyph->image);
+    }
+}
+
+/**
+ * This is the GLSL fragment shader source code for rendering LCD-optimized
+ * text.  Do not be frightened; it is much easier to understand than the
+ * equivalent ASM-like fragment program!
+ *
+ * The "uniform" variables at the top are initialized once the program is
+ * linked, and are updated at runtime as needed (e.g. when the source color
+ * changes, we will modify the "src_adj" value in OGLTR_UpdateLCDTextColor()).
+ *
+ * The "main" function is executed for each "fragment" (or pixel) in the
+ * glyph image. The pow() routine operates on vectors, gives precise results,
+ * and provides acceptable level of performance, so we use it to perform
+ * the gamma adjustment.
+ *
+ * The variables involved in the equation can be expressed as follows:
+ *
+ *   Cs = Color component of the source (foreground color) [0.0, 1.0]
+ *   Cd = Color component of the destination (background color) [0.0, 1.0]
+ *   Cr = Color component to be written to the destination [0.0, 1.0]
+ *   Ag = Glyph alpha (aka intensity or coverage) [0.0, 1.0]
+ *   Ga = Gamma adjustment in the range [1.0, 2.5]
+ *   (^ means raised to the power)
+ *
+ * And here is the theoretical equation approximated by this shader:
+ *
+ *            Cr = (Ag*(Cs^Ga) + (1-Ag)*(Cd^Ga)) ^ (1/Ga)
+ */
+static const char *lcdTextShaderSource =
+    "uniform vec3 src_adj;"
+    "uniform sampler2D glyph_tex;"
+    "uniform sampler2D dst_tex;"
+    "uniform vec3 gamma;"
+    "uniform vec3 invgamma;"
+    ""
+    "void main(void)"
+    "{"
+         // load the RGB value from the glyph image at the current texcoord
+    "    vec3 glyph_clr = vec3(texture2D(glyph_tex, gl_TexCoord[0].st));"
+    "    if (glyph_clr == vec3(0.0)) {"
+             // zero coverage, so skip this fragment
+    "        discard;"
+    "    }"
+         // load the RGB value from the corresponding destination pixel
+    "    vec3 dst_clr = vec3(texture2D(dst_tex, gl_TexCoord[1].st));"
+         // gamma adjust the dest color
+    "    vec3 dst_adj = pow(dst_clr.rgb, gamma);"
+         // linearly interpolate the three color values
+    "    vec3 result = mix(dst_adj, src_adj, glyph_clr);"
+         // gamma re-adjust the resulting color (alpha is always set to 1.0)
+    "    gl_FragColor = vec4(pow(result.rgb, invgamma), 1.0);"
+    "}";
+
+/**
+ * Compiles and links the LCD text shader program.  If successful, this
+ * function returns a handle to the newly created shader program; otherwise
+ * returns 0.
+ */
+static GLhandleARB
+OGLTR_CreateLCDTextProgram()
+{
+    GLhandleARB lcdTextProgram;
+    GLint loc;
+
+    J2dTraceLn(J2D_TRACE_INFO, "OGLTR_CreateLCDTextProgram");
+
+    lcdTextProgram = OGLContext_CreateFragmentProgram(lcdTextShaderSource);
+    if (lcdTextProgram == 0) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+                      "OGLTR_CreateLCDTextProgram: error creating program");
+        return 0;
+    }
+
+    // "use" the program object temporarily so that we can set the uniforms
+    j2d_glUseProgramObjectARB(lcdTextProgram);
+
+    // set the "uniform" values
+    loc = j2d_glGetUniformLocationARB(lcdTextProgram, "glyph_tex");
+    j2d_glUniform1iARB(loc, 0); // texture unit 0
+    loc = j2d_glGetUniformLocationARB(lcdTextProgram, "dst_tex");
+    j2d_glUniform1iARB(loc, 1); // texture unit 1
+
+    // "unuse" the program object; it will be re-bound later as needed
+    j2d_glUseProgramObjectARB(0);
+
+    return lcdTextProgram;
+}
+
+/**
+ * (Re)Initializes the gamma related uniforms.
+ *
+ * The given contrast value is an int in the range [100, 250] which we will
+ * then scale to fit in the range [1.0, 2.5].
+ */
+static jboolean
+OGLTR_UpdateLCDTextContrast(jint contrast)
+{
+    double g = ((double)contrast) / 100.0;
+    double ig = 1.0 / g;
+    GLint loc;
+
+    J2dTraceLn1(J2D_TRACE_INFO,
+                "OGLTR_UpdateLCDTextContrast: contrast=%d", contrast);
+
+    loc = j2d_glGetUniformLocationARB(lcdTextProgram, "gamma");
+    j2d_glUniform3fARB(loc, g, g, g);
+
+    loc = j2d_glGetUniformLocationARB(lcdTextProgram, "invgamma");
+    j2d_glUniform3fARB(loc, ig, ig, ig);
+
+    return JNI_TRUE;
+}
+
+/**
+ * Updates the current gamma-adjusted source color ("src_adj") of the LCD
+ * text shader program.  Note that we could calculate this value in the
+ * shader (e.g. just as we do for "dst_adj"), but would be unnecessary work
+ * (and a measurable performance hit, maybe around 5%) since this value is
+ * constant over the entire glyph list.  So instead we just calculate the
+ * gamma-adjusted value once and update the uniform parameter of the LCD
+ * shader as needed.
+ */
+static jboolean
+OGLTR_UpdateLCDTextColor(jint contrast)
+{
+    double gamma = ((double)contrast) / 100.0;
+    GLfloat radj, gadj, badj;
+    GLfloat clr[4];
+    GLint loc;
+
+    J2dTraceLn1(J2D_TRACE_INFO,
+                "OGLTR_UpdateLCDTextColor: contrast=%d", contrast);
+
+    /*
+     * Note: Ideally we would update the "src_adj" uniform parameter only
+     * when there is a change in the source color.  Fortunately, the cost
+     * of querying the current OpenGL color state and updating the uniform
+     * value is quite small, and in the common case we only need to do this
+     * once per GlyphList, so we gain little from trying to optimize too
+     * eagerly here.
+     */
+
+    // get the current OpenGL primary color state
+    j2d_glGetFloatv(GL_CURRENT_COLOR, clr);
+
+    // gamma adjust the primary color
+    radj = (GLfloat)pow(clr[0], gamma);
+    gadj = (GLfloat)pow(clr[1], gamma);
+    badj = (GLfloat)pow(clr[2], gamma);
+
+    // update the "src_adj" parameter of the shader program with this value
+    loc = j2d_glGetUniformLocationARB(lcdTextProgram, "src_adj");
+    j2d_glUniform3fARB(loc, radj, gadj, badj);
+
+    return JNI_TRUE;
+}
+
+/**
+ * Enables the LCD text shader and updates any related state, such as the
+ * gamma lookup table textures.
+ */
+static jboolean
+OGLTR_EnableLCDGlyphModeState(GLuint glyphTextureID,
+                              GLuint dstTextureID,
+                              jint contrast)
+{
+    // bind the texture containing glyph data to texture unit 0
+    j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
+    j2d_glBindTexture(GL_TEXTURE_2D, glyphTextureID);
+    j2d_glEnable(GL_TEXTURE_2D);
+
+    // bind the texture tile containing destination data to texture unit 1
+    j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
+    if (dstTextureID != 0) {
+        j2d_glBindTexture(GL_TEXTURE_2D, dstTextureID);
+    } else {
+        if (cachedDestTextureID == 0) {
+            cachedDestTextureID =
+                OGLContext_CreateBlitTexture(GL_RGB8, GL_RGB,
+                                             OGLTR_CACHED_DEST_WIDTH,
+                                             OGLTR_CACHED_DEST_HEIGHT);
+            if (cachedDestTextureID == 0) {
+                return JNI_FALSE;
+            }
+        }
+        j2d_glBindTexture(GL_TEXTURE_2D, cachedDestTextureID);
+    }
+
+    // note that GL_TEXTURE_2D was already enabled for texture unit 0,
+    // but we need to explicitly enable it for texture unit 1
+    j2d_glEnable(GL_TEXTURE_2D);
+
+    // create the LCD text shader, if necessary
+    if (lcdTextProgram == 0) {
+        lcdTextProgram = OGLTR_CreateLCDTextProgram();
+        if (lcdTextProgram == 0) {
+            return JNI_FALSE;
+        }
+    }
+
+    // enable the LCD text shader
+    j2d_glUseProgramObjectARB(lcdTextProgram);
+
+    // update the current contrast settings, if necessary
+    if (lastLCDContrast != contrast) {
+        if (!OGLTR_UpdateLCDTextContrast(contrast)) {
+            return JNI_FALSE;
+        }
+        lastLCDContrast = contrast;
+    }
+
+    // update the current color settings
+    if (!OGLTR_UpdateLCDTextColor(contrast)) {
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+void
+OGLTR_EnableGlyphVertexCache(OGLContext *oglc)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "OGLTR_EnableGlyphVertexCache");
+
+    if (!OGLVertexCache_InitVertexCache(oglc)) {
+        return;
+    }
+
+    if (glyphCacheAA == NULL) {
+        if (!OGLTR_InitGlyphCache(JNI_FALSE)) {
+            return;
+        }
+    }
+
+    j2d_glEnable(GL_TEXTURE_2D);
+    j2d_glBindTexture(GL_TEXTURE_2D, glyphCacheAA->cacheID);
+    j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+    // for grayscale/monochrome text, the current OpenGL source color
+    // is modulated with the glyph image as part of the texture
+    // application stage, so we use GL_MODULATE here
+    OGLC_UPDATE_TEXTURE_FUNCTION(oglc, GL_MODULATE);
+}
+
+void
+OGLTR_DisableGlyphVertexCache(OGLContext *oglc)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DisableGlyphVertexCache");
+
+    OGLVertexCache_FlushVertexCache();
+    OGLVertexCache_RestoreColorState(oglc);
+
+    j2d_glDisable(GL_TEXTURE_2D);
+    j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+    j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
+    j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
+    j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+}
+
+/**
+ * Disables any pending state associated with the current "glyph mode".
+ */
+static void
+OGLTR_DisableGlyphModeState()
+{
+    switch (glyphMode) {
+    case MODE_NO_CACHE_LCD:
+        j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
+        j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
+        /* FALLTHROUGH */
+
+    case MODE_USE_CACHE_LCD:
+        j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+        j2d_glUseProgramObjectARB(0);
+        j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
+        j2d_glDisable(GL_TEXTURE_2D);
+        j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
+        j2d_glDisable(GL_TEXTURE_2D);
+        break;
+
+    case MODE_NO_CACHE_GRAY:
+    case MODE_USE_CACHE_GRAY:
+    case MODE_NOT_INITED:
+    default:
+        break;
+    }
+}
+
+static jboolean
+OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc,
+                                 GlyphInfo *ginfo, jint x, jint y)
+{
+    CacheCellInfo *cell;
+    jfloat x1, y1, x2, y2;
+
+    if (glyphMode != MODE_USE_CACHE_GRAY) {
+        OGLTR_DisableGlyphModeState();
+        CHECK_PREVIOUS_OP(OGL_STATE_GLYPH_OP);
+        glyphMode = MODE_USE_CACHE_GRAY;
+    }
+
+    if (ginfo->cellInfo == NULL) {
+        // attempt to add glyph to accelerated glyph cache
+        OGLTR_AddToGlyphCache(ginfo, GL_LUMINANCE);
+
+        if (ginfo->cellInfo == NULL) {
+            // we'll just no-op in the rare case that the cell is NULL
+            return JNI_TRUE;
+        }
+    }
+
+    cell = (CacheCellInfo *) (ginfo->cellInfo);
+    cell->timesRendered++;
+
+    x1 = (jfloat)x;
+    y1 = (jfloat)y;
+    x2 = x1 + ginfo->width;
+    y2 = y1 + ginfo->height;
+
+    OGLVertexCache_AddGlyphQuad(oglc,
+                                cell->tx1, cell->ty1,
+                                cell->tx2, cell->ty2,
+                                x1, y1, x2, y2);
+
+    return JNI_TRUE;
+}
+
+/**
+ * Evaluates to true if the rectangle defined by gx1/gy1/gx2/gy2 is
+ * inside outerBounds.
+ */
+#define INSIDE(gx1, gy1, gx2, gy2, outerBounds) \
+    (((gx1) >= outerBounds.x1) && ((gy1) >= outerBounds.y1) && \
+     ((gx2) <= outerBounds.x2) && ((gy2) <= outerBounds.y2))
+
+/**
+ * Evaluates to true if the rectangle defined by gx1/gy1/gx2/gy2 intersects
+ * the rectangle defined by bounds.
+ */
+#define INTERSECTS(gx1, gy1, gx2, gy2, bounds) \
+    ((bounds.x2 > (gx1)) && (bounds.y2 > (gy1)) && \
+     (bounds.x1 < (gx2)) && (bounds.y1 < (gy2)))
+
+/**
+ * This method checks to see if the given LCD glyph bounds fall within the
+ * cached destination texture bounds.  If so, this method can return
+ * immediately.  If not, this method will copy a chunk of framebuffer data
+ * into the cached destination texture and then update the current cached
+ * destination bounds before returning.
+ */
+static void
+OGLTR_UpdateCachedDestination(OGLSDOps *dstOps, GlyphInfo *ginfo,
+                              jint gx1, jint gy1, jint gx2, jint gy2,
+                              jint glyphIndex, jint totalGlyphs)
+{
+    jint dx1, dy1, dx2, dy2;
+    jint dx1adj, dy1adj;
+
+    if (isCachedDestValid && INSIDE(gx1, gy1, gx2, gy2, cachedDestBounds)) {
+        // glyph is already within the cached destination bounds; no need
+        // to read back the entire destination region again, but we do
+        // need to see if the current glyph overlaps the previous glyph...
+
+        if (INTERSECTS(gx1, gy1, gx2, gy2, previousGlyphBounds)) {
+            // the current glyph overlaps the destination region touched
+            // by the previous glyph, so now we need to read back the part
+            // of the destination corresponding to the previous glyph
+            dx1 = previousGlyphBounds.x1;
+            dy1 = previousGlyphBounds.y1;
+            dx2 = previousGlyphBounds.x2;
+            dy2 = previousGlyphBounds.y2;
+
+            // this accounts for lower-left origin of the destination region
+            dx1adj = dstOps->xOffset + dx1;
+            dy1adj = dstOps->yOffset + dstOps->height - dy2;
+
+            // copy destination into subregion of cached texture tile:
+            //   dx1-cachedDestBounds.x1 == +xoffset from left side of texture
+            //   cachedDestBounds.y2-dy2 == +yoffset from bottom of texture
+            j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
+            j2d_glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
+                                    dx1 - cachedDestBounds.x1,
+                                    cachedDestBounds.y2 - dy2,
+                                    dx1adj, dy1adj,
+                                    dx2-dx1, dy2-dy1);
+        }
+    } else {
+        jint remainingWidth;
+
+        // destination region is not valid, so we need to read back a
+        // chunk of the destination into our cached texture
+
+        // position the upper-left corner of the destination region on the
+        // "top" line of glyph list
+        // REMIND: this isn't ideal; it would be better if we had some idea
+        //         of the bounding box of the whole glyph list (this is
+        //         do-able, but would require iterating through the whole
+        //         list up front, which may present its own problems)
+        dx1 = gx1;
+        dy1 = gy1;
+
+        if (ginfo->advanceX > 0) {
+            // estimate the width based on our current position in the glyph
+            // list and using the x advance of the current glyph (this is just
+            // a quick and dirty heuristic; if this is a "thin" glyph image,
+            // then we're likely to underestimate, and if it's "thick" then we
+            // may end up reading back more than we need to)
+            remainingWidth =
+                (jint)(ginfo->advanceX * (totalGlyphs - glyphIndex));
+            if (remainingWidth > OGLTR_CACHED_DEST_WIDTH) {
+                remainingWidth = OGLTR_CACHED_DEST_WIDTH;
+            } else if (remainingWidth < ginfo->width) {
+                // in some cases, the x-advance may be slightly smaller
+                // than the actual width of the glyph; if so, adjust our
+                // estimate so that we can accommodate the entire glyph
+                remainingWidth = ginfo->width;
+            }
+        } else {
+            // a negative advance is possible when rendering rotated text,
+            // in which case it is difficult to estimate an appropriate
+            // region for readback, so we will pick a region that
+            // encompasses just the current glyph
+            remainingWidth = ginfo->width;
+        }
+        dx2 = dx1 + remainingWidth;
+
+        // estimate the height (this is another sloppy heuristic; we'll
+        // make the cached destination region tall enough to encompass most
+        // glyphs that are small enough to fit in the glyph cache, and then
+        // we add a little something extra to account for descenders
+        dy2 = dy1 + OGLTR_CACHE_CELL_HEIGHT + 2;
+
+        // this accounts for lower-left origin of the destination region
+        dx1adj = dstOps->xOffset + dx1;
+        dy1adj = dstOps->yOffset + dstOps->height - dy2;
+
+        // copy destination into cached texture tile (the lower-left corner
+        // of the destination region will be positioned at the lower-left
+        // corner (0,0) of the texture)
+        j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
+        j2d_glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
+                                0, 0, dx1adj, dy1adj,
+                                dx2-dx1, dy2-dy1);
+
+        // update the cached bounds and mark it valid
+        cachedDestBounds.x1 = dx1;
+        cachedDestBounds.y1 = dy1;
+        cachedDestBounds.x2 = dx2;
+        cachedDestBounds.y2 = dy2;
+        isCachedDestValid = JNI_TRUE;
+    }
+
+    // always update the previous glyph bounds
+    previousGlyphBounds.x1 = gx1;
+    previousGlyphBounds.y1 = gy1;
+    previousGlyphBounds.x2 = gx2;
+    previousGlyphBounds.y2 = gy2;
+}
+
+static jboolean
+OGLTR_DrawLCDGlyphViaCache(OGLContext *oglc, OGLSDOps *dstOps,
+                           GlyphInfo *ginfo, jint x, jint y,
+                           jint glyphIndex, jint totalGlyphs,
+                           jboolean rgbOrder, jint contrast,
+                            GLuint dstTextureID)
+{
+    CacheCellInfo *cell;
+    jint dx1, dy1, dx2, dy2;
+    jfloat dtx1, dty1, dtx2, dty2;
+
+    if (glyphMode != MODE_USE_CACHE_LCD) {
+        OGLTR_DisableGlyphModeState();
+        CHECK_PREVIOUS_OP(GL_TEXTURE_2D);
+        j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+        if (glyphCacheLCD == NULL) {
+            if (!OGLTR_InitGlyphCache(JNI_TRUE)) {
+                return JNI_FALSE;
+            }
+        }
+
+        if (rgbOrder != lastRGBOrder) {
+            // need to invalidate the cache in this case; see comments
+            // for lastRGBOrder above
+            AccelGlyphCache_Invalidate(glyphCacheLCD);
+            lastRGBOrder = rgbOrder;
+        }
+
+        if (!OGLTR_EnableLCDGlyphModeState(glyphCacheLCD->cacheID,
+                                           dstTextureID, contrast))
+        {
+            return JNI_FALSE;
+        }
+
+        // when a fragment shader is enabled, the texture function state is
+        // ignored, so the following line is not needed...
+        // OGLC_UPDATE_TEXTURE_FUNCTION(oglc, GL_MODULATE);
+
+        glyphMode = MODE_USE_CACHE_LCD;
+    }
+
+    if (ginfo->cellInfo == NULL) {
+        // rowBytes will always be a multiple of 3, so the following is safe
+        j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, ginfo->rowBytes / 3);
+
+        // make sure the glyph cache texture is bound to texture unit 0
+        j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
+
+        // attempt to add glyph to accelerated glyph cache
+        OGLTR_AddToGlyphCache(ginfo, rgbOrder ? GL_RGB : GL_BGR);
+
+        if (ginfo->cellInfo == NULL) {
+            // we'll just no-op in the rare case that the cell is NULL
+            return JNI_TRUE;
+        }
+    }
+
+    cell = (CacheCellInfo *) (ginfo->cellInfo);
+    cell->timesRendered++;
+
+    // location of the glyph in the destination's coordinate space
+    dx1 = x;
+    dy1 = y;
+    dx2 = dx1 + ginfo->width;
+    dy2 = dy1 + ginfo->height;
+
+    if (dstTextureID == 0) {
+        // copy destination into second cached texture, if necessary
+        OGLTR_UpdateCachedDestination(dstOps, ginfo,
+                                      dx1, dy1, dx2, dy2,
+                                      glyphIndex, totalGlyphs);
+
+        // texture coordinates of the destination tile
+        dtx1 = ((jfloat)(dx1 - cachedDestBounds.x1)) / OGLTR_CACHED_DEST_WIDTH;
+        dty1 = ((jfloat)(cachedDestBounds.y2 - dy1)) / OGLTR_CACHED_DEST_HEIGHT;
+        dtx2 = ((jfloat)(dx2 - cachedDestBounds.x1)) / OGLTR_CACHED_DEST_WIDTH;
+        dty2 = ((jfloat)(cachedDestBounds.y2 - dy2)) / OGLTR_CACHED_DEST_HEIGHT;
+    } else {
+        jint gw = ginfo->width;
+        jint gh = ginfo->height;
+
+        // this accounts for lower-left origin of the destination region
+        jint dxadj = dstOps->xOffset + x;
+        jint dyadj = dstOps->yOffset + dstOps->height - (y + gh);
+
+        // update the remaining destination texture coordinates
+        dtx1 =((GLfloat)dxadj) / dstOps->textureWidth;
+        dtx2 = ((GLfloat)dxadj + gw) / dstOps->textureWidth;
+
+        dty1 = ((GLfloat)dyadj + gh) / dstOps->textureHeight;
+        dty2 = ((GLfloat)dyadj) / dstOps->textureHeight;
+
+        j2d_glTextureBarrierNV();
+    }
+
+    // render composed texture to the destination surface
+    j2d_glBegin(GL_QUADS);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx1, cell->ty1);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty1);
+    j2d_glVertex2i(dx1, dy1);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx2, cell->ty1);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty1);
+    j2d_glVertex2i(dx2, dy1);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx2, cell->ty2);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty2);
+    j2d_glVertex2i(dx2, dy2);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, cell->tx1, cell->ty2);
+    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty2);
+    j2d_glVertex2i(dx1, dy2);
+    j2d_glEnd();
+
+    return JNI_TRUE;
+}
+
+static jboolean
+OGLTR_DrawGrayscaleGlyphNoCache(OGLContext *oglc,
+                                GlyphInfo *ginfo, jint x, jint y)
+{
+    jint tw, th;
+    jint sx, sy, sw, sh;
+    jint x0;
+    jint w = ginfo->width;
+    jint h = ginfo->height;
+
+    if (glyphMode != MODE_NO_CACHE_GRAY) {
+        OGLTR_DisableGlyphModeState();
+        CHECK_PREVIOUS_OP(OGL_STATE_MASK_OP);
+        glyphMode = MODE_NO_CACHE_GRAY;
+    }
+
+    x0 = x;
+    tw = OGLVC_MASK_CACHE_TILE_WIDTH;
+    th = OGLVC_MASK_CACHE_TILE_HEIGHT;
+
+    for (sy = 0; sy < h; sy += th, y += th) {
+        x = x0;
+        sh = ((sy + th) > h) ? (h - sy) : th;
+
+        for (sx = 0; sx < w; sx += tw, x += tw) {
+            sw = ((sx + tw) > w) ? (w - sx) : tw;
+
+            OGLVertexCache_AddMaskQuad(oglc,
+                                       sx, sy, x, y, sw, sh,
+                                       w, ginfo->image);
+        }
+    }
+
+    return JNI_TRUE;
+}
+
+static jboolean
+OGLTR_DrawLCDGlyphNoCache(OGLContext *oglc, OGLSDOps *dstOps,
+                          GlyphInfo *ginfo, jint x, jint y,
+                          jint rowBytesOffset,
+                          jboolean rgbOrder, jint contrast,
+                          GLuint dstTextureID)
+{
+    GLfloat tx1, ty1, tx2, ty2;
+    GLfloat dtx1, dty1, dtx2, dty2;
+    jint tw, th;
+    jint sx, sy, sw, sh, dxadj, dyadj;
+    jint x0;
+    jint w = ginfo->width;
+    jint h = ginfo->height;
+    GLenum pixelFormat = rgbOrder ? GL_RGB : GL_BGR;
+
+    if (glyphMode != MODE_NO_CACHE_LCD) {
+        OGLTR_DisableGlyphModeState();
+        CHECK_PREVIOUS_OP(GL_TEXTURE_2D);
+        j2d_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+        if (oglc->blitTextureID == 0) {
+            if (!OGLContext_InitBlitTileTexture(oglc)) {
+                return JNI_FALSE;
+            }
+        }
+
+        if (!OGLTR_EnableLCDGlyphModeState(oglc->blitTextureID,
+                                           dstTextureID, contrast))
+        {
+            return JNI_FALSE;
+        }
+
+        // when a fragment shader is enabled, the texture function state is
+        // ignored, so the following line is not needed...
+        // OGLC_UPDATE_TEXTURE_FUNCTION(oglc, GL_MODULATE);
+
+        glyphMode = MODE_NO_CACHE_LCD;
+    }
+
+    // rowBytes will always be a multiple of 3, so the following is safe
+    j2d_glPixelStorei(GL_UNPACK_ROW_LENGTH, ginfo->rowBytes / 3);
+
+    x0 = x;
+    tx1 = 0.0f;
+    ty1 = 0.0f;
+    dtx1 = 0.0f;
+    dty2 = 0.0f;
+    tw = OGLTR_NOCACHE_TILE_SIZE;
+    th = OGLTR_NOCACHE_TILE_SIZE;
+
+    for (sy = 0; sy < h; sy += th, y += th) {
+        x = x0;
+        sh = ((sy + th) > h) ? (h - sy) : th;
+
+        for (sx = 0; sx < w; sx += tw, x += tw) {
+            sw = ((sx + tw) > w) ? (w - sx) : tw;
+
+            // update the source pointer offsets
+            j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, sx);
+            j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, sy);
+
+            // copy LCD mask into glyph texture tile
+            j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
+            j2d_glTexSubImage2D(GL_TEXTURE_2D, 0,
+                                0, 0, sw, sh,
+                                pixelFormat, GL_UNSIGNED_BYTE,
+                                ginfo->image + rowBytesOffset);
+
+            // update the lower-right glyph texture coordinates
+            tx2 = ((GLfloat)sw) / OGLC_BLIT_TILE_SIZE;
+            ty2 = ((GLfloat)sh) / OGLC_BLIT_TILE_SIZE;
+
+            // this accounts for lower-left origin of the destination region
+            dxadj = dstOps->xOffset + x;
+            dyadj = dstOps->yOffset + dstOps->height - (y + sh);
+
+            if (dstTextureID == 0) {
+                // copy destination into cached texture tile (the lower-left
+                // corner of the destination region will be positioned at the
+                // lower-left corner (0,0) of the texture)
+                j2d_glActiveTextureARB(GL_TEXTURE1_ARB);
+                j2d_glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
+                                        0, 0,
+                                        dxadj, dyadj,
+                                        sw, sh);
+                // update the remaining destination texture coordinates
+                dtx2 = ((GLfloat)sw) / OGLTR_CACHED_DEST_WIDTH;
+                dty1 = ((GLfloat)sh) / OGLTR_CACHED_DEST_HEIGHT;
+            } else {
+                // use the destination texture directly
+                // update the remaining destination texture coordinates
+                dtx1 =((GLfloat)dxadj) / dstOps->textureWidth;
+                dtx2 = ((GLfloat)dxadj + sw) / dstOps->textureWidth;
+
+                dty1 = ((GLfloat)dyadj + sh) / dstOps->textureHeight;
+                dty2 = ((GLfloat)dyadj) / dstOps->textureHeight;
+
+                j2d_glTextureBarrierNV();
+            }
+
+            // render composed texture to the destination surface
+            j2d_glBegin(GL_QUADS);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx1, ty1);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty1);
+            j2d_glVertex2i(x, y);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx2, ty1);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty1);
+            j2d_glVertex2i(x + sw, y);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx2, ty2);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx2, dty2);
+            j2d_glVertex2i(x + sw, y + sh);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tx1, ty2);
+            j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, dtx1, dty2);
+            j2d_glVertex2i(x, y + sh);
+            j2d_glEnd();
+        }
+    }
+
+    return JNI_TRUE;
+}
+
+// see DrawGlyphList.c for more on this macro...
+#define FLOOR_ASSIGN(l, r) \
+    if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r))
+
+void
+OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
+                    jint totalGlyphs, jboolean usePositions,
+                    jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
+                    jfloat glyphListOrigX, jfloat glyphListOrigY,
+                    unsigned char *images, unsigned char *positions)
+{
+    int glyphCounter;
+    GLuint dstTextureID = 0;
+
+    J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DrawGlyphList");
+
+    RETURN_IF_NULL(oglc);
+    RETURN_IF_NULL(dstOps);
+    RETURN_IF_NULL(images);
+    if (usePositions) {
+        RETURN_IF_NULL(positions);
+    }
+
+    glyphMode = MODE_NOT_INITED;
+    isCachedDestValid = JNI_FALSE;
+
+    // We have to obtain an information about destination content
+    // in order to render lcd glyphs. It could be done by copying
+    // a part of desitination buffer into an intermediate texture
+    // using glCopyTexSubImage2D(). However, on macosx this path is
+    // slow, and it dramatically reduces the overall speed of lcd
+    // text rendering.
+    //
+    // In some cases, we can use a texture from the destination
+    // surface data in oredr to avoid this slow reading routine.
+    // It requires:
+    //  * An appropriate textureTarget for the destination SD.
+    //    In particular, we need GL_TEXTURE_2D
+    //  * Means to prevent read-after-write problem.
+    //    At the moment, a GL_NV_texture_barrier extension is used
+    //    to achieve this.
+    if (OGLC_IS_CAP_PRESENT(oglc, CAPS_EXT_TEXBARRIER) &&
+        dstOps->textureTarget == GL_TEXTURE_2D)
+    {
+        dstTextureID = dstOps->textureID;
+    }
+
+    for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
+        jint x, y;
+        jfloat glyphx, glyphy;
+        jboolean grayscale, ok;
+        GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images));
+
+        if (ginfo == NULL) {
+            // this shouldn't happen, but if it does we'll just break out...
+            J2dRlsTraceLn(J2D_TRACE_ERROR,
+                          "OGLTR_DrawGlyphList: glyph info is null");
+            break;
+        }
+
+        grayscale = (ginfo->rowBytes == ginfo->width);
+
+        if (usePositions) {
+            jfloat posx = NEXT_FLOAT(positions);
+            jfloat posy = NEXT_FLOAT(positions);
+            glyphx = glyphListOrigX + posx + ginfo->topLeftX;
+            glyphy = glyphListOrigY + posy + ginfo->topLeftY;
+            FLOOR_ASSIGN(x, glyphx);
+            FLOOR_ASSIGN(y, glyphy);
+        } else {
+            glyphx = glyphListOrigX + ginfo->topLeftX;
+            glyphy = glyphListOrigY + ginfo->topLeftY;
+            FLOOR_ASSIGN(x, glyphx);
+            FLOOR_ASSIGN(y, glyphy);
+            glyphListOrigX += ginfo->advanceX;
+            glyphListOrigY += ginfo->advanceY;
+        }
+
+        if (ginfo->image == NULL) {
+            continue;
+        }
+
+        if (grayscale) {
+            // grayscale or monochrome glyph data
+            if (ginfo->width <= OGLTR_CACHE_CELL_WIDTH &&
+                ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
+            {
+                ok = OGLTR_DrawGrayscaleGlyphViaCache(oglc, ginfo, x, y);
+            } else {
+                ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y);
+            }
+        } else {
+            // LCD-optimized glyph data
+            jint rowBytesOffset = 0;
+
+            if (subPixPos) {
+                jint frac = (jint)((glyphx - x) * 3);
+                if (frac != 0) {
+                    rowBytesOffset = 3 - frac;
+                    x += 1;
+                }
+            }
+
+            if (rowBytesOffset == 0 &&
+                ginfo->width <= OGLTR_CACHE_CELL_WIDTH &&
+                ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
+            {
+                ok = OGLTR_DrawLCDGlyphViaCache(oglc, dstOps,
+                                                ginfo, x, y,
+                                                glyphCounter, totalGlyphs,
+                                                rgbOrder, lcdContrast,
+                                                dstTextureID);
+            } else {
+                ok = OGLTR_DrawLCDGlyphNoCache(oglc, dstOps,
+                                               ginfo, x, y,
+                                               rowBytesOffset,
+                                               rgbOrder, lcdContrast,
+                                               dstTextureID);
+            }
+        }
+
+        if (!ok) {
+            break;
+        }
+    }
+
+    OGLTR_DisableGlyphModeState();
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_opengl_OGLTextRenderer_drawGlyphList
+    (JNIEnv *env, jobject self,
+     jint numGlyphs, jboolean usePositions,
+     jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
+     jfloat glyphListOrigX, jfloat glyphListOrigY,
+     jlongArray imgArray, jfloatArray posArray)
+{
+    unsigned char *images;
+
+    J2dTraceLn(J2D_TRACE_INFO, "OGLTextRenderer_drawGlyphList");
+
+    images = (unsigned char *)
+        (*env)->GetPrimitiveArrayCritical(env, imgArray, NULL);
+    if (images != NULL) {
+        OGLContext *oglc = OGLRenderQueue_GetCurrentContext();
+        OGLSDOps *dstOps = OGLRenderQueue_GetCurrentDestination();
+
+        if (usePositions) {
+            unsigned char *positions = (unsigned char *)
+                (*env)->GetPrimitiveArrayCritical(env, posArray, NULL);
+            if (positions != NULL) {
+                OGLTR_DrawGlyphList(env, oglc, dstOps,
+                                    numGlyphs, usePositions,
+                                    subPixPos, rgbOrder, lcdContrast,
+                                    glyphListOrigX, glyphListOrigY,
+                                    images, positions);
+                (*env)->ReleasePrimitiveArrayCritical(env, posArray,
+                                                      positions, JNI_ABORT);
+            }
+        } else {
+            OGLTR_DrawGlyphList(env, oglc, dstOps,
+                                numGlyphs, usePositions,
+                                subPixPos, rgbOrder, lcdContrast,
+                                glyphListOrigX, glyphListOrigY,
+                                images, NULL);
+        }
+
+        // 6358147: reset current state, and ensure rendering is
+        // flushed to dest
+        if (oglc != NULL) {
+            RESET_PREVIOUS_OP();
+            j2d_glFlush();
+        }
+
+        (*env)->ReleasePrimitiveArrayCritical(env, imgArray,
+                                              images, JNI_ABORT);
+    }
+}
+
+#endif /* !HEADLESS */