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