src/java.desktop/share/native/common/java2d/opengl/OGLRenderer.c
author ihse
Sat, 03 Mar 2018 08:21:47 +0100
branchihse-warnings-cflags-branch
changeset 56230 489867818774
parent 47216 71c04702a3d5
permissions -rw-r--r--
No longer disable E_OLD_STYLE_FUNC_DEF.

/*
 * Copyright (c) 2003, 2008, 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 <jlong.h>
#include <jni_util.h>
#include <math.h>

#include "sun_java2d_opengl_OGLRenderer.h"

#include "OGLRenderer.h"
#include "OGLRenderQueue.h"
#include "OGLSurfaceData.h"

/**
 * Note: Some of the methods in this file apply a "magic number"
 * translation to line segments.  The OpenGL specification lays out the
 * "diamond exit rule" for line rasterization, but it is loose enough to
 * allow for a wide range of line rendering hardware.  (It appears that
 * some hardware, such as the Nvidia GeForce2 series, does not even meet
 * the spec in all cases.)  As such it is difficult to find a mapping
 * between the Java2D and OpenGL line specs that works consistently across
 * all hardware combinations.
 *
 * Therefore the "magic numbers" you see here have been empirically derived
 * after testing on a variety of graphics hardware in order to find some
 * reasonable middle ground between the two specifications.  The general
 * approach is to apply a fractional translation to vertices so that they
 * hit pixel centers and therefore touch the same pixels as in our other
 * pipelines.  Emphasis was placed on finding values so that OGL lines with
 * a slope of +/- 1 hit all the same pixels as our other (software) loops.
 * The stepping in other diagonal lines rendered with OGL may deviate
 * slightly from those rendered with our software loops, but the most
 * important thing is that these magic numbers ensure that all OGL lines
 * hit the same endpoints as our software loops.
 *
 * If you find it necessary to change any of these magic numbers in the
 * future, just be sure that you test the changes across a variety of
 * hardware to ensure consistent rendering everywhere.
 */

void
OGLRenderer_DrawLine(OGLContext *oglc, jint x1, jint y1, jint x2, jint y2)
{
    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_DrawLine");

    RETURN_IF_NULL(oglc);

    CHECK_PREVIOUS_OP(GL_LINES);

    if (y1 == y2) {
        // horizontal
        GLfloat fx1 = (GLfloat)x1;
        GLfloat fx2 = (GLfloat)x2;
        GLfloat fy  = ((GLfloat)y1) + 0.2f;

        if (x1 > x2) {
            GLfloat t = fx1; fx1 = fx2; fx2 = t;
        }

        j2d_glVertex2f(fx1+0.2f, fy);
        j2d_glVertex2f(fx2+1.2f, fy);
    } else if (x1 == x2) {
        // vertical
        GLfloat fx  = ((GLfloat)x1) + 0.2f;
        GLfloat fy1 = (GLfloat)y1;
        GLfloat fy2 = (GLfloat)y2;

        if (y1 > y2) {
            GLfloat t = fy1; fy1 = fy2; fy2 = t;
        }

        j2d_glVertex2f(fx, fy1+0.2f);
        j2d_glVertex2f(fx, fy2+1.2f);
    } else {
        // diagonal
        GLfloat fx1 = (GLfloat)x1;
        GLfloat fy1 = (GLfloat)y1;
        GLfloat fx2 = (GLfloat)x2;
        GLfloat fy2 = (GLfloat)y2;

        if (x1 < x2) {
            fx1 += 0.2f;
            fx2 += 1.0f;
        } else {
            fx1 += 0.8f;
            fx2 -= 0.2f;
        }

        if (y1 < y2) {
            fy1 += 0.2f;
            fy2 += 1.0f;
        } else {
            fy1 += 0.8f;
            fy2 -= 0.2f;
        }

        j2d_glVertex2f(fx1, fy1);
        j2d_glVertex2f(fx2, fy2);
    }
}

