1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #ifndef HEADLESS
  27 
  28 #include <jni.h>
  29 #include <jlong.h>
  30 
  31 #include "SurfaceData.h"
  32 #include "MTLBlitLoops.h"
  33 #include "MTLRenderQueue.h"
  34 #include "MTLSurfaceData.h"
  35 #include "MTLUtils.h"
  36 #include "GraphicsPrimitiveMgr.h"
  37 
  38 #include <stdlib.h> // malloc
  39 #include <string.h> // memcpy
  40 #include "IntArgbPre.h"
  41 
  42 extern MTLPixelFormat PixelFormats[];
  43 extern void J2dTraceImpl(int level, jboolean cr, const char *string, ...);
  44 
  45 void fillTxQuad(
  46         struct TxtVertex * txQuadVerts,
  47         jint sx1, jint sy1, jint sx2, jint sy2, jint sw, jint sh,
  48         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2, jdouble dw, jdouble dh
  49 ) {
  50     const float nsx1 = sx1/(float)sw;
  51     const float nsy1 = sy1/(float)sh;
  52     const float nsx2 = sx2/(float)sw;
  53     const float nsy2 = sy2/(float)sh;
  54 
  55     txQuadVerts[0].position[0] = dx1;
  56     txQuadVerts[0].position[1] = dy1;
  57     txQuadVerts[0].position[2] = 0;
  58     txQuadVerts[0].txtpos[0]   = nsx1;
  59     txQuadVerts[0].txtpos[1]   = nsy1;
  60 
  61     txQuadVerts[1].position[0] = dx2;
  62     txQuadVerts[1].position[1] = dy1;
  63     txQuadVerts[1].position[2] = 0;
  64     txQuadVerts[1].txtpos[0]   = nsx2;
  65     txQuadVerts[1].txtpos[1]   = nsy1;
  66 
  67     txQuadVerts[2].position[0] = dx2;
  68     txQuadVerts[2].position[1] = dy2;
  69     txQuadVerts[2].position[2] = 0;
  70     txQuadVerts[2].txtpos[0]   = nsx2;
  71     txQuadVerts[2].txtpos[1]   = nsy2;
  72 
  73     txQuadVerts[3].position[0] = dx2;
  74     txQuadVerts[3].position[1] = dy2;
  75     txQuadVerts[3].position[2] = 0;
  76     txQuadVerts[3].txtpos[0]   = nsx2;
  77     txQuadVerts[3].txtpos[1]   = nsy2;
  78 
  79     txQuadVerts[4].position[0] = dx1;
  80     txQuadVerts[4].position[1] = dy2;
  81     txQuadVerts[4].position[2] = 0;
  82     txQuadVerts[4].txtpos[0]   = nsx1;
  83     txQuadVerts[4].txtpos[1]   = nsy2;
  84 
  85     txQuadVerts[5].position[0] = dx1;
  86     txQuadVerts[5].position[1] = dy1;
  87     txQuadVerts[5].position[2] = 0;
  88     txQuadVerts[5].txtpos[0]   = nsx1;
  89     txQuadVerts[5].txtpos[1]   = nsy1;
  90 }
  91 
  92 /**
  93  * Inner loop used for copying a source MTL "Surface" (window, pbuffer,
  94  * etc.) to a destination OpenGL "Surface".  Note that the same surface can
  95  * be used as both the source and destination, as is the case in a copyArea()
  96  * operation.  This method is invoked from MTLBlitLoops_IsoBlit() as well as
  97  * MTLBlitLoops_CopyArea().
  98  *
  99  * The standard glCopyPixels() mechanism is used to copy the source region
 100  * into the destination region.  If the regions have different dimensions,
 101  * the source will be scaled into the destination as appropriate (only
 102  * nearest neighbor filtering will be applied for simple scale operations).
 103  */
 104 static void
 105 MTLBlitSurfaceToSurface(MTLContext *mtlc, BMTLSDOps *srcOps, BMTLSDOps *dstOps,
 106                         jint sx1, jint sy1, jint sx2, jint sy2,
 107                         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 108 {
 109     //TODO
 110     //J2dTraceNotImplPrimitive("MTLBlitSurfaceToSurface");
 111 }
 112 
 113 static void drawTex2Tex(MTLContext *mtlc,
 114                         id<MTLTexture> src, id<MTLTexture> dst,
 115                         jboolean rtt, jint hint,
 116                         jint sx1, jint sy1, jint sx2, jint sy2,
 117                         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 118 {
 119     if (mtlc == NULL || src == nil || dst == nil)
 120         return;
 121 
 122 //    J2dTraceLn2(J2D_TRACE_VERBOSE, "_drawTex2Tex: src tex=%p, dst tex=%p", src, dst);
 123 //    J2dTraceLn4(J2D_TRACE_VERBOSE, "  sw=%d sh=%d dw=%d dh=%d", src.width, src.height, dst.width, dst.height);
 124 //    J2dTraceLn4(J2D_TRACE_VERBOSE, "  sx1=%d sy1=%d sx2=%d sy2=%d", sx1, sy1, sx2, sy2);
 125 //    J2dTraceLn4(J2D_TRACE_VERBOSE, "  dx1=%f dy1=%f dx2=%f dy2=%f", dx1, dy1, dx2, dy2);
 126 
 127     id<MTLRenderCommandEncoder> encoder = [mtlc createSamplingEncoderForDest:dst];
 128 
 129 
 130     const jboolean normalize = !mtlc.useTransform;
 131     struct TxtVertex quadTxVerticesBuffer[6];
 132     fillTxQuad(quadTxVerticesBuffer, sx1, sy1, sx2, sy2, src.width, src.height, dx1, dy1, dx2, dy2, dst.width, dst.height);
 133 
 134     [encoder setVertexBytes:quadTxVerticesBuffer length:sizeof(quadTxVerticesBuffer) atIndex:MeshVertexBuffer];
 135     [encoder setFragmentTexture:src atIndex: 0];
 136     [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
 137     [encoder endEncoding];
 138 }
 139 
 140 /**
 141  * Inner loop used for copying a source MTL "Texture" to a destination
 142  * MTL "Surface".  This method is invoked from MTLBlitLoops_IsoBlit().
 143  *
 144  * This method will copy, scale, or transform the source texture into the
 145  * destination depending on the transform state, as established in
 146  * and MTLContext_SetTransform().  If the source texture is
 147  * transformed in any way when rendered into the destination, the filtering
 148  * method applied is determined by the hint parameter (can be GL_NEAREST or
 149  * GL_LINEAR).
 150  */
 151 static void
 152 MTLBlitTextureToSurface(MTLContext *mtlc,
 153                         BMTLSDOps *srcOps, BMTLSDOps *dstOps,
 154                         jboolean rtt, jint hint,
 155                         jint sx1, jint sy1, jint sx2, jint sy2,
 156                         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 157 {
 158     id<MTLTexture> srcTex = srcOps->pTexture;
 159 
 160 #ifdef DEBUG
 161     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, "MTLBlitLoops_IsoBlit [via sampling]: bsrc=%p [tex=%p], bdst=%p [tex=%p] | s (%dx%d) -> d (%dx%d) | src (%d, %d, %d, %d) -> dst (%1.2f, %1.2f, %1.2f, %1.2f)", srcOps, srcOps->pTexture, dstOps, dstOps->pTexture, srcTex.width, srcTex.height, dstOps->width, dstOps->height, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
 162 #endif //DEBUG
 163 
 164     drawTex2Tex(mtlc, srcOps->pTexture, dstOps->pTexture, rtt, hint, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
 165 }
 166 
 167 /**
 168  * Inner loop used for copying a source system memory ("Sw") surface to a
 169  * destination MTL "Surface".  This method is invoked from
 170  * MTLBlitLoops_Blit().
 171  *
 172  * The standard glDrawPixels() mechanism is used to copy the source region
 173  * into the destination region.  If the regions have different
 174  * dimensions, the source will be scaled into the destination
 175  * as appropriate (only nearest neighbor filtering will be applied for simple
 176  * scale operations).
 177  */
 178 
 179 static void
 180 MTLBlitSwToSurfaceViaTexture(MTLContext *ctx, SurfaceDataRasInfo *srcInfo, BMTLSDOps * bmtlsdOps,
 181                    MTPixelFormat *pf,
 182                    jint sx1, jint sy1, jint sx2, jint sy2,
 183                    jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 184 {
 185     if (bmtlsdOps == NULL || bmtlsdOps->pTexture == NULL) {
 186         J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitSwToSurfaceViaTexture: dest is null");
 187         return;
 188     }
 189 
 190     const int sw = sx2 - sx1;
 191     const int sh = sy2 - sy1;
 192     id<MTLTexture> dest = bmtlsdOps->pTexture;
 193 
 194 #ifdef DEBUG
 195     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, "MTLBlitLoops_Blit [via pooled texture]: bdst=%p [tex=%p], sw=%d, sh=%d | src (%d, %d, %d, %d) -> dst (%1.2f, %1.2f, %1.2f, %1.2f)", bmtlsdOps, dest, sw, sh, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
 196 #endif //DEBUG
 197 
 198     id<MTLTexture> texBuff = [ctx.texturePool getTexture:sw height:sh format:MTLPixelFormatBGRA8Unorm];
 199     if (texBuff == nil) {
 200         J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitSwToSurfaceViaTexture: can't obtain temporary texture object from pool");
 201         return;
 202     }
 203     MTLRegion region = MTLRegionMake2D(0, 0, sw, sh);
 204     [texBuff replaceRegion:region mipmapLevel:0 withBytes:srcInfo->rasBase bytesPerRow:srcInfo->scanStride]; // texBuff is locked for current frame
 205 
 206     drawTex2Tex(ctx, texBuff, dest, 0, 0, 0, 0, sw, sh, dx1, dy1, dx2, dy2);
 207 }
 208 
 209 /**
 210  * Inner loop used for copying a source system memory ("Sw") surface or
 211  * MTL "Surface" to a destination OpenGL "Surface", using an MTL texture
 212  * tile as an intermediate surface.  This method is invoked from
 213  * MTLBlitLoops_Blit() for "Sw" surfaces and MTLBlitLoops_IsoBlit() for
 214  * "Surface" surfaces.
 215  *
 216  * This method is used to transform the source surface into the destination.
 217  * Pixel rectangles cannot be arbitrarily transformed (without the
 218  * GL_EXT_pixel_transform extension, which is not supported on most modern
 219  * hardware).  However, texture mapped quads do respect the GL_MODELVIEW
 220  * transform matrix, so we use textures here to perform the transform
 221  * operation.  This method uses a tile-based approach in which a small
 222  * subregion of the source surface is copied into a cached texture tile.  The
 223  * texture tile is then mapped into the appropriate location in the
 224  * destination surface.
 225  *
 226  * REMIND: this only works well using GL_NEAREST for the filtering mode
 227  *         (GL_LINEAR causes visible stitching problems between tiles,
 228  *         but this can be fixed by making use of texture borders)
 229  */
 230 static void
 231 MTLBlitToSurfaceViaTexture(MTLContext *mtlc, SurfaceDataRasInfo *srcInfo,
 232                            MTPixelFormat *pf, MTLSDOps *srcOps,
 233                            jboolean swsurface, jint hint,
 234                            jint sx1, jint sy1, jint sx2, jint sy2,
 235                            jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 236 {
 237     //TODO
 238     //J2dTraceNotImplPrimitive("MTLBlitToSurfaceViaTexture");
 239 }
 240 
 241 /**
 242  * Inner loop used for copying a source system memory ("Sw") surface to a
 243  * destination OpenGL "Texture".  This method is invoked from
 244  * MTLBlitLoops_Blit().
 245  *
 246  * The source surface is effectively loaded into the MTL texture object,
 247  * which must have already been initialized by MTLSD_initTexture().  Note
 248  * that this method is only capable of copying the source surface into the
 249  * destination surface (i.e. no scaling or general transform is allowed).
 250  * This restriction should not be an issue as this method is only used
 251  * currently to cache a static system memory image into an MTL texture in
 252  * a hidden-acceleration situation.
 253  */
 254 static void
 255 MTLBlitSwToTexture(SurfaceDataRasInfo *srcInfo, MTPixelFormat *pf,
 256                    MTLSDOps *dstOps,
 257                    jint dx1, jint dy1, jint dx2, jint dy2)
 258 {
 259     //TODO
 260     //J2dTraceNotImplPrimitive("MTLBlitSwToTexture");
 261 }
 262 
 263 /**
 264  * General blit method for copying a native MTL surface (of type "Surface"
 265  * or "Texture") to another MTL "Surface".  If texture is JNI_TRUE, this
 266  * method will invoke the Texture->Surface inner loop; otherwise, one of the
 267  * Surface->Surface inner loops will be invoked, depending on the transform
 268  * state.
 269  *
 270  * REMIND: we can trick these blit methods into doing XOR simply by passing
 271  *         in the (pixel ^ xorpixel) as the pixel value and preceding the
 272  *         blit with a fillrect...
 273  */
 274 void
 275 MTLBlitLoops_IsoBlit(JNIEnv *env,
 276                      MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
 277                      jboolean xform, jint hint,
 278                      jboolean texture, jboolean rtt,
 279                      jint sx1, jint sy1, jint sx2, jint sy2,
 280                      jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 281 {
 282     BMTLSDOps *srcOps = (BMTLSDOps *)jlong_to_ptr(pSrcOps);
 283     BMTLSDOps *dstOps = (BMTLSDOps *)jlong_to_ptr(pDstOps);
 284 
 285     RETURN_IF_NULL(srcOps);
 286     RETURN_IF_NULL(dstOps);
 287 
 288     id<MTLTexture> srcTex = srcOps->pTexture;
 289     id<MTLTexture> dstTex = dstOps->pTexture;
 290     if (mtlc == NULL || srcTex == nil || srcTex == nil) {
 291         J2dTraceLn2(J2D_TRACE_ERROR, "MTLBlitLoops_IsoBlit: surface is null (stex=%p, dtex=%p)", srcTex, dstTex);
 292         return;
 293     }
 294 
 295     const jint sw    = sx2 - sx1;
 296     const jint sh    = sy2 - sy1;
 297     const jdouble dw = dx2 - dx1;
 298     const jdouble dh = dy2 - dy1;
 299 
 300     if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) {
 301         J2dTraceLn4(J2D_TRACE_WARNING, "MTLBlitLoops_IsoBlit: invalid dimensions: sw=%d, sh%d, dw=%d, dh=%d", sw, sh, dw, dh);
 302         return;
 303     }
 304 
 305     SurfaceDataRasInfo srcInfo;
 306     srcInfo.bounds.x1 = sx1;
 307     srcInfo.bounds.y1 = sy1;
 308     srcInfo.bounds.x2 = sx2;
 309     srcInfo.bounds.y2 = sy2;
 310     SurfaceData_IntersectBoundsXYXY(&srcInfo.bounds, 0, 0, srcOps->width, srcOps->height);
 311 
 312     if (srcInfo.bounds.x2 <= srcInfo.bounds.x1 || srcInfo.bounds.y2 <= srcInfo.bounds.y1) {
 313         J2dTraceLn(J2D_TRACE_VERBOSE, "MTLBlitLoops_IsoBlit: source rectangle doesn't intersect with source surface bounds");
 314         J2dTraceLn6(J2D_TRACE_VERBOSE, "  sx1=%d sy1=%d sx2=%d sy2=%d sw=%d sh=%d", sx1, sy1, sx2, sy2, srcOps->width, srcOps->height);
 315         J2dTraceLn4(J2D_TRACE_VERBOSE, "  dx1=%f dy1=%f dx2=%f dy2=%f", dx1, dy1, dx2, dy2);
 316         return;
 317     }
 318 
 319     if (srcInfo.bounds.x1 != sx1) {
 320         dx1 += (srcInfo.bounds.x1 - sx1) * (dw / sw);
 321         sx1 = srcInfo.bounds.x1;
 322     }
 323     if (srcInfo.bounds.y1 != sy1) {
 324         dy1 += (srcInfo.bounds.y1 - sy1) * (dh / sh);
 325         sy1 = srcInfo.bounds.y1;
 326     }
 327     if (srcInfo.bounds.x2 != sx2) {
 328         dx2 += (srcInfo.bounds.x2 - sx2) * (dw / sw);
 329         sx2 = srcInfo.bounds.x2;
 330     }
 331     if (srcInfo.bounds.y2 != sy2) {
 332         dy2 += (srcInfo.bounds.y2 - sy2) * (dh / sh);
 333         sy2 = srcInfo.bounds.y2;
 334     }
 335 
 336     const jboolean useBlitEncoder =
 337             mtlc.isBlendingDisabled
 338             && fabs(dx2 - dx1 - sx2 + sx1) < 0.001f && fabs(dy2 - dy1 - sy2 + sy1) < 0.001f // dimensions are equal (TODO: check that dx1,dy1 is integer)
 339             && !mtlc.useTransform; // TODO: check whether transform is simple translate (and use blitEncoder in this case)
 340     if (useBlitEncoder) {
 341 #ifdef DEBUG
 342         J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, "MTLBlitLoops_IsoBlit [via blitEncoder]: bdst=%p [tex=%p] %dx%d | src (%d, %d, %d, %d) -> dst (%1.2f, %1.2f, %1.2f, %1.2f)", dstOps, dstTex, dstTex.width, dstTex.height, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
 343 #endif //DEBUG
 344         id <MTLBlitCommandEncoder> blitEncoder = [mtlc createBlitEncoder];
 345         [blitEncoder copyFromTexture:srcTex sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(sx1, sy1, 0) sourceSize:MTLSizeMake(sx2 - sx1, sy2 - sy1, 1) toTexture:dstTex destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(dx1, dy1, 0)];
 346         [blitEncoder endEncoding];
 347     } else {
 348         // TODO: support other flags
 349         MTLBlitTextureToSurface(mtlc, srcOps, dstOps, rtt, hint, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
 350     }
 351 }
 352 
 353 /**
 354  * General blit method for copying a system memory ("Sw") surface to a native
 355  * MTL surface (of type "Surface" or "Texture").  If texture is JNI_TRUE,
 356  * this method will invoke the Sw->Texture inner loop; otherwise, one of the
 357  * Sw->Surface inner loops will be invoked, depending on the transform state.
 358  */
 359 void
 360 MTLBlitLoops_Blit(JNIEnv *env,
 361                   MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
 362                   jboolean xform, jint hint,
 363                   jint srctype, jboolean texture,
 364                   jint sx1, jint sy1, jint sx2, jint sy2,
 365                   jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 366 {
 367     RETURN_IF_NULL(jlong_to_ptr(pSrcOps));
 368     RETURN_IF_NULL(jlong_to_ptr(pDstOps));
 369 
 370     SurfaceDataOps *srcOps = (SurfaceDataOps *)jlong_to_ptr(pSrcOps);
 371     BMTLSDOps *dstOps = (BMTLSDOps *)jlong_to_ptr(pDstOps);
 372     SurfaceDataRasInfo srcInfo;
 373     MTLPixelFormat pf = MTLPixelFormatBGRA8Unorm;//PixelFormats[srctype];
 374 
 375     if (dstOps == NULL || dstOps->pTexture == NULL) {
 376         J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitLoops_Blit: dest is null");
 377         return;
 378     }
 379     id<MTLTexture> dest = dstOps->pTexture;
 380     if (dx1 < 0) {
 381         sx1 += dx1;
 382         dx1 = 0;
 383     }
 384     if (dx2 > dest.width) {
 385         sx2 -= dx2 - dest.width;
 386         dx2 = dest.width;
 387     }
 388     if (dy1 < 0) {
 389         sy1 += dy1;
 390         dy1 = 0;
 391     }
 392     if (dy2 > dest.height) {
 393         sy2 -= dy2 - dest.height;
 394         dy2 = dest.height;
 395     }
 396     jint sw    = sx2 - sx1;
 397     jint sh    = sy2 - sy1;
 398     jdouble dw = dx2 - dx1;
 399     jdouble dh = dy2 - dy1;
 400 
 401     if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0 || srctype < 0) {
 402         J2dTraceLn(J2D_TRACE_WARNING, "MTLBlitLoops_Blit: invalid dimensions or srctype");
 403         return;
 404     }
 405 
 406     srcInfo.bounds.x1 = sx1;
 407     srcInfo.bounds.y1 = sy1;
 408     srcInfo.bounds.x2 = sx2;
 409     srcInfo.bounds.y2 = sy2;
 410 
 411     if (srcOps->Lock(env, srcOps, &srcInfo, SD_LOCK_READ) != SD_SUCCESS) {
 412         J2dTraceLn(J2D_TRACE_WARNING, "MTLBlitLoops_Blit: could not acquire lock");
 413         return;
 414     }
 415 
 416     J2dTraceLn5(J2D_TRACE_VERBOSE, "MTLBlitLoops_Blit:  pf=%d texture=%d srctype=%d xform=%d hint=%d", pf, texture, srctype, xform, hint);
 417 
 418     if (srcInfo.bounds.x2 > srcInfo.bounds.x1 && srcInfo.bounds.y2 > srcInfo.bounds.y1) {
 419         srcOps->GetRasInfo(env, srcOps, &srcInfo);
 420         if (srcInfo.rasBase) {
 421             if (srcInfo.bounds.x1 != sx1) {
 422                 dx1 += (srcInfo.bounds.x1 - sx1) * (dw / sw);
 423                 sx1 = srcInfo.bounds.x1;
 424             }
 425             if (srcInfo.bounds.y1 != sy1) {
 426                 dy1 += (srcInfo.bounds.y1 - sy1) * (dh / sh);
 427                 sy1 = srcInfo.bounds.y1;
 428             }
 429             if (srcInfo.bounds.x2 != sx2) {
 430                 dx2 += (srcInfo.bounds.x2 - sx2) * (dw / sw);
 431                 sx2 = srcInfo.bounds.x2;
 432             }
 433             if (srcInfo.bounds.y2 != sy2) {
 434                 dy2 += (srcInfo.bounds.y2 - sy2) * (dh / sh);
 435                 sy2 = srcInfo.bounds.y2;
 436             }
 437 
 438             // NOTE: if (texture) => dest coordinates will always be integers since we only ever do a straight copy from sw to texture.
 439             const jboolean useReplaceRegion = texture ||
 440                     (mtlc.isBlendingDisabled
 441                     && fabs(dx2 - dx1 - sx2 + sx1) < 0.001f && fabs(dy2 - dy1 - sy2 + sy1) < 0.001f // dimensions are equal (TODO: check that dx1,dy1 is integer)
 442                     && !mtlc.useTransform); // TODO: check whether transform is simple translate (and use replaceRegion in this case)
 443             if (useReplaceRegion) {
 444                 MTLRegion region = MTLRegionMake2D(dx1, dy1, dx2 - dx1, dy2 - dy1);
 445 #ifdef DEBUG
 446                 J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, "MTLBlitLoops_Blit [replaceRegion]: bdst=%p [tex=%p] %dx%d | src (%d, %d, %d, %d) -> dst (%1.2f, %1.2f, %1.2f, %1.2f)", dstOps, dest, dest.width, dest.height, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
 447 #endif //DEBUG
 448                 [dest replaceRegion:region mipmapLevel:0 withBytes:srcInfo.rasBase bytesPerRow:srcInfo.scanStride]; // executed at CPU (sync), TODO: lock dest for current frame
 449             } else {
 450                 MTLBlitSwToSurfaceViaTexture(mtlc, &srcInfo, dstOps, &pf, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
 451             }
 452         }
 453         SurfaceData_InvokeRelease(env, srcOps, &srcInfo);
 454     }
 455     SurfaceData_InvokeUnlock(env, srcOps, &srcInfo);
 456 }
 457 
 458 /**
 459  * Specialized blit method for copying a native MTL "Surface" (pbuffer,
 460  * window, etc.) to a system memory ("Sw") surface.
 461  */
 462 void
 463 MTLBlitLoops_SurfaceToSwBlit(JNIEnv *env, MTLContext *mtlc,
 464                              jlong pSrcOps, jlong pDstOps, jint dsttype,
 465                              jint srcx, jint srcy, jint dstx, jint dsty,
 466                              jint width, jint height)
 467 {
 468     //TODO
 469     //J2dTraceNotImplPrimitive("MTLBlitLoops_SurfaceToSwBlit");
 470 }
 471 
 472 void
 473 MTLBlitLoops_CopyArea(JNIEnv *env,
 474                       MTLContext *mtlc, BMTLSDOps *dstOps,
 475                       jint x, jint y, jint width, jint height,
 476                       jint dx, jint dy)
 477 {
 478     //TODO
 479     //J2dTraceNotImplPrimitive("MTLBlitLoops_CopyArea");
 480 }
 481 
 482 #endif /* !HEADLESS */