/*
 * Copyright (c) 2019, 2025, 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.
 */

#include <stdlib.h>

#include "sun_java2d_pipe_BufferedOpCodes.h"

#include "jlong.h"
#include "MTLBlitLoops.h"
#include "MTLBufImgOps.h"
#include "MTLMaskBlit.h"
#include "MTLMaskFill.h"
#include "MTLPaints.h"
#include "MTLRenderQueue.h"
#include "MTLRenderer.h"
#include "MTLTextRenderer.h"
#import "ThreadUtilities.h"

/**
 * References to the "current" context and destination surface.
 */
static MTLContext *mtlc = NULL;
static BMTLSDOps *dstOps = NULL;
jint mtlPreviousOp = MTL_OP_INIT;


extern void MTLGC_DestroyMTLGraphicsConfig(jlong pConfigInfo);

void MTLRenderQueue_CheckPreviousOp(jint op) {

    if (mtlPreviousOp == op) {
        // The op is the same as last time, so we can return immediately.
        return;
    }

    if (op == MTL_OP_SET_COLOR) {
        if (mtlPreviousOp != MTL_OP_MASK_OP) {
            return; // SET_COLOR should not cause endEncoder
        }
    } else if (op == MTL_OP_MASK_OP) {
        MTLVertexCache_EnableMaskCache(mtlc, dstOps);
        mtlPreviousOp = op;
        return;
    }

    J2dTraceLn(J2D_TRACE_VERBOSE,
               "MTLRenderQueue_CheckPreviousOp: new op=%d", op);

    switch (mtlPreviousOp) {
        case MTL_OP_INIT :
            mtlPreviousOp = op;
            return;
        case MTL_OP_MASK_OP :
            MTLVertexCache_DisableMaskCache(mtlc);
            break;
    }

    if (mtlc != NULL) {
        [mtlc.encoderManager endEncoder];

        if (op == MTL_OP_RESET_PAINT || op == MTL_OP_SYNC || op == MTL_OP_SHAPE_CLIP_SPANS ||
            mtlPreviousOp == MTL_OP_MASK_OP)
        {
            MTLCommandBufferWrapper *cbwrapper = [mtlc pullCommandBufferWrapper];
            id <MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
            [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
                [cbwrapper release];
            }];
            [commandbuf commit];
            if (op == MTL_OP_SYNC || op == MTL_OP_SHAPE_CLIP_SPANS) {
                [commandbuf waitUntilCompleted];
            }
        }
    }
    mtlPreviousOp = op;
}