void
OGLRenderer_DrawRect(OGLContext *oglc, jint x, jint y, jint w, jint h)
{
    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_DrawRect");

    if (w < 0 || h < 0) {
        return;
    }

    RETURN_IF_NULL(oglc);

    if (w < 2 || h < 2) {
        // If one dimension is less than 2 then there is no
        // gap in the middle - draw a solid filled rectangle.
        CHECK_PREVIOUS_OP(GL_QUADS);
        GLRECT_BODY_XYWH(x, y, w+1, h+1);
    } else {
        GLfloat fx1 = ((GLfloat)x) + 0.2f;
        GLfloat fy1 = ((GLfloat)y) + 0.2f;
        GLfloat fx2 = fx1 + ((GLfloat)w);
        GLfloat fy2 = fy1 + ((GLfloat)h);

        // Avoid drawing the endpoints twice.
        // Also prefer including the endpoints in the
        // horizontal sections which draw pixels faster.

        CHECK_PREVIOUS_OP(GL_LINES);
        // top
        j2d_glVertex2f(fx1,      fy1);
        j2d_glVertex2f(fx2+1.0f, fy1);
        // right
        j2d_glVertex2f(fx2,      fy1+1.0f);
        j2d_glVertex2f(fx2,      fy2);
        // bottom
        j2d_glVertex2f(fx1,      fy2);
        j2d_glVertex2f(fx2+1.0f, fy2);
        // left
        j2d_glVertex2f(fx1,      fy1+1.0f);
        j2d_glVertex2f(fx1,      fy2);
    }
}

void
OGLRenderer_DrawPoly(OGLContext *oglc,
                     jint nPoints, jint isClosed,
                     jint transX, jint transY,
                     jint *xPoints, jint *yPoints)
{
    jboolean isEmpty = JNI_TRUE;
    jint mx, my;
    jint i;

    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_DrawPoly");

    if (xPoints == NULL || yPoints == NULL) {
        J2dRlsTraceLn(J2D_TRACE_ERROR,
                      "OGLRenderer_DrawPoly: points array is null");
        return;
    }

    RETURN_IF_NULL(oglc);

    // Note that BufferedRenderPipe.drawPoly() has already rejected polys
    // with nPoints<2, so we can be certain here that we have nPoints>=2.

    mx = xPoints[0];
    my = yPoints[0];

    CHECK_PREVIOUS_OP(GL_LINE_STRIP);
    for (i = 0; i < nPoints; i++) {
        jint x = xPoints[i];
        jint y = yPoints[i];

        isEmpty = isEmpty && (x == mx && y == my);

        // Translate each vertex by a fraction so that we hit pixel centers.
        j2d_glVertex2f((GLfloat)(x + transX) + 0.5f,
                       (GLfloat)(y + transY) + 0.5f);
    }

    if (isClosed && !isEmpty &&
        (xPoints[nPoints-1] != mx ||
         yPoints[nPoints-1] != my))
    {
        // In this case, the polyline's start and end positions are
        // different and need to be closed manually; we do this by adding
        // one more segment back to the starting position.  Note that we
        // do not need to fill in the last pixel (as we do in the following
        // block) because we are returning to the starting pixel, which
        // has already been filled in.
        j2d_glVertex2f((GLfloat)(mx + transX) + 0.5f,
                       (GLfloat)(my + transY) + 0.5f);
        RESET_PREVIOUS_OP(); // so that we don't leave the line strip open
    } else if (!isClosed || isEmpty) {
        // OpenGL omits the last pixel in a polyline, so we fix this by
        // adding a one-pixel segment at the end.  Also, if the polyline
        // never went anywhere (isEmpty is true), we need to use this
        // workaround to ensure that a single pixel is touched.
        CHECK_PREVIOUS_OP(GL_LINES); // this closes the line strip first
        mx = xPoints[nPoints-1] + transX;
        my = yPoints[nPoints-1] + transY;
        j2d_glVertex2i(mx, my);
        j2d_glVertex2i(mx+1, my+1);
        // no need for RESET_PREVIOUS_OP, as the line strip is no longer open
    } else {
        RESET_PREVIOUS_OP(); // so that we don't leave the line strip open
    }
}

