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 #import <Accelerate/Accelerate.h>
 43 
 44 //#define TRACE_ISOBLIT
 45 //#define TRACE_BLIT
 46 //#define DEBUG_ISOBLIT
 47 //#define DEBUG_BLIT
 48 
 49 typedef struct {
 50     MTLPixelFormat   format;
 51     jboolean hasAlpha;
 52     jboolean isPremult;
 53     const uint8_t * permuteMap;
 54 } MTLRasterFormatInfo;
 55 
 56 // 0 denotes the alpha channel, 1 the red channel, 2 the green channel, and 3 the blue channel.
 57 const uint8_t permuteMap_rgbx[4] = { 1, 2, 3, 0 };
 58 const uint8_t permuteMap_bgrx[4] = { 3, 2, 1, 0 };
 59 
 60 static uint8_t revertPerm(const uint8_t * perm, uint8_t pos) {
 61     for (int c = 0; c < 4; ++c) {
 62         if (perm[c] == pos)
 63             return c;
 64     }
 65     return -1;
 66 }
 67 
 68 #define uint2swizzle(channel) (channel == 0 ? MTLTextureSwizzleAlpha : (channel == 1 ? MTLTextureSwizzleRed : (channel == 2 ? MTLTextureSwizzleGreen : (channel == 3 ? MTLTextureSwizzleBlue : MTLTextureSwizzleZero))))
 69 
 70 /**
 71  * This table contains the "pixel formats" for all system memory surfaces
 72  * that Metal is capable of handling, indexed by the "PF_" constants defined
 73  * in MTLLSurfaceData.java.  These pixel formats contain information that is
 74  * passed to Metal when copying from a system memory ("Sw") surface to
 75  * an Metal surface
 76  */
 77 MTLRasterFormatInfo RasterFormatInfos[] = {
 78         { MTLPixelFormatBGRA8Unorm, 1, 0, NULL }, /* 0 - IntArgb      */ // Argb (in java notation)
 79         { MTLPixelFormatBGRA8Unorm, 1, 1, NULL }, /* 1 - IntArgbPre   */
 80         { MTLPixelFormatBGRA8Unorm, 0, 1, NULL }, /* 2 - IntRgb       */ // xrgb
 81         { MTLPixelFormatBGRA8Unorm, 0, 1, permuteMap_rgbx }, /* 3 - IntRgbx      */
 82         { MTLPixelFormatRGBA8Unorm, 0, 1, NULL }, /* 4 - IntBgr       */ // xbgr
 83         { MTLPixelFormatBGRA8Unorm, 0, 1, permuteMap_bgrx }, /* 5 - IntBgrx      */
 84 
 85 //        TODO: support 2-byte formats
 86 //        { GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV,
 87 //                2, 0, 1,                                     }, /* 7 - Ushort555Rgb */
 88 //        { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1,
 89 //                2, 0, 1,                                     }, /* 8 - Ushort555Rgbx*/
 90 //        { GL_LUMINANCE, GL_UNSIGNED_BYTE,
 91 //                1, 0, 1,                                     }, /* 9 - ByteGray     */
 92 //        { GL_LUMINANCE, GL_UNSIGNED_SHORT,
 93 //                2, 0, 1,                                     }, /*10 - UshortGray   */
 94 //        { GL_BGR,  GL_UNSIGNED_BYTE,
 95 //                1, 0, 1,                                     }, /*11 - ThreeByteBgr */
 96 };
 97 
 98 extern void J2dTraceImpl(int level, jboolean cr, const char *string, ...);
 99 