JNIEXPORT void JNICALL
Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
    (JNIEnv *env, jobject mtlrq,
     jlong buf, jint limit)
{
    unsigned char *b, *end;

    J2dTraceLn(J2D_TRACE_INFO,
               "MTLRenderQueue_flushBuffer: limit=%d", limit);

    b = (unsigned char *)jlong_to_ptr(buf);
    if (b == NULL) {
        J2dRlsTraceLn(J2D_TRACE_ERROR,
            "MTLRenderQueue_flushBuffer: cannot get direct buffer address");
        return;
    }

    end = b + limit;
    @autoreleasepool {
        while (b < end) {
            jint opcode = NEXT_INT(b);

            J2dTraceLn(J2D_TRACE_VERBOSE,
                       "MTLRenderQueue_flushBuffer: opcode=%d, rem=%d",
                       opcode, (end-b));

            switch (opcode) {

                // draw ops
                case sun_java2d_pipe_BufferedOpCodes_DRAW_LINE:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "DRAW_LINE in XOR mode - Force commit earlier draw calls before DRAW_LINE.");
                    }
                    jint x1 = NEXT_INT(b);
                    jint y1 = NEXT_INT(b);
                    jint x2 = NEXT_INT(b);
                    jint y2 = NEXT_INT(b);
                    MTLRenderer_DrawLine(mtlc, dstOps, x1, y1, x2, y2);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DRAW_RECT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "DRAW_RECT in XOR mode - Force commit earlier draw calls before DRAW_RECT.");
                    }
                    jint x = NEXT_INT(b);
                    jint y = NEXT_INT(b);
                    jint w = NEXT_INT(b);
                    jint h = NEXT_INT(b);
                    MTLRenderer_DrawRect(mtlc, dstOps, x, y, w, h);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DRAW_POLY:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint nPoints      = NEXT_INT(b);
                    jboolean isClosed = NEXT_BOOLEAN(b);
                    jint transX       = NEXT_INT(b);
                    jint transY       = NEXT_INT(b);
                    jint *xPoints = (jint *)b;
                    jint *yPoints = ((jint *)b) + nPoints;

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "DRAW_POLY in XOR mode - Force commit earlier draw calls before DRAW_POLY.");

                        // draw separate (N-1) lines using N points
                        for(int point = 0; point < nPoints-1; point++) {
                            jint x1 = xPoints[point] + transX;
                            jint y1 = yPoints[point] + transY;
                            jint x2 = xPoints[point + 1] + transX;
                            jint y2 = yPoints[point + 1] + transY;
                            MTLRenderer_DrawLine(mtlc, dstOps, x1, y1, x2, y2);
                        }

                        if (isClosed) {
                            MTLRenderer_DrawLine(mtlc, dstOps, xPoints[0] + transX, yPoints[0] + transY,
                                                 xPoints[nPoints-1] + transX, yPoints[nPoints-1] + transY);
                        }
                    } else {
                        MTLRenderer_DrawPoly(mtlc, dstOps, nPoints, isClosed, transX, transY, xPoints, yPoints);
                    }

                    SKIP_BYTES(b, nPoints * BYTES_PER_POLY_POINT);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DRAW_PIXEL:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "DRAW_PIXEL in XOR mode - Force commit earlier draw calls before DRAW_PIXEL.");
                    }

                    jint x = NEXT_INT(b);
                    jint y = NEXT_INT(b);
                    CONTINUE_IF_NULL(mtlc);
                    MTLRenderer_DrawPixel(mtlc, dstOps, x, y);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DRAW_SCANLINES:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "DRAW_SCANLINES in XOR mode - Force commit earlier draw calls before "
                                   "DRAW_SCANLINES.");
                    }

                    jint count = NEXT_INT(b);
                    MTLRenderer_DrawScanlines(mtlc, dstOps, count, (jint *)b);

                    SKIP_BYTES(b, count * BYTES_PER_SCANLINE);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DRAW_PARALLELOGRAM:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "DRAW_PARALLELOGRAM in XOR mode - Force commit earlier draw calls before "
                                   "DRAW_PARALLELOGRAM.");
                    }

                    jfloat x11 = NEXT_FLOAT(b);
                    jfloat y11 = NEXT_FLOAT(b);
                    jfloat dx21 = NEXT_FLOAT(b);
                    jfloat dy21 = NEXT_FLOAT(b);
                    jfloat dx12 = NEXT_FLOAT(b);
                    jfloat dy12 = NEXT_FLOAT(b);
                    jfloat lwr21 = NEXT_FLOAT(b);
                    jfloat lwr12 = NEXT_FLOAT(b);

                    MTLRenderer_DrawParallelogram(mtlc, dstOps,
                                                  x11, y11,
                                                  dx21, dy21,
                                                  dx12, dy12,
                                                  lwr21, lwr12);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DRAW_AAPARALLELOGRAM:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jfloat x11 = NEXT_FLOAT(b);
                    jfloat y11 = NEXT_FLOAT(b);
                    jfloat dx21 = NEXT_FLOAT(b);
                    jfloat dy21 = NEXT_FLOAT(b);
                    jfloat dx12 = NEXT_FLOAT(b);
                    jfloat dy12 = NEXT_FLOAT(b);
                    jfloat lwr21 = NEXT_FLOAT(b);
                    jfloat lwr12 = NEXT_FLOAT(b);

                    MTLRenderer_DrawAAParallelogram(mtlc, dstOps,
                                                    x11, y11,
                                                    dx21, dy21,
                                                    dx12, dy12,
                                                    lwr21, lwr12);
                    break;
                }

                // fill ops
                case sun_java2d_pipe_BufferedOpCodes_FILL_RECT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "FILL_RECT in XOR mode - Force commit earlier draw calls before FILL_RECT.");
                    }

                    jint x = NEXT_INT(b);
                    jint y = NEXT_INT(b);
                    jint w = NEXT_INT(b);
                    jint h = NEXT_INT(b);
                    MTLRenderer_FillRect(mtlc, dstOps, x, y, w, h);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_FILL_SPANS:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "FILL_SPANS in XOR mode - Force commit earlier draw calls before FILL_SPANS.");
                    }

                    jint count = NEXT_INT(b);
                    MTLRenderer_FillSpans(mtlc, dstOps, count, (jint *)b);
                    SKIP_BYTES(b, count * BYTES_PER_SPAN);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_FILL_PARALLELOGRAM:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "FILL_PARALLELOGRAM in XOR mode - Force commit earlier draw calls before "
                                   "FILL_PARALLELOGRAM.");
                    }

                    jfloat x11 = NEXT_FLOAT(b);
                    jfloat y11 = NEXT_FLOAT(b);
                    jfloat dx21 = NEXT_FLOAT(b);
                    jfloat dy21 = NEXT_FLOAT(b);
                    jfloat dx12 = NEXT_FLOAT(b);
                    jfloat dy12 = NEXT_FLOAT(b);
                    MTLRenderer_FillParallelogram(mtlc, dstOps,
                                                  x11, y11,
                                                  dx21, dy21,
                                                  dx12, dy12);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_FILL_AAPARALLELOGRAM:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jfloat x11 = NEXT_FLOAT(b);
                    jfloat y11 = NEXT_FLOAT(b);
                    jfloat dx21 = NEXT_FLOAT(b);
                    jfloat dy21 = NEXT_FLOAT(b);
                    jfloat dx12 = NEXT_FLOAT(b);
                    jfloat dy12 = NEXT_FLOAT(b);
                    MTLRenderer_FillAAParallelogram(mtlc, dstOps,
                                                    x11, y11,
                                                    dx21, dy21,
                                                    dx12, dy12);
                    break;
                }

                // text-related ops
                case sun_java2d_pipe_BufferedOpCodes_DRAW_GLYPH_LIST:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);

                    if ([mtlc useXORComposite]) {
                        commitEncodedCommands();
                        J2dTraceLn(J2D_TRACE_VERBOSE,
                                   "DRAW_GLYPH_LIST in XOR mode - Force commit earlier draw calls before "
                                   "DRAW_GLYPH_LIST.");
                    }

                    jint numGlyphs        = NEXT_INT(b);
                    jint packedParams     = NEXT_INT(b);
                    jfloat glyphListOrigX = NEXT_FLOAT(b);
                    jfloat glyphListOrigY = NEXT_FLOAT(b);
                    jboolean usePositions = EXTRACT_BOOLEAN(packedParams,
                                                            OFFSET_POSITIONS);
                    jboolean subPixPos    = EXTRACT_BOOLEAN(packedParams,
                                                            OFFSET_SUBPIXPOS);
                    jboolean rgbOrder     = EXTRACT_BOOLEAN(packedParams,
                                                            OFFSET_RGBORDER);
                    jint lcdContrast      = EXTRACT_BYTE(packedParams,
                                                         OFFSET_CONTRAST);
                    unsigned char *images = b;
                    unsigned char *positions;
                    jint bytesPerGlyph;
                    if (usePositions) {
                        positions = (b + numGlyphs * BYTES_PER_GLYPH_IMAGE);
                        bytesPerGlyph = BYTES_PER_POSITIONED_GLYPH;
                    } else {
                        positions = NULL;
                        bytesPerGlyph = BYTES_PER_GLYPH_IMAGE;
                    }
                    MTLTR_DrawGlyphList(env, mtlc, dstOps,
                                        numGlyphs, usePositions,
                                        subPixPos, rgbOrder, lcdContrast,
                                        glyphListOrigX, glyphListOrigY,
                                        images, positions);
                    SKIP_BYTES(b, numGlyphs * bytesPerGlyph);
                    break;
                }

                // copy-related ops
                case sun_java2d_pipe_BufferedOpCodes_COPY_AREA:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint x  = NEXT_INT(b);
                    jint y  = NEXT_INT(b);
                    jint w  = NEXT_INT(b);
                    jint h  = NEXT_INT(b);
                    jint dx = NEXT_INT(b);
                    jint dy = NEXT_INT(b);
                    MTLBlitLoops_CopyArea(env, mtlc, dstOps,
                                          x, y, w, h, dx, dy);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_BLIT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint packedParams = NEXT_INT(b);
                    jint sx1          = NEXT_INT(b);
                    jint sy1          = NEXT_INT(b);
                    jint sx2          = NEXT_INT(b);
                    jint sy2          = NEXT_INT(b);
                    jdouble dx1       = NEXT_DOUBLE(b);
                    jdouble dy1       = NEXT_DOUBLE(b);
                    jdouble dx2       = NEXT_DOUBLE(b);
                    jdouble dy2       = NEXT_DOUBLE(b);
                    jlong pSrc        = NEXT_LONG(b);
                    jlong pDst        = NEXT_LONG(b);
                    jint hint         = EXTRACT_BYTE(packedParams, OFFSET_HINT);
                    jboolean texture  = EXTRACT_BOOLEAN(packedParams,
                                                        OFFSET_TEXTURE);
                    jboolean xform    = EXTRACT_BOOLEAN(packedParams,
                                                        OFFSET_XFORM);
                    jboolean isoblit  = EXTRACT_BOOLEAN(packedParams,
                                                        OFFSET_ISOBLIT);
                    if (isoblit) {
                        MTLBlitLoops_IsoBlit(env, mtlc, pSrc, pDst,
                                             xform, hint, texture,
                                             sx1, sy1, sx2, sy2,
                                             dx1, dy1, dx2, dy2);
                    } else {
                        jint srctype = EXTRACT_BYTE(packedParams, OFFSET_SRCTYPE);
                        MTLBlitLoops_Blit(env, mtlc, pSrc, pDst,
                                          xform, hint, srctype, texture,
                                          sx1, sy1, sx2, sy2,
                                          dx1, dy1, dx2, dy2);
                    }
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SURFACE_TO_SW_BLIT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint sx      = NEXT_INT(b);
                    jint sy      = NEXT_INT(b);
                    jint dx      = NEXT_INT(b);
                    jint dy      = NEXT_INT(b);
                    jint w       = NEXT_INT(b);
                    jint h       = NEXT_INT(b);
                    jint dsttype = NEXT_INT(b);
                    jlong pSrc   = NEXT_LONG(b);
                    jlong pDst   = NEXT_LONG(b);
                    MTLBlitLoops_SurfaceToSwBlit(env, mtlc,
                                                 pSrc, pDst, dsttype,
                                                 sx, sy, dx, dy, w, h);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_MASK_FILL:
                {
                    jint x        = NEXT_INT(b);
                    jint y        = NEXT_INT(b);
                    jint w        = NEXT_INT(b);
                    jint h        = NEXT_INT(b);
                    jint maskoff  = NEXT_INT(b);
                    jint maskscan = NEXT_INT(b);
                    jint masklen  = NEXT_INT(b);
                    unsigned char *pMask = (masklen > 0) ? b : NULL;
                    if (mtlc == nil)
                        return;
                    CHECK_PREVIOUS_OP(MTL_OP_MASK_OP);
                    MTLMaskFill_MaskFill(mtlc, dstOps, x, y, w, h,
                                         maskoff, maskscan, masklen, pMask);
                    SKIP_BYTES(b, masklen);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_MASK_BLIT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint dstx     = NEXT_INT(b);
                    jint dsty     = NEXT_INT(b);
                    jint width    = NEXT_INT(b);
                    jint height   = NEXT_INT(b);
                    jint masklen  = width * height * sizeof(jint);
                    MTLMaskBlit_MaskBlit(env, mtlc, dstOps,
                                         dstx, dsty, width, height, b);
                    SKIP_BYTES(b, masklen);
                    break;
                }

                // state-related ops
                case sun_java2d_pipe_BufferedOpCodes_SET_RECT_CLIP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint x1 = NEXT_INT(b);
                    jint y1 = NEXT_INT(b);
                    jint x2 = NEXT_INT(b);
                    jint y2 = NEXT_INT(b);
                    [mtlc setClipRectX1:x1 Y1:y1 X2:x2 Y2:y2];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_BEGIN_SHAPE_CLIP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc beginShapeClip:dstOps];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_SHAPE_CLIP_SPANS:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_SHAPE_CLIP_SPANS);
                    // This results in creation of new render encoder with
                    // stencil buffer set as render target
                    jint count = NEXT_INT(b);
                    MTLRenderer_FillSpans(mtlc, dstOps, count, (jint *)b);
                    SKIP_BYTES(b, count * BYTES_PER_SPAN);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_END_SHAPE_CLIP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc endShapeClip:dstOps];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_RESET_CLIP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc resetClip];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_ALPHA_COMPOSITE:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint rule         = NEXT_INT(b);
                    jfloat extraAlpha = NEXT_FLOAT(b);
                    jint flags        = NEXT_INT(b);
                    [mtlc setAlphaCompositeRule:rule extraAlpha:extraAlpha flags:flags];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_XOR_COMPOSITE:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jint xorPixel = NEXT_INT(b);
                    [mtlc setXorComposite:xorPixel];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_RESET_COMPOSITE:
                {
                    /* TODO: check whether something needs to be done here if we are moving out of XOR composite
                    commitEncodedCommands();
                    MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
                    [cbwrapper onComplete];

                    J2dTraceLn(J2D_TRACE_VERBOSE,
                     "RESET_COMPOSITE - Force commit earlier draw calls before RESET_COMPOSITE.");*/

                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc resetComposite];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_TRANSFORM:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jdouble m00 = NEXT_DOUBLE(b);
                    jdouble m10 = NEXT_DOUBLE(b);
                    jdouble m01 = NEXT_DOUBLE(b);
                    jdouble m11 = NEXT_DOUBLE(b);
                    jdouble m02 = NEXT_DOUBLE(b);
                    jdouble m12 = NEXT_DOUBLE(b);
                    [mtlc setTransformM00:m00 M10:m10 M01:m01 M11:m11 M02:m02 M12:m12];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_RESET_TRANSFORM:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc resetTransform];
                    break;
                }

                // context-related ops
                case sun_java2d_pipe_BufferedOpCodes_SET_SURFACES:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pSrc = NEXT_LONG(b);
                    jlong pDst = NEXT_LONG(b);

                    if (mtlc != NULL) {
                        [mtlc.encoderManager endEncoder];
                        MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
                        id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
                        [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
                            [cbwrapper release];
                        }];
                        [commandbuf commit];
                    }
                    mtlc = [MTLContext setSurfacesEnv:env src:pSrc dst:pDst];
                    dstOps = (BMTLSDOps *)jlong_to_ptr(pDst);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_SCRATCH_SURFACE:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pConfigInfo = NEXT_LONG(b);
                    MTLGraphicsConfigInfo *mtlInfo =
                            (MTLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo);

                    if (mtlInfo == NULL) {

                    } else {
                        MTLContext *newMtlc = mtlInfo->context;
                        if (newMtlc == NULL) {

                        } else {
                            if (mtlc != NULL) {
                                [mtlc.encoderManager endEncoder];
                                MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
                                id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
                                [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
                                    [cbwrapper release];
                                }];
                                [commandbuf commit];
                            }
                            mtlc = newMtlc;
                            dstOps = NULL;
                        }
                    }
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_FLUSH_SURFACE:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pData = NEXT_LONG(b);
                    BMTLSDOps *mtlsdo = (BMTLSDOps *)jlong_to_ptr(pData);
                    if (mtlsdo != NULL) {
                        CONTINUE_IF_NULL(mtlc);
                        MTLTR_FreeGlyphCaches();
                        MTLSD_Delete(env, mtlsdo);
                    }
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DISPOSE_SURFACE:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pData = NEXT_LONG(b);
                    BMTLSDOps *mtlsdo = (BMTLSDOps *)jlong_to_ptr(pData);
                    if (mtlsdo != NULL) {
                        CONTINUE_IF_NULL(mtlc);
                        MTLSD_Delete(env, mtlsdo);
                        if (mtlsdo->privOps != NULL) {
                            free(mtlsdo->privOps);
                        }
                    }
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DISPOSE_CONFIG:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pConfigInfo = NEXT_LONG(b);
                    CONTINUE_IF_NULL(mtlc);

                    if (mtlc != NULL) {
                        [mtlc.encoderManager endEncoder];
                    }

                    MTLGC_DestroyMTLGraphicsConfig(pConfigInfo);

                    mtlc = NULL;
                 //   dstOps = NULL;
                    break;
                }

                case sun_java2d_pipe_BufferedOpCodes_SYNC:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_SYNC);
                    break;
                }

                // special no-op (mainly used for achieving 8-byte alignment)
                case sun_java2d_pipe_BufferedOpCodes_NOOP:
                    break;

                // paint-related ops
                case sun_java2d_pipe_BufferedOpCodes_RESET_PAINT:
                {
                  CHECK_PREVIOUS_OP(MTL_OP_RESET_PAINT);
                  [mtlc resetPaint];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_COLOR:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_SET_COLOR);
                    jint pixel = NEXT_INT(b);
                    [mtlc setColorPaint:pixel];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_GRADIENT_PAINT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jboolean useMask= NEXT_BOOLEAN(b);
                    jboolean cyclic = NEXT_BOOLEAN(b);
                    jdouble p0      = NEXT_DOUBLE(b);
                    jdouble p1      = NEXT_DOUBLE(b);
                    jdouble p3      = NEXT_DOUBLE(b);
                    jint pixel1     = NEXT_INT(b);
                    jint pixel2     = NEXT_INT(b);
                    [mtlc setGradientPaintUseMask:useMask
                                        cyclic:cyclic
                                            p0:p0
                                            p1:p1
                                            p3:p3
                                        pixel1:pixel1
                                        pixel2:pixel2];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_LINEAR_GRADIENT_PAINT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jboolean useMask = NEXT_BOOLEAN(b);
                    jboolean linear  = NEXT_BOOLEAN(b);
                    jint cycleMethod = NEXT_INT(b);
                    jint numStops    = NEXT_INT(b);
                    jfloat p0        = NEXT_FLOAT(b);
                    jfloat p1        = NEXT_FLOAT(b);
                    jfloat p3        = NEXT_FLOAT(b);
                    void *fractions, *pixels;
                    fractions = b; SKIP_BYTES(b, numStops * sizeof(jfloat));
                    pixels    = b; SKIP_BYTES(b, numStops * sizeof(jint));
                    [mtlc setLinearGradientPaint:useMask
                                          linear:linear
                                     cycleMethod:cycleMethod
                                        numStops:numStops
                                              p0:p0
                                              p1:p1
                                              p3:p3
                                       fractions:fractions
                                          pixels:pixels];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_RADIAL_GRADIENT_PAINT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jboolean useMask = NEXT_BOOLEAN(b);
                    jboolean linear  = NEXT_BOOLEAN(b);
                    jint numStops    = NEXT_INT(b);
                    jint cycleMethod = NEXT_INT(b);
                    jfloat m00       = NEXT_FLOAT(b);
                    jfloat m01       = NEXT_FLOAT(b);
                    jfloat m02       = NEXT_FLOAT(b);
                    jfloat m10       = NEXT_FLOAT(b);
                    jfloat m11       = NEXT_FLOAT(b);
                    jfloat m12       = NEXT_FLOAT(b);
                    jfloat focusX    = NEXT_FLOAT(b);
                    void *fractions, *pixels;
                    fractions = b; SKIP_BYTES(b, numStops * sizeof(jfloat));
                    pixels    = b; SKIP_BYTES(b, numStops * sizeof(jint));
                    [mtlc setRadialGradientPaint:useMask
                                          linear:linear
                                     cycleMethod:cycleMethod
                                        numStops:numStops
                                             m00:m00
                                             m01:m01
                                             m02:m02
                                             m10:m10
                                             m11:m11
                                             m12:m12
                                          focusX:focusX
                                       fractions:fractions
                                          pixels:pixels];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_SET_TEXTURE_PAINT:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jboolean useMask= NEXT_BOOLEAN(b);
                    jboolean filter = NEXT_BOOLEAN(b);
                    jlong pSrc      = NEXT_LONG(b);
                    jdouble xp0     = NEXT_DOUBLE(b);
                    jdouble xp1     = NEXT_DOUBLE(b);
                    jdouble xp3     = NEXT_DOUBLE(b);
                    jdouble yp0     = NEXT_DOUBLE(b);
                    jdouble yp1     = NEXT_DOUBLE(b);
                    jdouble yp3     = NEXT_DOUBLE(b);
                    [mtlc setTexturePaint:useMask
                                  pSrcOps:pSrc
                                   filter:filter
                                      xp0:xp0
                                      xp1:xp1
                                      xp3:xp3
                                      yp0:yp0
                                      yp1:yp1
                                      yp3:yp3];
                    break;
                }

                // BufferedImageOp-related ops
                case sun_java2d_pipe_BufferedOpCodes_ENABLE_CONVOLVE_OP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pSrc        = NEXT_LONG(b);
                    jboolean edgeZero = NEXT_BOOLEAN(b);
                    jint kernelWidth  = NEXT_INT(b);
                    jint kernelHeight = NEXT_INT(b);

                    BMTLSDOps * bmtlsdOps = (BMTLSDOps *)pSrc;
                    MTLConvolveOp * convolveOp = [[MTLConvolveOp alloc] init:edgeZero
                            kernelWidth:kernelWidth
                           kernelHeight:kernelHeight
                               srcWidth:bmtlsdOps->width
                              srcHeight:bmtlsdOps->height
                                 kernel:b
                                 device:mtlc.device
                                                  ];
                    [mtlc setBufImgOp:convolveOp];
                    SKIP_BYTES(b, kernelWidth * kernelHeight * sizeof(jfloat));
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DISABLE_CONVOLVE_OP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc setBufImgOp:NULL];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_ENABLE_RESCALE_OP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pSrc          = NEXT_LONG(b);
                    jboolean nonPremult = NEXT_BOOLEAN(b);
                    jint numFactors     = 4;
                    unsigned char *scaleFactors = b;
                    unsigned char *offsets = (b + numFactors * sizeof(jfloat));
                    MTLRescaleOp * rescaleOp =
                            [[MTLRescaleOp alloc] init:nonPremult factors:scaleFactors offsets:offsets];
                    [mtlc setBufImgOp:rescaleOp];
                    SKIP_BYTES(b, numFactors * sizeof(jfloat) * 2);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DISABLE_RESCALE_OP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc setBufImgOp:NULL];
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_ENABLE_LOOKUP_OP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    jlong pSrc          = NEXT_LONG(b);
                    jboolean nonPremult = NEXT_BOOLEAN(b);
                    jboolean shortData  = NEXT_BOOLEAN(b);
                    jint numBands       = NEXT_INT(b);
                    jint bandLength     = NEXT_INT(b);
                    jint offset         = NEXT_INT(b);
                    jint bytesPerElem = shortData ? sizeof(jshort):sizeof(jbyte);
                    void *tableValues = b;

                    MTLLookupOp * lookupOp = [[MTLLookupOp alloc] init:nonPremult
                                                             shortData:shortData
                                                              numBands:numBands
                                                            bandLength:bandLength
                                                                offset:offset
                                                           tableValues:tableValues
                                                                device:mtlc.device];
                    [mtlc setBufImgOp:lookupOp];
                    SKIP_BYTES(b, numBands * bandLength * bytesPerElem);
                    break;
                }
                case sun_java2d_pipe_BufferedOpCodes_DISABLE_LOOKUP_OP:
                {
                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
                    [mtlc setBufImgOp:NULL];
                    break;
                }

                default:
                    J2dRlsTraceLn(J2D_TRACE_ERROR,
                                  "MTLRenderQueue_flushBuffer: invalid opcode=%d",
                                  opcode);
                    return;
            }
        }

        if (mtlc != NULL) {
            if (mtlPreviousOp == MTL_OP_MASK_OP) {
                MTLVertexCache_DisableMaskCache(mtlc);
            }
            [mtlc.encoderManager endEncoder];
            MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
            id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
            [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
                [cbwrapper release];
            }];
            [commandbuf commit];
            BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
            if (dstOps != NULL) {
                MTLSDOps *dstMTLOps = (MTLSDOps *)dstOps->privOps;
                MTLLayer *layer = (MTLLayer*)dstMTLOps->layer;
                if (layer != NULL) {
                    [layer startDisplayLink];
                }
            }
        }
        RESET_PREVIOUS_OP();
    }
}

/**
 * Returns a pointer to the "current" context, as set by the last SET_SURFACES
 * or SET_SCRATCH_SURFACE operation.
 */
MTLContext *
MTLRenderQueue_GetCurrentContext()
{
    return mtlc;
}

/**
 * Returns a pointer to the "current" destination surface, as set by the last
 * SET_SURFACES operation.
 */
BMTLSDOps *
MTLRenderQueue_GetCurrentDestination()
{
    return dstOps;
}

/**
 * commit earlier encoded commands
 * these would be rendered to the back-buffer - which is read in shader while rendering in XOR mode
 */
void commitEncodedCommands() {
    [mtlc.encoderManager endEncoder];

    MTLCommandBufferWrapper *cbwrapper = [mtlc pullCommandBufferWrapper];
    id <MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
    [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
        [cbwrapper release];
    }];
    [commandbuf commit];
    [commandbuf waitUntilCompleted];
}