JNIEXPORT void JNICALL
Java_sun_java2d_opengl_OGLRenderer_drawPoly
    (JNIEnv *env, jobject oglr,
     jintArray xpointsArray, jintArray ypointsArray,
     jint nPoints, jboolean isClosed,
     jint transX, jint transY)
{
    jint *xPoints, *yPoints;

    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_drawPoly");

    xPoints = (jint *)
        (*env)->GetPrimitiveArrayCritical(env, xpointsArray, NULL);
    if (xPoints != NULL) {
        yPoints = (jint *)
            (*env)->GetPrimitiveArrayCritical(env, ypointsArray, NULL);
        if (yPoints != NULL) {
            OGLContext *oglc = OGLRenderQueue_GetCurrentContext();

            OGLRenderer_DrawPoly(oglc,
                                 nPoints, isClosed,
                                 transX, transY,
                                 xPoints, yPoints);

            // 6358147: reset current state, and ensure rendering is
            // flushed to dest
            if (oglc != NULL) {
                RESET_PREVIOUS_OP();
                j2d_glFlush();
            }

            (*env)->ReleasePrimitiveArrayCritical(env, ypointsArray, yPoints,
                                                  JNI_ABORT);
        }
        (*env)->ReleasePrimitiveArrayCritical(env, xpointsArray, xPoints,
                                              JNI_ABORT);
    }
}

void
OGLRenderer_DrawScanlines(OGLContext *oglc,
                          jint scanlineCount, jint *scanlines)
{
    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_DrawScanlines");

    RETURN_IF_NULL(oglc);
    RETURN_IF_NULL(scanlines);

    CHECK_PREVIOUS_OP(GL_LINES);
    while (scanlineCount > 0) {
        // Translate each vertex by a fraction so
        // that we hit pixel centers.
        GLfloat x1 = ((GLfloat)*(scanlines++)) + 0.2f;
        GLfloat x2 = ((GLfloat)*(scanlines++)) + 1.2f;
        GLfloat y  = ((GLfloat)*(scanlines++)) + 0.5f;
        j2d_glVertex2f(x1, y);
        j2d_glVertex2f(x2, y);
        scanlineCount--;
    }
}

void
OGLRenderer_FillRect(OGLContext *oglc, jint x, jint y, jint w, jint h)
{
    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_FillRect");

    if (w <= 0 || h <= 0) {
        return;
    }

    RETURN_IF_NULL(oglc);

    CHECK_PREVIOUS_OP(GL_QUADS);
    GLRECT_BODY_XYWH(x, y, w, h);
}

void
OGLRenderer_FillSpans(OGLContext *oglc, jint spanCount, jint *spans)
{
    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_FillSpans");

    RETURN_IF_NULL(oglc);
    RETURN_IF_NULL(spans);

    CHECK_PREVIOUS_OP(GL_QUADS);
    while (spanCount > 0) {
        jint x1 = *(spans++);
        jint y1 = *(spans++);
        jint x2 = *(spans++);
        jint y2 = *(spans++);
        GLRECT_BODY_XYXY(x1, y1, x2, y2);
        spanCount--;
    }
}

#define FILL_PGRAM(fx11, fy11, dx21, dy21, dx12, dy12) \
    do { \
        j2d_glVertex2f(fx11,               fy11); \
        j2d_glVertex2f(fx11 + dx21,        fy11 + dy21); \
        j2d_glVertex2f(fx11 + dx21 + dx12, fy11 + dy21 + dy12); \
        j2d_glVertex2f(fx11 + dx12,        fy11 + dy12); \
    } while (0)

void
OGLRenderer_FillParallelogram(OGLContext *oglc,
                              jfloat fx11, jfloat fy11,
                              jfloat dx21, jfloat dy21,
                              jfloat dx12, jfloat dy12)
{
    J2dTraceLn6(J2D_TRACE_INFO,
                "OGLRenderer_FillParallelogram "
                "(x=%6.2f y=%6.2f "
                "dx1=%6.2f dy1=%6.2f "
                "dx2=%6.2f dy2=%6.2f)",
                fx11, fy11,
                dx21, dy21,
                dx12, dy12);

    RETURN_IF_NULL(oglc);

    CHECK_PREVIOUS_OP(GL_QUADS);

    FILL_PGRAM(fx11, fy11, dx21, dy21, dx12, dy12);
}

