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 */