100 void fillTxQuad(
101         struct TxtVertex * txQuadVerts,
102         jint sx1, jint sy1, jint sx2, jint sy2, jint sw, jint sh,
103         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2, jdouble dw, jdouble dh
104 ) {
105     const float nsx1 = sx1/(float)sw;
106     const float nsy1 = sy1/(float)sh;
107     const float nsx2 = sx2/(float)sw;
108     const float nsy2 = sy2/(float)sh;
109 
110     txQuadVerts[0].position[0] = dx1;
111     txQuadVerts[0].position[1] = dy1;
112     txQuadVerts[0].txtpos[0]   = nsx1;
113     txQuadVerts[0].txtpos[1]   = nsy1;
114 
115     txQuadVerts[1].position[0] = dx2;
116     txQuadVerts[1].position[1] = dy1;
117     txQuadVerts[1].txtpos[0]   = nsx2;
118     txQuadVerts[1].txtpos[1]   = nsy1;
119 
120     txQuadVerts[2].position[0] = dx2;
121     txQuadVerts[2].position[1] = dy2;
122     txQuadVerts[2].txtpos[0]   = nsx2;
123     txQuadVerts[2].txtpos[1]   = nsy2;
124 
125     txQuadVerts[3].position[0] = dx2;
126     txQuadVerts[3].position[1] = dy2;
127     txQuadVerts[3].txtpos[0]   = nsx2;
128     txQuadVerts[3].txtpos[1]   = nsy2;
129 
130     txQuadVerts[4].position[0] = dx1;
131     txQuadVerts[4].position[1] = dy2;
132     txQuadVerts[4].txtpos[0]   = nsx1;
133     txQuadVerts[4].txtpos[1]   = nsy2;
134 
135     txQuadVerts[5].position[0] = dx1;
136     txQuadVerts[5].position[1] = dy1;
137     txQuadVerts[5].txtpos[0]   = nsx1;
138     txQuadVerts[5].txtpos[1]   = nsy1;
139 }
140 
141 //#define TRACE_drawTex2Tex
142 
143 void drawTex2Tex(MTLContext *mtlc,
144                         id<MTLTexture> src, id<MTLTexture> dst,
145                         jboolean isSrcOpaque, jboolean isDstOpaque, jint hint,
146                         jint sx1, jint sy1, jint sx2, jint sy2,
147                         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
148 {
149 #ifdef TRACE_drawTex2Tex
150     J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "drawTex2Tex: src tex=%p, dst tex=%p", src, dst);
151     J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "  sw=%d sh=%d dw=%d dh=%d", src.width, src.height, dst.width, dst.height);
152     J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "  sx1=%d sy1=%d sx2=%d sy2=%d", sx1, sy1, sx2, sy2);
153     J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "  dx1=%f dy1=%f dx2=%f dy2=%f", dx1, dy1, dx2, dy2);
154 #endif //TRACE_drawTex2Tex
155 
156     id<MTLRenderCommandEncoder> encoder = [mtlc.encoderManager getTextureEncoder:dst
157                                                                      isSrcOpaque:isSrcOpaque
158                                                                      isDstOpaque:isDstOpaque
159                                                                    interpolation:hint
160     ];
161 
162     struct TxtVertex quadTxVerticesBuffer[6];
163     fillTxQuad(quadTxVerticesBuffer, sx1, sy1, sx2, sy2, src.width, src.height, dx1, dy1, dx2, dy2, dst.width, dst.height);
164 
165     [encoder setVertexBytes:quadTxVerticesBuffer length:sizeof(quadTxVerticesBuffer) atIndex:MeshVertexBuffer];
166     [encoder setFragmentTexture:src atIndex: 0];
167     [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
168 }
169 
170 static
171 id<MTLTexture> replaceTextureRegion(id<MTLTexture> dest, const SurfaceDataRasInfo * srcInfo, const MTLRasterFormatInfo * rfi, int dx1, int dy1, int dx2, int dy2) {
172     const int dw = dx2 - dx1;
173     const int dh = dy2 - dy1;
174 
175     const void * raster = srcInfo->rasBase;
176     id<MTLTexture> result = nil;
177     if (rfi->permuteMap != NULL) {
178 #if defined(__MAC_10_15) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_15
179         if (@available(macOS 10.15, *)) {
180             @autoreleasepool {
181                 const uint8_t swzRed = revertPerm(rfi->permuteMap, 1);
182                 const uint8_t swzGreen = revertPerm(rfi->permuteMap, 2);
183                 const uint8_t swzBlue = revertPerm(rfi->permuteMap, 3);
184                 const uint8_t swzAlpha = revertPerm(rfi->permuteMap, 0);
185                 MTLTextureSwizzleChannels swizzle = MTLTextureSwizzleChannelsMake(
186                         uint2swizzle(swzRed),
187                         uint2swizzle(swzGreen),
188                         uint2swizzle(swzBlue),
189                         rfi->hasAlpha ? uint2swizzle(swzAlpha) : MTLTextureSwizzleOne
190                 );
191                 result = [dest
192                         newTextureViewWithPixelFormat:MTLPixelFormatBGRA8Unorm
193                         textureType:MTLTextureType2D
194                         levels:NSMakeRange(0, 1) slices:NSMakeRange(0, 1)
195                         swizzle:swizzle];
196                 J2dTraceLn5(J2D_TRACE_VERBOSE, "replaceTextureRegion [use swizzle for pooled]: %d, %d, %d, %d, hasA=%d",
197                             swizzle.red, swizzle.green, swizzle.blue, swizzle.alpha, rfi->hasAlpha);
198             }
199         } else
200 #endif // __MAC_10_15 && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_15
201         {
202             // perform raster conversion
203             // invoked only from rq-thread, so use static buffers
204             // but it's better to use thread-local buffers (or special buffer manager)
205             const int destRasterSize = dw*dh*4;
206 
207             static int bufferSize = 0;
208             static void * buffer = NULL;
209             if (buffer == NULL || bufferSize < destRasterSize) {
210                 bufferSize = destRasterSize;
211                 buffer = realloc(buffer, bufferSize);
212             }
213             if (buffer == NULL) {
214                 J2dTraceLn1(J2D_TRACE_ERROR, "replaceTextureRegion: can't alloc buffer for raster conversion, size=%d", bufferSize);
215                 bufferSize = 0;
216                 return nil;
217             }
218             vImage_Buffer srcBuf;
219             srcBuf.height = dh;
220             srcBuf.width = dw;
221             srcBuf.rowBytes = srcInfo->scanStride;
222             srcBuf.data = srcInfo->rasBase;
223 
224             vImage_Buffer destBuf;
225             destBuf.height = dh;
226             destBuf.width = dw;
227             destBuf.rowBytes = dw*4;
228             destBuf.data = buffer;
229 
230             vImagePermuteChannels_ARGB8888(&srcBuf, &destBuf, rfi->permuteMap, kvImageNoFlags);
231             raster = buffer;
232 
233             J2dTraceLn5(J2D_TRACE_VERBOSE, "replaceTextureRegion [use conversion]: %d, %d, %d, %d, hasA=%d",
234                         rfi->permuteMap[0], rfi->permuteMap[1], rfi->permuteMap[2], rfi->permuteMap[3], rfi->hasAlpha);
235         }
236     }
237 
238     MTLRegion region = MTLRegionMake2D(dx1, dy1, dw, dh);
239     if (result != nil)
240         dest = result;
241     [dest replaceRegion:region mipmapLevel:0 withBytes:raster bytesPerRow:srcInfo->scanStride];
242     return result;
243 }
244 
245 /**
246  * Inner loop used for copying a source system memory ("Sw") surface to a
247  * destination MTL "Surface".  This method is invoked from
248  * MTLBlitLoops_Blit().
249  */
250 
251 static void
252 MTLBlitSwToTextureViaPooledTexture(
253         MTLContext *mtlc, SurfaceDataRasInfo *srcInfo, BMTLSDOps * bmtlsdOps,
254         MTLRasterFormatInfo * rfi, jboolean useBlitEncoder, jint hint,
255         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
256 {
257     const int sw = srcInfo->bounds.x2 - srcInfo->bounds.x1;
258     const int sh = srcInfo->bounds.y2 - srcInfo->bounds.y1;
259     id<MTLTexture> dest = bmtlsdOps->pTexture;
260 
261     MTLPooledTextureHandle * texHandle = [mtlc.texturePool getTexture:sw height:sh format:rfi->format];
262     if (texHandle == nil) {
263         J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitSwToTextureViaPooledTexture: can't obtain temporary texture object from pool");
264         return;
265     }
266     [[mtlc getCommandBufferWrapper] registerPooledTexture:texHandle];
267     [texHandle release];
268 
269     id<MTLTexture> texBuff = texHandle.texture;
270     id<MTLTexture> swizzledTexture = replaceTextureRegion(texBuff, srcInfo, rfi, 0, 0, sw, sh);
271     if (useBlitEncoder) {
272         id <MTLBlitCommandEncoder> blitEncoder = [mtlc.encoderManager createBlitEncoder];
273         [blitEncoder copyFromTexture:swizzledTexture != nil ? swizzledTexture : texBuff
274                          sourceSlice:0
275                          sourceLevel:0
276                         sourceOrigin:MTLOriginMake(0, 0, 0)
277                           sourceSize:MTLSizeMake(sw, sh, 1)
278                            toTexture:dest
279                     destinationSlice:0
280                     destinationLevel:0
281                    destinationOrigin:MTLOriginMake(dx1, dy1, 0)];
282         [blitEncoder endEncoding];
283     } else {
284         drawTex2Tex(mtlc, swizzledTexture != nil ? swizzledTexture : texBuff, dest, !rfi->hasAlpha, bmtlsdOps->isOpaque, hint,
285                     0, 0, sw, sh, dx1, dy1, dx2, dy2);
286     }
287 
288     if (swizzledTexture != nil) {
289         [swizzledTexture release];
290     }
291 }
292 
293 static
294 jboolean isIntegerAndUnscaled(
295         jint sx1, jint sy1, jint sx2, jint sy2,
296         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2
297 ) {
298     const jdouble epsilon = 0.0001f;
299 
300     // check that dx1,dy1 is integer
301     if (fabs(dx1 - (int)dx1) > epsilon || fabs(dy1 - (int)dy1) > epsilon) {
302         return JNI_FALSE;
303     }
304     // check that destSize equals srcSize
305     if (fabs(dx2 - dx1 - sx2 + sx1) > epsilon || fabs(dy2 - dy1 - sy2 + sy1) > epsilon) {
306         return JNI_FALSE;
307     }
308     return JNI_TRUE;
309 }
310 
311 static
312 jboolean clipDestCoords(
313         jdouble *dx1, jdouble *dy1, jdouble *dx2, jdouble *dy2,
314         jint *sx1, jint *sy1, jint *sx2, jint *sy2,
315         jint destW, jint destH, const MTLScissorRect * clipRect
316 ) {
317     // Trim destination rect by clip-rect (or dest.bounds)
318     const jint sw    = *sx2 - *sx1;
319     const jint sh    = *sy2 - *sy1;
320     const jdouble dw = *dx2 - *dx1;
321     const jdouble dh = *dy2 - *dy1;
322 
323     jdouble dcx1 = 0;
324     jdouble dcx2 = destW;
325     jdouble dcy1 = 0;
326     jdouble dcy2 = destH;
327     if (clipRect != NULL) {
328         if (clipRect->x > dcx1)
329             dcx1 = clipRect->x;
330         const int maxX = clipRect->x + clipRect->width;
331         if (dcx2 > maxX)
332             dcx2 = maxX;
333         if (clipRect->y > dcy1)
334             dcy1 = clipRect->y;
335         const int maxY = clipRect->y + clipRect->height;
336         if (dcy2 > maxY)
337             dcy2 = maxY;
338 
339         if (dcx1 >= dcx2) {
340             J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcx1=%1.2f, dcx2=%1.2f", dcx1, dcx2);
341             dcx1 = dcx2;
342         }
343         if (dcy1 >= dcy2) {
344             J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcy1=%1.2f, dcy2=%1.2f", dcy1, dcy2);
345             dcy1 = dcy2;
346         }
347     }
348     if (*dx2 <= dcx1 || *dx1 >= dcx2 || *dy2 <= dcy1 || *dy1 >= dcy2) {
349         J2dTraceLn(J2D_TRACE_INFO, "\tclipDestCoords: dest rect doesn't intersect clip area");
350         return JNI_FALSE;
351     }
352     if (*dx1 < dcx1) {
353         J2dTraceLn2(J2D_TRACE_VERBOSE, "\t\tdx1=%1.2f, will be clipped to %1.2f", *dx1, dcx1);
354         *sx1 += (jint)((dcx1 - *dx1) * (sw/dw));
355         *dx1 = dcx1;
356     }
357     if (*dx2 > dcx2) {
358         J2dTraceLn2(J2D_TRACE_VERBOSE, "\t\tdx2=%1.2f, will be clipped to %1.2f", *dx2, dcx2);
359         *sx2 -= (jint)((*dx2 - dcx2) * (sw/dw));
360         *dx2 = dcx2;
361     }
362     if (*dy1 < dcy1) {
363         J2dTraceLn2(J2D_TRACE_VERBOSE, "\t\tdy1=%1.2f, will be clipped to %1.2f", *dy1, dcy1);
364         *sy1 += (jint)((dcy1 - *dy1) * (sh/dh));
365         *dy1 = dcy1;
366     }
367     if (*dy2 > dcy2) {
368         J2dTraceLn2(J2D_TRACE_VERBOSE, "\t\tdy2=%1.2f, will be clipped to %1.2f", *dy2, dcy2);
369         *sy2 -= (jint)((*dy2 - dcy2) * (sh/dh));
370         *dy2 = dcy2;
371     }
372     return JNI_TRUE;
373 }
374 
375 /**
376  * General blit method for copying a native MTL surface to another MTL "Surface".
377  * Parameter texture == true forces to use 'texture' codepath (dest coordinates will always be integers).
378  * Parameter xform == true only when AffineTransform is used (invoked only from TransformBlit, dest coordinates will always be integers).
379  */
380 void
381 MTLBlitLoops_IsoBlit(JNIEnv *env,
382                      MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
383                      jboolean xform, jint hint, jboolean texture,
384                      jint sx1, jint sy1, jint sx2, jint sy2,
385                      jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
386 {
387     BMTLSDOps *srcOps = (BMTLSDOps *)jlong_to_ptr(pSrcOps);
388     BMTLSDOps *dstOps = (BMTLSDOps *)jlong_to_ptr(pDstOps);
389 
390     RETURN_IF_NULL(mtlc);
391     RETURN_IF_NULL(srcOps);
392     RETURN_IF_NULL(dstOps);
393 
394     id<MTLTexture> srcTex = srcOps->pTexture;
395     id<MTLTexture> dstTex = dstOps->pTexture;
396     if (srcTex == nil || srcTex == nil) {
397         J2dTraceLn2(J2D_TRACE_ERROR, "MTLBlitLoops_IsoBlit: surface is null (stex=%p, dtex=%p)", srcTex, dstTex);
398         return;
399     }
400 
401     const jint sw    = sx2 - sx1;
402     const jint sh    = sy2 - sy1;
403     const jdouble dw = dx2 - dx1;
404     const jdouble dh = dy2 - dy1;
405 
406     if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) {
407         J2dTraceLn4(J2D_TRACE_WARNING, "MTLBlitLoops_IsoBlit: invalid dimensions: sw=%d, sh%d, dw=%d, dh=%d", sw, sh, dw, dh);
408         return;
409     }
410 
411 #ifdef DEBUG_ISOBLIT
412     if ((xform == JNI_TRUE) != (mtlc.useTransform == JNI_TRUE)) {
413         J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
414                 "MTLBlitLoops_IsoBlit state error: xform=%d, mtlc.useTransform=%d, texture=%d",
415                 xform, mtlc.useTransform, texture);
416     }
417 #endif // DEBUG_ISOBLIT
418 
419     clipDestCoords(
420             &dx1, &dy1, &dx2, &dy2,
421             &sx1, &sy1, &sx2, &sy2,
422             dstTex.width, dstTex.height, [mtlc.clip getRect]
423     );
424 
425     SurfaceDataBounds bounds;
426     bounds.x1 = sx1;
427     bounds.y1 = sy1;
428     bounds.x2 = sx2;
429     bounds.y2 = sy2;
430     SurfaceData_IntersectBoundsXYXY(&bounds, 0, 0, srcOps->width, srcOps->height);
431 
432     if (bounds.x2 <= bounds.x1 || bounds.y2 <= bounds.y1) {
433         J2dTraceLn(J2D_TRACE_VERBOSE, "MTLBlitLoops_IsoBlit: source rectangle doesn't intersect with source surface bounds");
434         J2dTraceLn6(J2D_TRACE_VERBOSE, "  sx1=%d sy1=%d sx2=%d sy2=%d sw=%d sh=%d", sx1, sy1, sx2, sy2, srcOps->width, srcOps->height);
435         J2dTraceLn4(J2D_TRACE_VERBOSE, "  dx1=%f dy1=%f dx2=%f dy2=%f", dx1, dy1, dx2, dy2);
436         return;
437     }
438 
439     if (bounds.x1 != sx1) {
440         dx1 += (bounds.x1 - sx1) * (dw / sw);
441         sx1 = bounds.x1;
442     }
443     if (bounds.y1 != sy1) {
444         dy1 += (bounds.y1 - sy1) * (dh / sh);
445         sy1 = bounds.y1;
446     }
447     if (bounds.x2 != sx2) {
448         dx2 += (bounds.x2 - sx2) * (dw / sw);
449         sx2 = bounds.x2;
450     }
451     if (bounds.y2 != sy2) {
452         dy2 += (bounds.y2 - sy2) * (dh / sh);
453         sy2 = bounds.y2;
454     }
455 
456 #ifdef TRACE_ISOBLIT
457     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE,
458          "MTLBlitLoops_IsoBlit [tx=%d, xf=%d, AC=%s]: src=%s, dst=%s | (%d, %d, %d, %d)->(%1.2f, %1.2f, %1.2f, %1.2f)",
459          texture, xform, [mtlc getCompositeDescription].cString,
460          getSurfaceDescription(srcOps).cString, getSurfaceDescription(dstOps).cString,
461          sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
462 #endif //TRACE_ISOBLIT
463 
464     if (!texture && !xform
465         && [mtlc isBlendingDisabled:srcOps->isOpaque]
466         && isIntegerAndUnscaled(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)
467         && (dstOps->isOpaque || !srcOps->isOpaque)
468     ) {
469 #ifdef TRACE_ISOBLIT
470         J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via blitEncoder]");
471 #endif //TRACE_ISOBLIT
472 
473         id <MTLBlitCommandEncoder> blitEncoder = [mtlc.encoderManager createBlitEncoder];
474         [blitEncoder copyFromTexture:srcTex
475                          sourceSlice:0
476                          sourceLevel:0
477                         sourceOrigin:MTLOriginMake(sx1, sy1, 0)
478                           sourceSize:MTLSizeMake(sx2 - sx1, sy2 - sy1, 1)
479                            toTexture:dstTex
480                     destinationSlice:0
481                     destinationLevel:0
482                    destinationOrigin:MTLOriginMake(dx1, dy1, 0)];
483         [blitEncoder endEncoding];
484         return;
485     }
486 
487 #ifdef TRACE_ISOBLIT
488     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via sampling]");
489 #endif //TRACE_ISOBLIT
490     drawTex2Tex(mtlc, srcTex, dstTex, srcOps->isOpaque, dstOps->isOpaque, hint, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
491 }
492 
493 /**
494  * General blit method for copying a system memory ("Sw") surface to a native MTL surface.
495  * Parameter texture == true only in SwToTextureBlit (straight copy from sw to texture), dest coordinates will always be integers.
496  * Parameter xform == true only when AffineTransform is used (invoked only from TransformBlit, dest coordinates will always be integers).
497  */
498 void
499 MTLBlitLoops_Blit(JNIEnv *env,
500                   MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
501                   jboolean xform, jint hint,
502                   jint srctype, jboolean texture,
503                   jint sx1, jint sy1, jint sx2, jint sy2,
504                   jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
505 {
506     SurfaceDataOps *srcOps = (SurfaceDataOps *)jlong_to_ptr(pSrcOps);
507     BMTLSDOps *dstOps = (BMTLSDOps *)jlong_to_ptr(pDstOps);
508 
509     RETURN_IF_NULL(mtlc);
510     RETURN_IF_NULL(srcOps);
511     RETURN_IF_NULL(dstOps);
512 
513     id<MTLTexture> dest = dstOps->pTexture;
514     if (dest == NULL) {
515         J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitLoops_Blit: dest is null");
516         return;
517     }
518     if (srctype < 0 || srctype >= sizeof(RasterFormatInfos)/ sizeof(MTLRasterFormatInfo)) {
519         J2dTraceLn1(J2D_TRACE_ERROR, "MTLBlitLoops_Blit: source pixel format %d isn't supported", srctype);
520         return;
521     }
522     const jint sw    = sx2 - sx1;
523     const jint sh    = sy2 - sy1;
524     const jdouble dw = dx2 - dx1;
525     const jdouble dh = dy2 - dy1;
526 
527     if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) {
528         J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitLoops_Blit: invalid dimensions");
529         return;
530     }
531 
532 #ifdef DEBUG_BLIT
533     if (
534         (xform == JNI_TRUE) != (mtlc.useTransform == JNI_TRUE)
535         || (xform && texture)
536     ) {
537         J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
538                 "MTLBlitLoops_Blit state error: xform=%d, mtlc.useTransform=%d, texture=%d",
539                 xform, mtlc.useTransform, texture);
540     }
541     if (texture) {
542         if (!isIntegerAndUnscaled(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)) {
543             J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
544                     "MTLBlitLoops_Blit state error: texture=true, but src and dst dimensions aren't equal or dest coords aren't integers");
545         }
546         if (!dstOps->isOpaque && !RasterFormatInfos[srctype].hasAlpha) {
547             J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
548                     "MTLBlitLoops_Blit state error: texture=true, but dest has alpha and source hasn't alpha, can't use texture-codepath");
549         }
550     }
551 #endif // DEBUG_BLIT
552 
553     clipDestCoords(
554             &dx1, &dy1, &dx2, &dy2,
555             &sx1, &sy1, &sx2, &sy2,
556             dest.width, dest.height, texture ? NULL : [mtlc.clip getRect]
557     );
558 
559     SurfaceDataRasInfo srcInfo;
560     srcInfo.bounds.x1 = sx1;
561     srcInfo.bounds.y1 = sy1;
562     srcInfo.bounds.x2 = sx2;
563     srcInfo.bounds.y2 = sy2;
564 
565     // NOTE: This function will modify the contents of the bounds field to represent the maximum available raster data.
566     if (srcOps->Lock(env, srcOps, &srcInfo, SD_LOCK_READ) != SD_SUCCESS) {
567         J2dTraceLn(J2D_TRACE_WARNING, "MTLBlitLoops_Blit: could not acquire lock");
568         return;
569     }
570 
571     if (srcInfo.bounds.x2 > srcInfo.bounds.x1 && srcInfo.bounds.y2 > srcInfo.bounds.y1) {
572         srcOps->GetRasInfo(env, srcOps, &srcInfo);
573         if (srcInfo.rasBase) {
574             if (srcInfo.bounds.x1 != sx1) {
575                 const int dx = srcInfo.bounds.x1 - sx1;
576                 dx1 += dx * (dw / sw);
577             }
578             if (srcInfo.bounds.y1 != sy1) {
579                 const int dy = srcInfo.bounds.y1 - sy1;
580                 dy1 += dy * (dh / sh);
581             }
582             if (srcInfo.bounds.x2 != sx2) {
583                 const int dx = srcInfo.bounds.x2 - sx2;
584                 dx2 += dx * (dw / sw);
585             }
586             if (srcInfo.bounds.y2 != sy2) {
587                 const int dy = srcInfo.bounds.y2 - sy2;
588                 dy2 += dy * (dh / sh);
589             }
590 
591 #ifdef TRACE_BLIT
592             J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE,
593                     "MTLBlitLoops_Blit [tx=%d, xf=%d, AC=%s]: bdst=%s, src=%p (%dx%d) O=%d premul=%d | (%d, %d, %d, %d)->(%1.2f, %1.2f, %1.2f, %1.2f)",
594                     texture, xform, [mtlc getCompositeDescription].cString,
595                     getSurfaceDescription(dstOps).cString, srcOps,
596                     sx2 - sx1, sy2 - sy1,
597                     RasterFormatInfos[srctype].hasAlpha ? 0 : 1, RasterFormatInfos[srctype].isPremult ? 1 : 0,
598                     sx1, sy1, sx2, sy2,
599                     dx1, dy1, dx2, dy2);
600 #endif //TRACE_BLIT
601 
602             MTLRasterFormatInfo rfi = RasterFormatInfos[srctype];
603             const jboolean useReplaceRegion = texture ||
604                     ([mtlc isBlendingDisabled:!rfi.hasAlpha]
605                     && !xform
606                     && isIntegerAndUnscaled(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2));
607 
608             if (useReplaceRegion) {
609                 if (dstOps->isOpaque || rfi.hasAlpha) {
610 #ifdef TRACE_BLIT
611                     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [replaceTextureRegion]");
612 #endif //TRACE_BLIT
613                     replaceTextureRegion(dest, &srcInfo, &rfi, (int) dx1, (int) dy1, (int) dx2, (int) dy2);
614                 } else {
615 #ifdef TRACE_BLIT
616                     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via pooled + blit]");
617 #endif //TRACE_BLIT
618                     MTLBlitSwToTextureViaPooledTexture(mtlc, &srcInfo, dstOps, &rfi, true, hint, dx1, dy1, dx2, dy2);
619                 }
620             } else { // !useReplaceRegion
621 #ifdef TRACE_BLIT
622                 J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via pooled texture]");
623 #endif //TRACE_BLIT
624                 MTLBlitSwToTextureViaPooledTexture(mtlc, &srcInfo, dstOps, &rfi, false, hint, dx1, dy1, dx2, dy2);
625             }
626         }
627         SurfaceData_InvokeRelease(env, srcOps, &srcInfo);
628     }
629     SurfaceData_InvokeUnlock(env, srcOps, &srcInfo);
630 }
631 
632 /**
633  * Specialized blit method for copying a native MTL "Surface" (pbuffer,
634  * window, etc.) to a system memory ("Sw") surface.
635  */
636 void
637 MTLBlitLoops_SurfaceToSwBlit(JNIEnv *env, MTLContext *mtlc,
638                              jlong pSrcOps, jlong pDstOps, jint dsttype,
639                              jint srcx, jint srcy, jint dstx, jint dsty,
640                              jint width, jint height)
641 {
642     J2dTraceLn6(J2D_TRACE_VERBOSE, "MTLBlitLoops_SurfaceToSwBlit: sx=%d sy=%d w=%d h=%d dx=%d dy=%d", srcx, srcy, width, height, dstx, dsty);
643 
644     BMTLSDOps *srcOps = (BMTLSDOps *)jlong_to_ptr(pSrcOps);
645     SurfaceDataOps *dstOps = (SurfaceDataOps *)jlong_to_ptr(pDstOps);
646     SurfaceDataRasInfo srcInfo, dstInfo;
647 
648     if (dsttype < 0 || dsttype >= sizeof(RasterFormatInfos)/ sizeof(MTLRasterFormatInfo)) {
649         J2dTraceLn1(J2D_TRACE_ERROR, "MTLBlitLoops_SurfaceToSwBlit: destination pixel format %d isn't supported", dsttype);
650         return;
651     }
652 
653     if (width <= 0 || height <= 0) {
654         J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitLoops_SurfaceToSwBlit: dimensions are non-positive");
655         return;
656     }
657 
658     RETURN_IF_NULL(srcOps);
659     RETURN_IF_NULL(dstOps);
660     RETURN_IF_NULL(mtlc);
661 
662     srcInfo.bounds.x1 = srcx;
663     srcInfo.bounds.y1 = srcy;
664     srcInfo.bounds.x2 = srcx + width;
665     srcInfo.bounds.y2 = srcy + height;
666     dstInfo.bounds.x1 = dstx;
667     dstInfo.bounds.y1 = dsty;
668     dstInfo.bounds.x2 = dstx + width;
669     dstInfo.bounds.y2 = dsty + height;
670 
671     if (dstOps->Lock(env, dstOps, &dstInfo, SD_LOCK_WRITE) != SD_SUCCESS) {
672         J2dTraceLn(J2D_TRACE_WARNING,"MTLBlitLoops_SurfaceToSwBlit: could not acquire dst lock");
673         return;
674     }
675 
676     SurfaceData_IntersectBoundsXYXY(&srcInfo.bounds,
677                                     0, 0, srcOps->width, srcOps->height);
678     SurfaceData_IntersectBlitBounds(&dstInfo.bounds, &srcInfo.bounds,
679                                     srcx - dstx, srcy - dsty);
680 
681     if (srcInfo.bounds.x2 > srcInfo.bounds.x1 &&
682         srcInfo.bounds.y2 > srcInfo.bounds.y1)
683     {
684         dstOps->GetRasInfo(env, dstOps, &dstInfo);
685         if (dstInfo.rasBase) {
686             void *pDst = dstInfo.rasBase;
687 
688             srcx = srcInfo.bounds.x1;
689             srcy = srcInfo.bounds.y1;
690             dstx = dstInfo.bounds.x1;
691             dsty = dstInfo.bounds.y1;
692             width = srcInfo.bounds.x2 - srcInfo.bounds.x1;
693             height = srcInfo.bounds.y2 - srcInfo.bounds.y1;
694 
695             pDst = PtrAddBytes(pDst, dstx * dstInfo.pixelStride);
696             pDst = PtrPixelsRow(pDst, dsty, dstInfo.scanStride);
697 
698             // this accounts for lower-left origin of the source region
699             srcx = srcOps->xOffset + srcx;
700             srcy = srcOps->yOffset + srcOps->height - srcy - height;
701             const int srcLength = width * height * 4; // NOTE: assume that src format is MTLPixelFormatBGRA8Unorm
702 
703 #ifdef DEBUG
704             void *pDstEnd = dstInfo.rasBase + (height - 1)*dstInfo.scanStride + width*dstInfo.pixelStride;
705             if (pDst + srcLength > pDstEnd) {
706                 J2dTraceLn6(J2D_TRACE_ERROR, "MTLBlitLoops_SurfaceToSwBlit: length mismatch: dstx=%d, dsty=%d, w=%d, h=%d, pixStride=%d, scanStride=%d",
707                         dstx, dsty, width, height, dstInfo.pixelStride, dstInfo.scanStride);
708                 return;
709             }
710 #endif //DEBUG
711 
712             // Create MTLBuffer (or use static)
713             MTLRasterFormatInfo rfi = RasterFormatInfos[dsttype];
714             const jboolean directCopy = rfi.permuteMap == NULL;
715 
716             id<MTLBuffer> mtlbuf;
717 #ifdef USE_STATIC_BUFFER
718             if (directCopy) {
719                 // NOTE: theoretically we can use newBufferWithBytesNoCopy, but pDst must be allocated with special API
720                 // mtlbuf = [mtlc.device
721                 //          newBufferWithBytesNoCopy:pDst
722                 //                            length:(NSUInteger) srcLength
723                 //                           options:MTLResourceCPUCacheModeDefaultCache
724                 //                       deallocator:nil];
725                 //
726                 // see https://developer.apple.com/documentation/metal/mtldevice/1433382-newbufferwithbytesnocopy?language=objc
727                 //
728                 // The storage allocation of the returned new MTLBuffer object is the same as the pointer input value.
729                 // The existing memory allocation must be covered by a single VM region, typically allocated with vm_allocate or mmap.
730                 // Memory allocated by malloc is specifically disallowed.
731             }
732 
733             static id<MTLBuffer> mtlIntermediateBuffer = nil; // need to reimplement with MTLBufferManager
734             if (mtlIntermediateBuffer == nil || mtlIntermediateBuffer.length < srcLength) {
735                 if (mtlIntermediateBuffer != nil) {
736                     [mtlIntermediateBuffer release];
737                 }
738                 mtlIntermediateBuffer = [mtlc.device newBufferWithLength:srcLength options:MTLResourceCPUCacheModeDefaultCache];
739             }
740             mtlbuf = mtlIntermediateBuffer;
741 #else // USE_STATIC_BUFFER
742             mtlbuf = [mtlc.device newBufferWithLength:width*height*4 options:MTLResourceStorageModeShared];
743 #endif // USE_STATIC_BUFFER
744 
745             // Read from surface into MTLBuffer
746             // NOTE: using of separate blitCommandBuffer can produce errors (draw into surface (with general cmd-buf)
747             // can be unfinished when reading raster from blit cmd-buf).
748             // Consider to use [mtlc.encoderManager createBlitEncoder] and [mtlc commitCommandBuffer:JNI_TRUE];
749             J2dTraceLn1(J2D_TRACE_VERBOSE, "MTLBlitLoops_SurfaceToSwBlit: source texture %p", srcOps->pTexture);
750 
751             id<MTLCommandBuffer> cb = [mtlc createBlitCommandBuffer];
752             id<MTLBlitCommandEncoder> blitEncoder = [cb blitCommandEncoder];
753             [blitEncoder synchronizeTexture:srcOps->pTexture slice:0 level:0];
754             [blitEncoder copyFromTexture:srcOps->pTexture
755                             sourceSlice:0
756                             sourceLevel:0
757                            sourceOrigin:MTLOriginMake(srcx, srcy, 0)
758                              sourceSize:MTLSizeMake(width, height, 1)
759                                toBuffer:mtlbuf
760                       destinationOffset:0 /*offset already taken in: pDst = PtrAddBytes(pDst, dstx * dstInfo.pixelStride)*/
761                  destinationBytesPerRow:width*4
762                destinationBytesPerImage:width * height*4];
763             [blitEncoder endEncoding];
764 
765             // Commit and wait for reading complete
766             [cb commit];
767             [cb waitUntilCompleted];
768 
769             // Perform conversion if necessary
770             if (directCopy) {
771                 memcpy(pDst, mtlbuf.contents, srcLength);
772             } else {
773                 J2dTraceLn6(J2D_TRACE_VERBOSE,"MTLBlitLoops_SurfaceToSwBlit: dsttype=%d, raster conversion will be performed, dest rfi: %d, %d, %d, %d, hasA=%d",
774                             dsttype, rfi.permuteMap[0], rfi.permuteMap[1], rfi.permuteMap[2], rfi.permuteMap[3], rfi.hasAlpha);
775 
776                 // perform raster conversion: mtlIntermediateBuffer(8888) -> pDst(rfi)
777                 // invoked only from rq-thread, so use static buffers
778                 // but it's better to use thread-local buffers (or special buffer manager)
779                 vImage_Buffer srcBuf;
780                 srcBuf.height = height;
781                 srcBuf.width = width;
782                 srcBuf.rowBytes = 4*width;
783                 srcBuf.data = mtlbuf.contents;
784 
785                 vImage_Buffer destBuf;
786                 destBuf.height = height;
787                 destBuf.width = width;
788                 destBuf.rowBytes = dstInfo.scanStride;
789                 destBuf.data = pDst;
790 
791                 vImagePermuteChannels_ARGB8888(&srcBuf, &destBuf, rfi.permuteMap, kvImageNoFlags);
792             }
793 #ifndef USE_STATIC_BUFFER
794             [mtlbuf release];
795 #endif // USE_STATIC_BUFFER
796         }
797         SurfaceData_InvokeRelease(env, dstOps, &dstInfo);
798     }
799     SurfaceData_InvokeUnlock(env, dstOps, &dstInfo);
800 }
801 
802 void
803 MTLBlitLoops_CopyArea(JNIEnv *env,
804                       MTLContext *mtlc, BMTLSDOps *dstOps,
805                       jint x, jint y, jint width, jint height,
806                       jint dx, jint dy)
807 {
808 #ifdef DEBUG
809     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, "MTLBlitLoops_CopyArea: bdst=%p [tex=%p] %dx%d | src (%d, %d), %dx%d -> dst (%d, %d)",
810             dstOps, dstOps->pTexture, ((id<MTLTexture>)dstOps->pTexture).width, ((id<MTLTexture>)dstOps->pTexture).height, x, y, width, height, dx, dy);
811 #endif //DEBUG
812     id <MTLBlitCommandEncoder> blitEncoder = [mtlc.encoderManager createBlitEncoder];
813     [blitEncoder
814             copyFromTexture:dstOps->pTexture
815             sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(x, y, 0) sourceSize:MTLSizeMake(width, height, 1)
816             toTexture:dstOps->pTexture destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(x + dx, y + dy, 0)];
817     [blitEncoder endEncoding];
818 
819     // TODO:
820     //  1. check rect bounds
821     //  2. support CopyArea with extra-alpha (and with custom Composite if necessary)
822 }
823 
824 #endif /* !HEADLESS */