void
OGLRenderer_DrawParallelogram(OGLContext *oglc,
                              jfloat fx11, jfloat fy11,
                              jfloat dx21, jfloat dy21,
                              jfloat dx12, jfloat dy12,
                              jfloat lwr21, jfloat lwr12)
{
    // dx,dy for line width in the "21" and "12" directions.
    jfloat ldx21 = dx21 * lwr21;
    jfloat ldy21 = dy21 * lwr21;
    jfloat ldx12 = dx12 * lwr12;
    jfloat ldy12 = dy12 * lwr12;

    // calculate origin of the outer parallelogram
    jfloat ox11 = fx11 - (ldx21 + ldx12) / 2.0f;
    jfloat oy11 = fy11 - (ldy21 + ldy12) / 2.0f;

    J2dTraceLn8(J2D_TRACE_INFO,
                "OGLRenderer_DrawParallelogram "
                "(x=%6.2f y=%6.2f "
                "dx1=%6.2f dy1=%6.2f lwr1=%6.2f "
                "dx2=%6.2f dy2=%6.2f lwr2=%6.2f)",
                fx11, fy11,
                dx21, dy21, lwr21,
                dx12, dy12, lwr12);

    RETURN_IF_NULL(oglc);

    CHECK_PREVIOUS_OP(GL_QUADS);

    // Only need to generate 4 quads if the interior still
    // has a hole in it (i.e. if the line width ratio was
    // less than 1.0)
    if (lwr21 < 1.0f && lwr12 < 1.0f) {
        // Note: "TOP", "BOTTOM", "LEFT" and "RIGHT" here are
        // relative to whether the dxNN variables are positive
        // and negative.  The math works fine regardless of
        // their signs, but for conceptual simplicity the
        // comments will refer to the sides as if the dxNN
        // were all positive.  "TOP" and "BOTTOM" segments
        // are defined by the dxy21 deltas.  "LEFT" and "RIGHT"
        // segments are defined by the dxy12 deltas.

        // Each segment includes its starting corner and comes
        // to just short of the following corner.  Thus, each
        // corner is included just once and the only lengths
        // needed are the original parallelogram delta lengths
        // and the "line width deltas".  The sides will cover
        // the following relative territories:
        //
        //     T T T T T R
        //      L         R
        //       L         R
        //        L         R
        //         L         R
        //          L B B B B B

        // TOP segment, to left side of RIGHT edge
        // "width" of original pgram, "height" of hor. line size
        fx11 = ox11;
        fy11 = oy11;
        FILL_PGRAM(fx11, fy11, dx21, dy21, ldx12, ldy12);

        // RIGHT segment, to top of BOTTOM edge
        // "width" of vert. line size , "height" of original pgram
        fx11 = ox11 + dx21;
        fy11 = oy11 + dy21;
        FILL_PGRAM(fx11, fy11, ldx21, ldy21, dx12, dy12);

        // BOTTOM segment, from right side of LEFT edge
        // "width" of original pgram, "height" of hor. line size
        fx11 = ox11 + dx12 + ldx21;
        fy11 = oy11 + dy12 + ldy21;
        FILL_PGRAM(fx11, fy11, dx21, dy21, ldx12, ldy12);

        // LEFT segment, from bottom of TOP edge
        // "width" of vert. line size , "height" of inner pgram
        fx11 = ox11 + ldx12;
        fy11 = oy11 + ldy12;
        FILL_PGRAM(fx11, fy11, ldx21, ldy21, dx12, dy12);
    } else {
        // The line width ratios were large enough to consume
        // the entire hole in the middle of the parallelogram
        // so we can just issue one large quad for the outer
        // parallelogram.
        dx21 += ldx21;
        dy21 += ldy21;
        dx12 += ldx12;
        dy12 += ldy12;
        FILL_PGRAM(ox11, oy11, dx21, dy21, dx12, dy12);
    }
}

static GLhandleARB aaPgramProgram = 0;

