1 /* 2 * Copyright (c) 2019, 2021, 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 package sun.java2d.metal; 27 28 import sun.awt.SunHints; 29 import sun.awt.image.PixelConverter; 30 import sun.java2d.SunGraphics2D; 31 import sun.java2d.SurfaceData; 32 import sun.java2d.SurfaceDataProxy; 33 import sun.java2d.loops.CompositeType; 34 import sun.java2d.loops.GraphicsPrimitive; 35 import sun.java2d.loops.MaskFill; 36 import sun.java2d.loops.SurfaceType; 37 import sun.java2d.pipe.ParallelogramPipe; 38 import sun.java2d.pipe.PixelToParallelogramConverter; 39 import sun.java2d.pipe.RenderBuffer; 40 import sun.java2d.pipe.TextPipe; 41 import sun.java2d.pipe.hw.AccelSurface; 42 43 import java.awt.AlphaComposite; 44 import java.awt.Composite; 45 import java.awt.GraphicsConfiguration; 46 import java.awt.GraphicsEnvironment; 47 import java.awt.Image; 48 import java.awt.Rectangle; 49 import java.awt.Transparency; 50 51 import java.awt.image.ColorModel; 52 import java.awt.image.Raster; 53 54 import static sun.java2d.pipe.BufferedOpCodes.DISPOSE_SURFACE; 55 import static sun.java2d.pipe.BufferedOpCodes.FLUSH_SURFACE; 56 import static sun.java2d.pipe.hw.ContextCapabilities.CAPS_MULTITEXTURE; 57 import static sun.java2d.pipe.hw.ContextCapabilities.CAPS_PS30; 58 59 60 public abstract class MTLSurfaceData extends SurfaceData 61 implements AccelSurface { 62 63 /** 64 * Pixel formats 65 */ 66 public static final int PF_INT_ARGB = 0; 67 public static final int PF_INT_ARGB_PRE = 1; 68 public static final int PF_INT_RGB = 2; 69 public static final int PF_INT_RGBX = 3; 70 public static final int PF_INT_BGR = 4; 71 public static final int PF_INT_BGRX = 5; 72 public static final int PF_USHORT_565_RGB = 6; 73 public static final int PF_USHORT_555_RGB = 7; 74 public static final int PF_USHORT_555_RGBX = 8; 75 public static final int PF_BYTE_GRAY = 9; 76 public static final int PF_USHORT_GRAY = 10; 77 public static final int PF_3BYTE_BGR = 11; 78 /** 79 * SurfaceTypes 80 */ 81 82 private static final String DESC_MTL_SURFACE = "MTL Surface"; 83 private static final String DESC_MTL_SURFACE_RTT = 84 "MTL Surface (render-to-texture)"; 85 private static final String DESC_MTL_TEXTURE = "MTL Texture"; 86 87 88 static final SurfaceType MTLSurface = 89 SurfaceType.Any.deriveSubType(DESC_MTL_SURFACE, 90 PixelConverter.ArgbPre.instance); 91 static final SurfaceType MTLSurfaceRTT = 92 MTLSurface.deriveSubType(DESC_MTL_SURFACE_RTT); 93 static final SurfaceType MTLTexture = 94 SurfaceType.Any.deriveSubType(DESC_MTL_TEXTURE); 95 96 protected static MTLRenderer mtlRenderPipe; 97 protected static PixelToParallelogramConverter mtlTxRenderPipe; 98 protected static ParallelogramPipe mtlAAPgramPipe; 99 protected static MTLTextRenderer mtlTextPipe; 100 protected static MTLDrawImage mtlImagePipe; 101 102 static { 103 if (!GraphicsEnvironment.isHeadless()) { 104 MTLRenderQueue rq = MTLRenderQueue.getInstance(); 105 mtlImagePipe = new MTLDrawImage(); 106 mtlTextPipe = new MTLTextRenderer(rq); 107 mtlRenderPipe = new MTLRenderer(rq); 108 if (GraphicsPrimitive.tracingEnabled()) { 109 mtlTextPipe = mtlTextPipe.traceWrap(); 110 //The wrapped mtlRenderPipe will wrap the AA pipe as well... 111 //mtlAAPgramPipe = mtlRenderPipe.traceWrap(); 112 } 113 mtlAAPgramPipe = mtlRenderPipe.getAAParallelogramPipe(); 114 mtlTxRenderPipe = 115 new PixelToParallelogramConverter(mtlRenderPipe, 116 mtlRenderPipe, 117 1.0, 0.25, true); 118 MTLBlitLoops.register()119 MTLBlitLoops.register(); MTLMaskFill.register()120 MTLMaskFill.register(); MTLMaskBlit.register()121 MTLMaskBlit.register(); 122 } 123 } 124 125 protected final int scale; 126 protected final int width; 127 protected final int height; 128 protected int type; 129 private MTLGraphicsConfig graphicsConfig; 130 // these fields are set from the native code when the surface is 131 // initialized 132 private int nativeWidth; 133 private int nativeHeight; 134 135 /** 136 * Returns the appropriate SurfaceType corresponding to the given Metal 137 * surface type constant (e.g. TEXTURE -> MTLTexture). 138 */ getCustomSurfaceType(int mtlType)139 private static SurfaceType getCustomSurfaceType(int mtlType) { 140 switch (mtlType) { 141 case TEXTURE: 142 return MTLTexture; 143 case RT_TEXTURE: 144 return MTLSurfaceRTT; 145 default: 146 return MTLSurface; 147 } 148 } 149 initOps(MTLGraphicsConfig gc, long pConfigInfo, long pPeerData, long layerPtr, int xoff, int yoff, boolean isOpaque)150 private native void initOps(MTLGraphicsConfig gc, long pConfigInfo, long pPeerData, long layerPtr, 151 int xoff, int yoff, boolean isOpaque); 152 MTLSurfaceData(MTLLayer layer, MTLGraphicsConfig gc, ColorModel cm, int type, int width, int height)153 private MTLSurfaceData(MTLLayer layer, MTLGraphicsConfig gc, 154 ColorModel cm, int type, int width, int height) 155 { 156 super(getCustomSurfaceType(type), cm); 157 this.graphicsConfig = gc; 158 this.type = type; 159 setBlitProxyKey(gc.getProxyKey()); 160 161 // TEXTURE shouldn't be scaled, it is used for managed BufferedImages. 162 scale = type == TEXTURE ? 1 : gc.getDevice().getScaleFactor(); 163 this.width = width * scale; 164 this.height = height * scale; 165 166 long pConfigInfo = gc.getNativeConfigInfo(); 167 long layerPtr = 0L; 168 boolean isOpaque = true; 169 if (layer != null) { 170 layerPtr = layer.getPointer(); 171 isOpaque = layer.isOpaque(); 172 } 173 initOps(gc, pConfigInfo, 0, layerPtr, 0, 0, isOpaque); 174 } 175 176 @Override getDeviceConfiguration()177 public GraphicsConfiguration getDeviceConfiguration() { 178 return graphicsConfig; 179 } 180 181 /** 182 * Creates a SurfaceData object representing the intermediate buffer 183 * between the Java2D flusher thread and the AppKit thread. 184 */ createData(MTLLayer layer)185 public static MTLLayerSurfaceData createData(MTLLayer layer) { 186 MTLGraphicsConfig gc = (MTLGraphicsConfig)layer.getGraphicsConfiguration(); 187 Rectangle r = layer.getBounds(); 188 return new MTLLayerSurfaceData(layer, gc, r.width, r.height); 189 } 190 191 /** 192 * Creates a SurfaceData object representing an off-screen buffer 193 */ createData(MTLGraphicsConfig gc, int width, int height, ColorModel cm, Image image, int type)194 public static MTLOffScreenSurfaceData createData(MTLGraphicsConfig gc, 195 int width, int height, 196 ColorModel cm, Image image, 197 int type) { 198 return new MTLOffScreenSurfaceData(gc, width, height, image, cm, 199 type); 200 } 201 202 @Override getDefaultScaleX()203 public double getDefaultScaleX() { 204 return scale; 205 } 206 207 @Override getDefaultScaleY()208 public double getDefaultScaleY() { 209 return scale; 210 } 211 212 @Override getBounds()213 public Rectangle getBounds() { 214 return new Rectangle(width, height); 215 } 216 clearWindow()217 protected native void clearWindow(); 218 initTexture(long pData, boolean isOpaque, int width, int height)219 protected native boolean initTexture(long pData, boolean isOpaque, int width, int height); 220 initRTexture(long pData, boolean isOpaque, int width, int height)221 protected native boolean initRTexture(long pData, boolean isOpaque, int width, int height); 222 initFlipBackbuffer(long pData)223 protected native boolean initFlipBackbuffer(long pData); 224 225 @Override makeProxyFor(SurfaceData srcData)226 public SurfaceDataProxy makeProxyFor(SurfaceData srcData) { 227 return MTLSurfaceDataProxy.createProxy(srcData, graphicsConfig); 228 } 229 230 /** 231 * Note: This should only be called from the QFT under the AWT lock. 232 * This method is kept separate from the initSurface() method below just 233 * to keep the code a bit cleaner. 234 */ initSurfaceNow(int width, int height)235 private void initSurfaceNow(int width, int height) { 236 boolean isOpaque = (getTransparency() == Transparency.OPAQUE); 237 boolean success = false; 238 239 switch (type) { 240 case TEXTURE: 241 success = initTexture(getNativeOps(), isOpaque, width, height); 242 break; 243 244 case RT_TEXTURE: 245 success = initRTexture(getNativeOps(), isOpaque, width, height); 246 break; 247 248 case FLIP_BACKBUFFER: 249 success = initFlipBackbuffer(getNativeOps()); 250 break; 251 252 default: 253 break; 254 } 255 256 if (!success) { 257 throw new OutOfMemoryError("can't create offscreen surface"); 258 } 259 } 260 261 /** 262 * Initializes the appropriate Metal offscreen surface based on the value 263 * of the type parameter. If the surface creation fails for any reason, 264 * an OutOfMemoryError will be thrown. 265 */ initSurface(final int width, final int height)266 protected void initSurface(final int width, final int height) { 267 MTLRenderQueue rq = MTLRenderQueue.getInstance(); 268 rq.lock(); 269 try { 270 switch (type) { 271 case TEXTURE: 272 case RT_TEXTURE: 273 // need to make sure the context is current before 274 // creating the texture 275 MTLContext.setScratchSurface(graphicsConfig); 276 break; 277 default: 278 break; 279 } 280 rq.flushAndInvokeNow(new Runnable() { 281 public void run() { 282 initSurfaceNow(width, height); 283 } 284 }); 285 } finally { 286 rq.unlock(); 287 } 288 } 289 290 /** 291 * Returns the MTLContext for the GraphicsConfig associated with this 292 * surface. 293 */ getContext()294 public final MTLContext getContext() { 295 return graphicsConfig.getContext(); 296 } 297 298 /** 299 * Returns the MTLGraphicsConfig associated with this surface. 300 */ getMTLGraphicsConfig()301 final MTLGraphicsConfig getMTLGraphicsConfig() { 302 return graphicsConfig; 303 } 304 305 /** 306 * Returns one of the surface type constants defined above. 307 */ getType()308 public final int getType() { 309 return type; 310 } 311 312 /** 313 * For now, we can only render LCD text if: 314 * - the fragment shader extension is available, and 315 * - the source color is opaque, and 316 * - blending is SrcOverNoEa or disabled 317 * - and the destination is opaque 318 * 319 * Eventually, we could enhance the native MTL text rendering code 320 * and remove the above restrictions, but that would require significantly 321 * more code just to support a few uncommon cases. 322 */ canRenderLCDText(SunGraphics2D sg2d)323 public boolean canRenderLCDText(SunGraphics2D sg2d) { 324 return 325 sg2d.surfaceData.getTransparency() == Transparency.OPAQUE && 326 sg2d.paintState <= SunGraphics2D.PAINT_OPAQUECOLOR && 327 (sg2d.compositeState <= SunGraphics2D.COMP_ISCOPY || 328 (sg2d.compositeState <= SunGraphics2D.COMP_ALPHA && canHandleComposite(sg2d.composite))); 329 } 330 canHandleComposite(Composite c)331 private boolean canHandleComposite(Composite c) { 332 if (c instanceof AlphaComposite) { 333 AlphaComposite ac = (AlphaComposite)c; 334 335 return ac.getRule() == AlphaComposite.SRC_OVER && ac.getAlpha() >= 1f; 336 } 337 return false; 338 } 339 validatePipe(SunGraphics2D sg2d)340 public void validatePipe(SunGraphics2D sg2d) { 341 TextPipe textpipe; 342 boolean validated = false; 343 344 // MTLTextRenderer handles both AA and non-AA text, but 345 // only works with the following modes: 346 // (Note: For LCD text we only enter this code path if 347 // canRenderLCDText() has already validated that the mode is 348 // CompositeType.SrcNoEa (opaque color), which will be subsumed 349 // by the CompositeType.SrcNoEa (any color) test below.) 350 351 if (/* CompositeType.SrcNoEa (any color) */ 352 (sg2d.compositeState <= SunGraphics2D.COMP_ISCOPY && 353 sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) || 354 355 /* CompositeType.SrcOver (any color) */ 356 (sg2d.compositeState == SunGraphics2D.COMP_ALPHA && 357 sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR && 358 (((AlphaComposite)sg2d.composite).getRule() == 359 AlphaComposite.SRC_OVER)) || 360 361 /* CompositeType.Xor (any color) */ 362 (sg2d.compositeState == SunGraphics2D.COMP_XOR && 363 sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR)) 364 { 365 textpipe = mtlTextPipe; 366 } else { 367 // do this to initialize textpipe correctly; we will attempt 368 // to override the non-text pipes below 369 super.validatePipe(sg2d); 370 textpipe = sg2d.textpipe; 371 validated = true; 372 } 373 374 PixelToParallelogramConverter txPipe = null; 375 MTLRenderer nonTxPipe = null; 376 377 if (sg2d.antialiasHint != SunHints.INTVAL_ANTIALIAS_ON) { 378 if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) { 379 if (sg2d.compositeState <= SunGraphics2D.COMP_XOR) { 380 txPipe = mtlTxRenderPipe; 381 nonTxPipe = mtlRenderPipe; 382 } 383 } else if (sg2d.compositeState <= SunGraphics2D.COMP_ALPHA) { 384 if (MTLPaints.isValid(sg2d)) { 385 txPipe = mtlTxRenderPipe; 386 nonTxPipe = mtlRenderPipe; 387 } 388 // custom paints handled by super.validatePipe() below 389 } 390 } else { 391 if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) { 392 if (graphicsConfig.isCapPresent(CAPS_PS30) && 393 (sg2d.imageComp == CompositeType.SrcOverNoEa || 394 sg2d.imageComp == CompositeType.SrcOver)) 395 { 396 if (!validated) { 397 super.validatePipe(sg2d); 398 validated = true; 399 } 400 PixelToParallelogramConverter aaConverter = 401 new PixelToParallelogramConverter(sg2d.shapepipe, 402 mtlAAPgramPipe, 403 1.0/8.0, 0.499, 404 false); 405 sg2d.drawpipe = aaConverter; 406 sg2d.fillpipe = aaConverter; 407 sg2d.shapepipe = aaConverter; 408 } else if (sg2d.compositeState == SunGraphics2D.COMP_XOR) { 409 // install the solid pipes when AA and XOR are both enabled 410 txPipe = mtlTxRenderPipe; 411 nonTxPipe = mtlRenderPipe; 412 } 413 } 414 // other cases handled by super.validatePipe() below 415 } 416 417 if (txPipe != null) { 418 if (sg2d.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) { 419 sg2d.drawpipe = txPipe; 420 sg2d.fillpipe = txPipe; 421 } else if (sg2d.strokeState != SunGraphics2D.STROKE_THIN) { 422 sg2d.drawpipe = txPipe; 423 sg2d.fillpipe = nonTxPipe; 424 } else { 425 sg2d.drawpipe = nonTxPipe; 426 sg2d.fillpipe = nonTxPipe; 427 } 428 // Note that we use the transforming pipe here because it 429 // will examine the shape and possibly perform an optimized 430 // operation if it can be simplified. The simplifications 431 // will be valid for all STROKE and TRANSFORM types. 432 sg2d.shapepipe = txPipe; 433 } else { 434 if (!validated) { 435 super.validatePipe(sg2d); 436 } 437 } 438 439 // install the text pipe based on our earlier decision 440 sg2d.textpipe = textpipe; 441 442 // always override the image pipe with the specialized MTL pipe 443 sg2d.imagepipe = mtlImagePipe; 444 } 445 446 @Override getMaskFill(SunGraphics2D sg2d)447 protected MaskFill getMaskFill(SunGraphics2D sg2d) { 448 if (sg2d.paintState > SunGraphics2D.PAINT_ALPHACOLOR) { 449 /* 450 * We can only accelerate non-Color MaskFill operations if 451 * all of the following conditions hold true: 452 * - there is an implementation for the given paintState 453 * - the current Paint can be accelerated for this destination 454 * - multitexturing is available (since we need to modulate 455 * the alpha mask texture with the paint texture) 456 * 457 * In all other cases, we return null, in which case the 458 * validation code will choose a more general software-based loop. 459 */ 460 if (!MTLPaints.isValid(sg2d) || 461 !graphicsConfig.isCapPresent(CAPS_MULTITEXTURE)) 462 { 463 return null; 464 } 465 } 466 return super.getMaskFill(sg2d); 467 } 468 flush()469 public void flush() { 470 invalidate(); 471 MTLRenderQueue rq = MTLRenderQueue.getInstance(); 472 rq.lock(); 473 try { 474 // make sure we have a current context before 475 // disposing the native resources (e.g. texture object) 476 MTLContext.setScratchSurface(graphicsConfig); 477 478 RenderBuffer buf = rq.getBuffer(); 479 rq.ensureCapacityAndAlignment(12, 4); 480 buf.putInt(FLUSH_SURFACE); 481 buf.putLong(getNativeOps()); 482 483 // this call is expected to complete synchronously, so flush now 484 rq.flushNow(); 485 } finally { 486 rq.unlock(); 487 } 488 } 489 isOnScreen()490 public boolean isOnScreen() { 491 return false; 492 } 493 getMTLTexturePointer(long pData)494 private native long getMTLTexturePointer(long pData); 495 496 /** 497 * Returns native resource of specified {@code resType} associated with 498 * this surface. 499 * 500 * Specifically, for {@code MTLSurfaceData} this method returns the 501 * the following: 502 * <pre> 503 * TEXTURE - texture id 504 * </pre> 505 * 506 * Note: the resource returned by this method is only valid on the rendering 507 * thread. 508 * 509 * @return native resource of specified type or 0L if 510 * such resource doesn't exist or can not be retrieved. 511 * @see AccelSurface#getNativeResource 512 */ getNativeResource(int resType)513 public long getNativeResource(int resType) { 514 if (resType == TEXTURE) { 515 return getMTLTexturePointer(getNativeOps()); 516 } 517 return 0L; 518 } 519 getRaster(int x, int y, int w, int h)520 public Raster getRaster(int x, int y, int w, int h) { 521 throw new InternalError("not implemented yet"); 522 } 523 524 @Override copyArea(SunGraphics2D sg2d, int x, int y, int w, int h, int dx, int dy)525 public boolean copyArea(SunGraphics2D sg2d, int x, int y, int w, int h, 526 int dx, int dy) { 527 if (sg2d.compositeState >= SunGraphics2D.COMP_XOR) { 528 return false; 529 } 530 mtlRenderPipe.copyArea(sg2d, x, y, w, h, dx, dy); 531 return true; 532 } 533 getNativeBounds()534 public Rectangle getNativeBounds() { 535 MTLRenderQueue rq = MTLRenderQueue.getInstance(); 536 rq.lock(); 537 try { 538 return new Rectangle(nativeWidth, nativeHeight); 539 } finally { 540 rq.unlock(); 541 } 542 } 543 544 /** 545 * A surface which implements an intermediate buffer between 546 * the Java2D flusher thread and the AppKit thread. 547 * 548 * This surface serves as a buffer attached to a MTLLayer and 549 * the layer redirects all painting to the buffer's graphics. 550 */ 551 public static class MTLLayerSurfaceData extends MTLSurfaceData { 552 553 private final MTLLayer layer; 554 MTLLayerSurfaceData(MTLLayer layer, MTLGraphicsConfig gc, int width, int height)555 private MTLLayerSurfaceData(MTLLayer layer, MTLGraphicsConfig gc, 556 int width, int height) { 557 super(layer, gc, gc.getColorModel(), RT_TEXTURE, width, height); 558 this.layer = layer; 559 initSurface(this.width, this.height); 560 } 561 562 @Override getReplacement()563 public SurfaceData getReplacement() { 564 return layer.getSurfaceData(); 565 } 566 567 @Override isOnScreen()568 public boolean isOnScreen() { 569 return true; 570 } 571 572 @Override getDestination()573 public Object getDestination() { 574 return layer.getDestination(); 575 } 576 577 @Override getTransparency()578 public int getTransparency() { 579 return layer.getTransparency(); 580 } 581 582 @Override invalidate()583 public void invalidate() { 584 super.invalidate(); 585 clearWindow(); 586 } 587 } 588 589 /** 590 * SurfaceData object representing an off-screen buffer 591 */ 592 public static class MTLOffScreenSurfaceData extends MTLSurfaceData { 593 private final Image offscreenImage; 594 MTLOffScreenSurfaceData(MTLGraphicsConfig gc, int width, int height, Image image, ColorModel cm, int type)595 public MTLOffScreenSurfaceData(MTLGraphicsConfig gc, int width, 596 int height, Image image, 597 ColorModel cm, int type) { 598 super(null, gc, cm, type, width, height); 599 offscreenImage = image; 600 initSurface(this.width, this.height); 601 } 602 603 @Override getReplacement()604 public SurfaceData getReplacement() { 605 return restoreContents(offscreenImage); 606 } 607 608 /** 609 * Returns destination Image associated with this SurfaceData. 610 */ 611 @Override getDestination()612 public Object getDestination() { 613 return offscreenImage; 614 } 615 } 616 617 618 /** 619 * Disposes the native resources associated with the given MTLSurfaceData 620 * (referenced by the pData parameter). This method is invoked from 621 * the native Dispose() method from the Disposer thread when the 622 * Java-level MTLSurfaceData object is about to go away. 623 */ dispose(long pData, MTLGraphicsConfig gc)624 public static void dispose(long pData, MTLGraphicsConfig gc) { 625 MTLRenderQueue rq = MTLRenderQueue.getInstance(); 626 rq.lock(); 627 try { 628 // make sure we have a current context before 629 // disposing the native resources (e.g. texture object) 630 MTLContext.setScratchSurface(gc); 631 RenderBuffer buf = rq.getBuffer(); 632 rq.ensureCapacityAndAlignment(12, 4); 633 buf.putInt(DISPOSE_SURFACE); 634 buf.putLong(pData); 635 636 // this call is expected to complete synchronously, so flush now 637 rq.flushNow(); 638 } finally { 639 rq.unlock(); 640 } 641 } 642 } 643