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