/*
 * This shader fills the space between an outer and inner parallelogram.
 * It can be used to draw an outline by specifying both inner and outer
 * values.  It fills pixels by estimating what portion falls inside the
 * outer shape, and subtracting an estimate of what portion falls inside
 * the inner shape.  Specifying both inner and outer values produces a
 * standard "wide outline".  Specifying an inner shape that falls far
 * outside the outer shape allows the same shader to fill the outer
 * shape entirely since pixels that fall within the outer shape are never
 * inside the inner shape and so they are filled based solely on their
 * coverage of the outer shape.
 *
 * The setup code renders this shader over the bounds of the outer
 * shape (or the only shape in the case of a fill operation) and
 * sets the texture 0 coordinates so that 0,0=>0,1=>1,1=>1,0 in those
 * texture coordinates map to the four corners of the parallelogram.
 * Similarly the texture 1 coordinates map the inner shape to the
 * unit square as well, but in a different coordinate system.
 *
 * When viewed in the texture coordinate systems the parallelograms
 * we are filling are unit squares, but the pixels have then become
 * tiny parallelograms themselves.  Both of the texture coordinate
 * systems are affine transforms so the rate of change in X and Y
 * of the texture coordinates are essentially constants and happen
 * to correspond to the size and direction of the slanted sides of
 * the distorted pixels relative to the "square mapped" boundary
 * of the parallelograms.
 *
 * The shader uses the dFdx() and dFdy() functions to measure the "rate
 * of change" of these texture coordinates and thus gets an accurate
 * measure of the size and shape of a pixel relative to the two
 * parallelograms.  It then uses the bounds of the size and shape
 * of a pixel to intersect with the unit square to estimate the
 * coverage of the pixel.  Unfortunately, without a lot more work
 * to calculate the exact area of intersection between a unit
 * square (the original parallelogram) and a parallelogram (the
 * distorted pixel), this shader only approximates the pixel
 * coverage, but emperically the estimate is very useful and
 * produces visually pleasing results, if not theoretically accurate.
 */
static const char *aaPgramShaderSource =
    "void main() {"
    // Calculate the vectors for the "legs" of the pixel parallelogram
    // for the outer parallelogram.
    "    vec2 oleg1 = dFdx(gl_TexCoord[0].st);"
    "    vec2 oleg2 = dFdy(gl_TexCoord[0].st);"
    // Calculate the bounds of the distorted pixel parallelogram.
    "    vec2 corner = gl_TexCoord[0].st - (oleg1+oleg2)/2.0;"
    "    vec2 omin = min(corner, corner+oleg1);"
    "    omin = min(omin, corner+oleg2);"
    "    omin = min(omin, corner+oleg1+oleg2);"
    "    vec2 omax = max(corner, corner+oleg1);"
    "    omax = max(omax, corner+oleg2);"
    "    omax = max(omax, corner+oleg1+oleg2);"
    // Calculate the vectors for the "legs" of the pixel parallelogram
    // for the inner parallelogram.
    "    vec2 ileg1 = dFdx(gl_TexCoord[1].st);"
    "    vec2 ileg2 = dFdy(gl_TexCoord[1].st);"
    // Calculate the bounds of the distorted pixel parallelogram.
    "    corner = gl_TexCoord[1].st - (ileg1+ileg2)/2.0;"
    "    vec2 imin = min(corner, corner+ileg1);"
    "    imin = min(imin, corner+ileg2);"
    "    imin = min(imin, corner+ileg1+ileg2);"
    "    vec2 imax = max(corner, corner+ileg1);"
    "    imax = max(imax, corner+ileg2);"
    "    imax = max(imax, corner+ileg1+ileg2);"
    // Clamp the bounds of the parallelograms to the unit square to
    // estimate the intersection of the pixel parallelogram with
    // the unit square.  The ratio of the 2 rectangle areas is a
    // reasonable estimate of the proportion of coverage.
    "    vec2 o1 = clamp(omin, 0.0, 1.0);"
    "    vec2 o2 = clamp(omax, 0.0, 1.0);"
    "    float oint = (o2.y-o1.y)*(o2.x-o1.x);"
    "    float oarea = (omax.y-omin.y)*(omax.x-omin.x);"
    "    vec2 i1 = clamp(imin, 0.0, 1.0);"
    "    vec2 i2 = clamp(imax, 0.0, 1.0);"
    "    float iint = (i2.y-i1.y)*(i2.x-i1.x);"
    "    float iarea = (imax.y-imin.y)*(imax.x-imin.x);"
    // Proportion of pixel in outer shape minus the proportion
    // of pixel in the inner shape == the coverage of the pixel
    // in the area between the two.
    "    float coverage = oint/oarea - iint / iarea;"
    "    gl_FragColor = gl_Color * coverage;"
    "}";

#define ADJUST_PGRAM(V1, DV, V2) \
    do { \
        if ((DV) >= 0) { \
            (V2) += (DV); \
        } else { \
            (V1) += (DV); \
        } \
    } while (0)

// Invert the following transform:
// DeltaT(0, 0) == (0,       0)
// DeltaT(1, 0) == (DX1,     DY1)
// DeltaT(0, 1) == (DX2,     DY2)
// DeltaT(1, 1) == (DX1+DX2, DY1+DY2)
// TM00 = DX1,   TM01 = DX2,   (TM02 = X11)
// TM10 = DY1,   TM11 = DY2,   (TM12 = Y11)
// Determinant = TM00*TM11 - TM01*TM10
//             =  DX1*DY2  -  DX2*DY1
// Inverse is:
// IM00 =  TM11/det,   IM01 = -TM01/det
// IM10 = -TM10/det,   IM11 =  TM00/det
// IM02 = (TM01 * TM12 - TM11 * TM02) / det,
// IM12 = (TM10 * TM02 - TM00 * TM12) / det,

#define DECLARE_MATRIX(MAT) \
    jfloat MAT ## 00, MAT ## 01, MAT ## 02, MAT ## 10, MAT ## 11, MAT ## 12

#define GET_INVERTED_MATRIX(MAT, X11, Y11, DX1, DY1, DX2, DY2, RET_CODE) \
    do { \
        jfloat det = DX1*DY2 - DX2*DY1; \
        if (det == 0) { \
            RET_CODE; \
        } \
        MAT ## 00 = DY2/det; \
        MAT ## 01 = -DX2/det; \
        MAT ## 10 = -DY1/det; \
        MAT ## 11 = DX1/det; \
        MAT ## 02 = (DX2 * Y11 - DY2 * X11) / det; \
        MAT ## 12 = (DY1 * X11 - DX1 * Y11) / det; \
    } while (0)

#define TRANSFORM(MAT, TX, TY, X, Y) \
    do { \
        TX = (X) * MAT ## 00 + (Y) * MAT ## 01 + MAT ## 02; \
        TY = (X) * MAT ## 10 + (Y) * MAT ## 11 + MAT ## 12; \
    } while (0)

void
OGLRenderer_FillAAParallelogram(OGLContext *oglc, OGLSDOps *dstOps,
                                jfloat fx11, jfloat fy11,
                                jfloat dx21, jfloat dy21,
                                jfloat dx12, jfloat dy12)
{
    DECLARE_MATRIX(om);
    // parameters for parallelogram bounding box
    jfloat bx11, by11, bx22, by22;
    // parameters for uv texture coordinates of parallelogram corners
    jfloat u11, v11, u12, v12, u21, v21, u22, v22;

    J2dTraceLn6(J2D_TRACE_INFO,
                "OGLRenderer_FillAAParallelogram "
                "(x=%6.2f y=%6.2f "
                "dx1=%6.2f dy1=%6.2f "
                "dx2=%6.2f dy2=%6.2f)",
                fx11, fy11,
                dx21, dy21,
                dx12, dy12);

    RETURN_IF_NULL(oglc);
    RETURN_IF_NULL(dstOps);

    GET_INVERTED_MATRIX(om, fx11, fy11, dx21, dy21, dx12, dy12,
                        return);

    CHECK_PREVIOUS_OP(OGL_STATE_PGRAM_OP);

    bx11 = bx22 = fx11;
    by11 = by22 = fy11;
    ADJUST_PGRAM(bx11, dx21, bx22);
    ADJUST_PGRAM(by11, dy21, by22);
    ADJUST_PGRAM(bx11, dx12, bx22);
    ADJUST_PGRAM(by11, dy12, by22);
    bx11 = (jfloat) floor(bx11);
    by11 = (jfloat) floor(by11);
    bx22 = (jfloat) ceil(bx22);
    by22 = (jfloat) ceil(by22);

    TRANSFORM(om, u11, v11, bx11, by11);
    TRANSFORM(om, u21, v21, bx22, by11);
    TRANSFORM(om, u12, v12, bx11, by22);
    TRANSFORM(om, u22, v22, bx22, by22);

    j2d_glBegin(GL_QUADS);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u11, v11);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 5.f, 5.f);
    j2d_glVertex2f(bx11, by11);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u21, v21);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 6.f, 5.f);
    j2d_glVertex2f(bx22, by11);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u22, v22);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 6.f, 6.f);
    j2d_glVertex2f(bx22, by22);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u12, v12);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 5.f, 6.f);
    j2d_glVertex2f(bx11, by22);
    j2d_glEnd();
}

void
OGLRenderer_FillAAParallelogramInnerOuter(OGLContext *oglc, OGLSDOps *dstOps,
                                          jfloat ox11, jfloat oy11,
                                          jfloat ox21, jfloat oy21,
                                          jfloat ox12, jfloat oy12,
                                          jfloat ix11, jfloat iy11,
                                          jfloat ix21, jfloat iy21,
                                          jfloat ix12, jfloat iy12)
{
    DECLARE_MATRIX(om);
    DECLARE_MATRIX(im);
    // parameters for parallelogram bounding box
    jfloat bx11, by11, bx22, by22;
    // parameters for uv texture coordinates of outer parallelogram corners
    jfloat ou11, ov11, ou12, ov12, ou21, ov21, ou22, ov22;
    // parameters for uv texture coordinates of inner parallelogram corners
    jfloat iu11, iv11, iu12, iv12, iu21, iv21, iu22, iv22;

    RETURN_IF_NULL(oglc);
    RETURN_IF_NULL(dstOps);

    GET_INVERTED_MATRIX(im, ix11, iy11, ix21, iy21, ix12, iy12,
                        // inner parallelogram is degenerate
                        // therefore it encloses no area
                        // fill outer
                        OGLRenderer_FillAAParallelogram(oglc, dstOps,
                                                        ox11, oy11,
                                                        ox21, oy21,
                                                        ox12, oy12);
                        return);
    GET_INVERTED_MATRIX(om, ox11, oy11, ox21, oy21, ox12, oy12,
                        return);

    CHECK_PREVIOUS_OP(OGL_STATE_PGRAM_OP);

    bx11 = bx22 = ox11;
    by11 = by22 = oy11;
    ADJUST_PGRAM(bx11, ox21, bx22);
    ADJUST_PGRAM(by11, oy21, by22);
    ADJUST_PGRAM(bx11, ox12, bx22);
    ADJUST_PGRAM(by11, oy12, by22);
    bx11 = (jfloat) floor(bx11);
    by11 = (jfloat) floor(by11);
    bx22 = (jfloat) ceil(bx22);
    by22 = (jfloat) ceil(by22);

    TRANSFORM(om, ou11, ov11, bx11, by11);
    TRANSFORM(om, ou21, ov21, bx22, by11);
    TRANSFORM(om, ou12, ov12, bx11, by22);
    TRANSFORM(om, ou22, ov22, bx22, by22);

    TRANSFORM(im, iu11, iv11, bx11, by11);
    TRANSFORM(im, iu21, iv21, bx22, by11);
    TRANSFORM(im, iu12, iv12, bx11, by22);
    TRANSFORM(im, iu22, iv22, bx22, by22);

    j2d_glBegin(GL_QUADS);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ou11, ov11);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, iu11, iv11);
    j2d_glVertex2f(bx11, by11);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ou21, ov21);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, iu21, iv21);
    j2d_glVertex2f(bx22, by11);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ou22, ov22);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, iu22, iv22);
    j2d_glVertex2f(bx22, by22);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ou12, ov12);
    j2d_glMultiTexCoord2fARB(GL_TEXTURE1_ARB, iu12, iv12);
    j2d_glVertex2f(bx11, by22);
    j2d_glEnd();
}

void
OGLRenderer_DrawAAParallelogram(OGLContext *oglc, OGLSDOps *dstOps,
                                jfloat fx11, jfloat fy11,
                                jfloat dx21, jfloat dy21,
                                jfloat dx12, jfloat dy12,
                                jfloat lwr21, jfloat lwr12)
{
    // dx,dy for line width in the "21" and "12" directions.
    jfloat ldx21, ldy21, ldx12, ldy12;
    // parameters for "outer" parallelogram
    jfloat ofx11, ofy11, odx21, ody21, odx12, ody12;
    // parameters for "inner" parallelogram
    jfloat ifx11, ify11, idx21, idy21, idx12, idy12;

    J2dTraceLn8(J2D_TRACE_INFO,
                "OGLRenderer_DrawAAParallelogram "
                "(x=%6.2f y=%6.2f "
                "dx1=%6.2f dy1=%6.2f lwr1=%6.2f "
                "dx2=%6.2f dy2=%6.2f lwr2=%6.2f)",
                fx11, fy11,
                dx21, dy21, lwr21,
                dx12, dy12, lwr12);

    RETURN_IF_NULL(oglc);
    RETURN_IF_NULL(dstOps);

    // calculate true dx,dy for line widths from the "line width ratios"
    ldx21 = dx21 * lwr21;
    ldy21 = dy21 * lwr21;
    ldx12 = dx12 * lwr12;
    ldy12 = dy12 * lwr12;

    // calculate coordinates of the outer parallelogram
    ofx11 = fx11 - (ldx21 + ldx12) / 2.0f;
    ofy11 = fy11 - (ldy21 + ldy12) / 2.0f;
    odx21 = dx21 + ldx21;
    ody21 = dy21 + ldy21;
    odx12 = dx12 + ldx12;
    ody12 = dy12 + ldy12;

    // Only process the inner parallelogram if the line width ratio
    // did not consume the entire interior of the parallelogram
    // (i.e. if the width ratio was less than 1.0)
    if (lwr21 < 1.0f && lwr12 < 1.0f) {
        // calculate coordinates of the inner parallelogram
        ifx11 = fx11 + (ldx21 + ldx12) / 2.0f;
        ify11 = fy11 + (ldy21 + ldy12) / 2.0f;
        idx21 = dx21 - ldx21;
        idy21 = dy21 - ldy21;
        idx12 = dx12 - ldx12;
        idy12 = dy12 - ldy12;

        OGLRenderer_FillAAParallelogramInnerOuter(oglc, dstOps,
                                                  ofx11, ofy11,
                                                  odx21, ody21,
                                                  odx12, ody12,
                                                  ifx11, ify11,
                                                  idx21, idy21,
                                                  idx12, idy12);
    } else {
        OGLRenderer_FillAAParallelogram(oglc, dstOps,
                                        ofx11, ofy11,
                                        odx21, ody21,
                                        odx12, ody12);
    }
}

void
OGLRenderer_EnableAAParallelogramProgram(void)
{
    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_EnableAAParallelogramProgram");

    if (aaPgramProgram == 0) {
        aaPgramProgram = OGLContext_CreateFragmentProgram(aaPgramShaderSource);
        if (aaPgramProgram == 0) {
            J2dRlsTraceLn(J2D_TRACE_ERROR,
                          "OGLRenderer_EnableAAParallelogramProgram: "
                          "error creating program");
            return;
        }
    }
    j2d_glUseProgramObjectARB(aaPgramProgram);
}

void
OGLRenderer_DisableAAParallelogramProgram(void)
{
    J2dTraceLn(J2D_TRACE_INFO, "OGLRenderer_DisableAAParallelogramProgram");

    j2d_glUseProgramObjectARB(0);
}

#endif /* !HEADLESS */