diff --git a/make/autoconf/spec.gmk.in b/make/autoconf/spec.gmk.in
index afa410c9f32..3681688105e 100644
--- a/make/autoconf/spec.gmk.in
+++ b/make/autoconf/spec.gmk.in
@@ -576,6 +576,8 @@ CXXFILT:=@CXXFILT@
 
 LIPO:=@LIPO@
 INSTALL_NAME_TOOL:=@INSTALL_NAME_TOOL@
+METAL := @METAL@
+METALLIB := @METALLIB@
 
 # Options to linker to specify a mapfile.
 # (Note absence of := assignment, because we do not want to evaluate the macro body here)
diff --git a/make/autoconf/toolchain.m4 b/make/autoconf/toolchain.m4
index 09a79636593..d9404d26eb4 100644
--- a/make/autoconf/toolchain.m4
+++ b/make/autoconf/toolchain.m4
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -723,6 +723,32 @@ AC_DEFUN_ONCE([TOOLCHAIN_DETECT_TOOLCHAIN_EXTRA],
     UTIL_LOOKUP_PROGS(LIPO, lipo)
     UTIL_REQUIRE_PROGS(OTOOL, otool)
     UTIL_REQUIRE_PROGS(INSTALL_NAME_TOOL, install_name_tool)
+
+    UTIL_LOOKUP_TOOLCHAIN_PROGS(METAL, metal)
+    if test "x$METAL" = x; then
+      AC_MSG_CHECKING([if metal can be run using xcrun])
+      METAL="xcrun -sdk macosx metal"
+      test_metal=`$METAL --version 2>&1`
+      if test $? -ne 0; then
+        AC_MSG_RESULT([no])
+        AC_MSG_ERROR([XCode tool 'metal' neither found in path nor with xcrun])
+      else
+        AC_MSG_RESULT([yes, will be using '$METAL'])
+      fi
+    fi
+
+    UTIL_LOOKUP_TOOLCHAIN_PROGS(METALLIB, metallib)
+    if test "x$METALLIB" = x; then
+      AC_MSG_CHECKING([if metallib can be run using xcrun])
+      METALLIB="xcrun -sdk macosx metallib"
+      test_metallib=`$METALLIB --version 2>&1`
+      if test $? -ne 0; then
+        AC_MSG_RESULT([no])
+        AC_MSG_ERROR([XCode tool 'metallib' neither found in path nor with xcrun])
+      else
+        AC_MSG_RESULT([yes, will be using '$METALLIB'])
+      fi
+    fi
   fi
 
   if test "x$TOOLCHAIN_TYPE" = xmicrosoft; then
diff --git a/make/modules/java.desktop/Lib.gmk b/make/modules/java.desktop/Lib.gmk
index bc0c349da6a..22b07289adf 100644
--- a/make/modules/java.desktop/Lib.gmk
+++ b/make/modules/java.desktop/Lib.gmk
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,7 @@
 #
 
 include LibCommon.gmk
+include Execute.gmk
 
 # Hook to include the corresponding custom file, if present.
 $(eval $(call IncludeCustomExtension, modules/java.desktop/Lib.gmk))
diff --git a/make/modules/java.desktop/lib/Awt2dLibraries.gmk b/make/modules/java.desktop/lib/Awt2dLibraries.gmk
index bc862c45bf4..e87bb3c6ae9 100644
--- a/make/modules/java.desktop/lib/Awt2dLibraries.gmk
+++ b/make/modules/java.desktop/lib/Awt2dLibraries.gmk
@@ -160,6 +160,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBAWT, \
     LIBS_macosx := -lmlib_image \
         -framework Cocoa \
         -framework OpenGL \
+        -framework Metal \
         -framework JavaRuntimeSupport \
         -framework ApplicationServices \
         -framework AudioToolbox, \
@@ -766,7 +767,8 @@ ifeq ($(ENABLE_HEADLESS_ONLY), false)
         -framework ApplicationServices \
         -framework Foundation \
         -framework Security \
-        -framework Cocoa
+        -framework Cocoa \
+        -framework Metal
   else ifeq ($(call isTargetOs, windows), true)
     LIBSPLASHSCREEN_LIBS += kernel32.lib user32.lib gdi32.lib delayimp.lib $(WIN_JAVA_LIB) jvm.lib
   else
@@ -827,6 +829,7 @@ ifeq ($(call isTargetOs, macosx), true)
       libawt_lwawt/awt \
       libawt_lwawt/font \
       libawt_lwawt/java2d/opengl \
+      libawt_lwawt/java2d/metal \
       include \
       common/awt/debug \
       common/java2d/opengl \
@@ -862,6 +865,7 @@ ifeq ($(call isTargetOs, macosx), true)
           -framework AudioToolbox \
           -framework Carbon \
           -framework Cocoa \
+          -framework Metal \
           -framework Security \
           -framework ExceptionHandling \
           -framework JavaRuntimeSupport \
@@ -884,6 +888,28 @@ endif
 ################################################################################
 
 ifeq ($(call isTargetOs, macosx), true)
+  SHADERS_SRC := $(TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal
+  SHADERS_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/native/java.desktop/libosxui
+  SHADERS_AIR := $(SHADERS_SUPPORT_DIR)/shaders.air
+  SHADERS_LIB := $(INSTALL_LIBRARIES_HERE)/shaders.metallib
+
+  $(eval $(call SetupExecute, metal_shaders, \
+      INFO := Running metal on $(notdir $(SHADERS_SRC)) (for libosxui.dylib), \
+      DEPS := $(SHADERS_SRC), \
+      OUTPUT_FILE := $(SHADERS_AIR), \
+      SUPPORT_DIR := $(SHADERS_SUPPORT_DIR), \
+      COMMAND := $(METAL) -c -std=osx-metal2.0 -o $(SHADERS_AIR) $(SHADERS_SRC), \
+  ))
+
+  $(eval $(call SetupExecute, metallib_shaders, \
+      INFO := Running metallib on $(notdir $(SHADERS_AIR)) (for libosxui.dylib), \
+      DEPS := $(SHADERS_AIR), \
+      OUTPUT_FILE := $(SHADERS_LIB), \
+      SUPPORT_DIR := $(SHADERS_SUPPORT_DIR), \
+      COMMAND := $(METALLIB) -o $(SHADERS_LIB) $(SHADERS_AIR), \
+  ))
+
+  TARGETS += $(SHADERS_LIB)
 
   $(eval $(call SetupJdkLibrary, BUILD_LIBOSXUI, \
       NAME := osxui, \
@@ -899,6 +925,7 @@ ifeq ($(call isTargetOs, macosx), true)
           -L$(INSTALL_LIBRARIES_HERE), \
       LIBS := -lawt -losxapp -lawt_lwawt \
           -framework Cocoa \
+          -framework Metal \
           -framework Carbon \
           -framework ApplicationServices \
           -framework JavaRuntimeSupport \
@@ -906,6 +933,7 @@ ifeq ($(call isTargetOs, macosx), true)
   ))
 
   TARGETS += $(BUILD_LIBOSXUI)
+  $(BUILD_LIBOSXUI): $(SHADERS_LIB)
 
   $(BUILD_LIBOSXUI): $(BUILD_LIBAWT)
 
diff --git a/src/java.desktop/macosx/classes/sun/awt/CGraphicsConfig.java b/src/java.desktop/macosx/classes/sun/awt/CGraphicsConfig.java
index c63d683629a..9e8c043184f 100644
--- a/src/java.desktop/macosx/classes/sun/awt/CGraphicsConfig.java
+++ b/src/java.desktop/macosx/classes/sun/awt/CGraphicsConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -32,8 +32,8 @@ import java.awt.geom.AffineTransform;
 import java.awt.image.ColorModel;
 
 import sun.java2d.SurfaceData;
-import sun.java2d.opengl.CGLLayer;
 import sun.lwawt.LWGraphicsConfig;
+import sun.lwawt.macosx.CFRetainedResource;
 
 public abstract class CGraphicsConfig extends GraphicsConfiguration
         implements LWGraphicsConfig {
@@ -80,7 +80,7 @@ public abstract class CGraphicsConfig extends GraphicsConfiguration
      * Creates a new SurfaceData that will be associated with the given
      * CGLLayer.
      */
-    public abstract SurfaceData createSurfaceData(CGLLayer layer);
+    public abstract SurfaceData createSurfaceData(CFRetainedResource layer);
 
     @Override
     public final boolean isTranslucencyCapable() {
diff --git a/src/java.desktop/macosx/classes/sun/awt/CGraphicsDevice.java b/src/java.desktop/macosx/classes/sun/awt/CGraphicsDevice.java
index 4417e327d74..d28c2aac111 100644
--- a/src/java.desktop/macosx/classes/sun/awt/CGraphicsDevice.java
+++ b/src/java.desktop/macosx/classes/sun/awt/CGraphicsDevice.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -37,6 +37,8 @@ import java.awt.peer.WindowPeer;
 import java.util.Objects;
 
 import sun.java2d.SunGraphicsEnvironment;
+import sun.java2d.MacOSFlags;
+import sun.java2d.metal.MTLGraphicsConfig;
 import sun.java2d.opengl.CGLGraphicsConfig;
 
 import static java.awt.peer.ComponentPeer.SET_BOUNDS;
@@ -54,7 +56,10 @@ public final class CGraphicsDevice extends GraphicsDevice
     private volatile Rectangle bounds;
     private volatile int scale;
 
-    private final GraphicsConfiguration config;
+    private GraphicsConfiguration config;
+    private static boolean metalPipelineEnabled = false;
+    private static boolean oglPipelineEnabled = false;
+
 
     private static AWTPermission fullScreenExclusivePermission;
 
@@ -63,7 +68,64 @@ public final class CGraphicsDevice extends GraphicsDevice
 
     public CGraphicsDevice(final int displayID) {
         this.displayID = displayID;
-        config = CGLGraphicsConfig.getConfig(this);
+
+        if (MacOSFlags.isMetalEnabled()) {
+            // Try to create MTLGraphicsConfig, if it fails,
+            // try to create CGLGraphicsConfig as a fallback
+            this.config = MTLGraphicsConfig.getConfig(this, displayID);
+
+            if (this.config != null) {
+                metalPipelineEnabled = true;
+            } else {
+                // Try falling back to OpenGL pipeline
+                if (MacOSFlags.isMetalVerbose()) {
+                    System.out.println("Metal rendering pipeline" +
+                        " initialization failed,using OpenGL" +
+                        " rendering pipeline");
+                }
+
+                this.config = CGLGraphicsConfig.getConfig(this);
+
+                if (this.config != null) {
+                    oglPipelineEnabled = true;
+                }
+            }
+        } else {
+            // Try to create CGLGraphicsConfig, if it fails,
+            // try to create MTLGraphicsConfig as a fallback
+            this.config = CGLGraphicsConfig.getConfig(this);
+
+            if (this.config != null) {
+                oglPipelineEnabled = true;
+            } else {
+                // Try falling back to Metal pipeline
+                if (MacOSFlags.isOGLVerbose()) {
+                    System.out.println("OpenGL rendering pipeline" +
+                        " initialization failed,using Metal" +
+                        " rendering pipeline");
+                }
+
+                this.config = MTLGraphicsConfig.getConfig(this, displayID);
+
+                if (this.config != null) {
+                    metalPipelineEnabled = true;
+                }
+            }
+        }
+
+        if (!metalPipelineEnabled && !oglPipelineEnabled) {
+            // This indicates fallback to other rendering pipeline also failed.
+            // Should never reach here
+            throw new InternalError("Error - unable to initialize any" +
+                " rendering pipeline.");
+        }
+
+        if (metalPipelineEnabled && MacOSFlags.isMetalVerbose()) {
+            System.out.println("Metal pipeline enabled on screen " + displayID);
+        } else if (oglPipelineEnabled && MacOSFlags.isOGLVerbose()) {
+            System.out.println("OpenGL pipeline enabled on screen " + displayID);
+        }
+
         // initializes default device state, might be redundant step since we
         // call "displayChanged()" later anyway, but we do not want to leave the
         // device in an inconsistent state after construction
@@ -265,6 +327,10 @@ public final class CGraphicsDevice extends GraphicsDevice
         return nativeGetDisplayModes(displayID);
     }
 
+    public static boolean usingMetalPipeline() {
+        return metalPipelineEnabled;
+    }
+
     private void initScaleFactor() {
         if (SunGraphicsEnvironment.isUIScaleEnabled()) {
             double debugScale = SunGraphicsEnvironment.getDebugScale();
diff --git a/src/java.desktop/macosx/classes/sun/java2d/MacOSFlags.java b/src/java.desktop/macosx/classes/sun/java2d/MacOSFlags.java
new file mode 100644
index 00000000000..097610300f2
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/MacOSFlags.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d;
+
+import java.security.PrivilegedAction;
+import sun.java2d.metal.MTLGraphicsConfig;
+import sun.java2d.opengl.CGLGraphicsConfig;
+
+
+public class MacOSFlags {
+
+    /**
+     * Description of command-line flags.  All flags with [true|false]
+     * values
+     *      metalEnabled: usage: "-Dsun.java2d.metal=[true|false]"
+     */
+
+    private static boolean oglEnabled;
+    private static boolean oglVerbose;
+    private static boolean metalEnabled;
+    private static boolean metalVerbose;
+
+    private enum PropertyState {ENABLED, DISABLED, UNSPECIFIED};
+
+    static {
+        initJavaFlags();
+    }
+
+    private static PropertyState getBooleanProp(String p, PropertyState defaultVal) {
+        String propString = System.getProperty(p);
+        PropertyState returnVal = defaultVal;
+        if (propString != null) {
+            if (propString.equals("true") ||
+                propString.equals("t") ||
+                propString.equals("True") ||
+                propString.equals("T") ||
+                propString.equals("")) // having the prop name alone
+            {                          // is equivalent to true
+                returnVal = PropertyState.ENABLED;
+            } else if (propString.equals("false") ||
+                       propString.equals("f") ||
+                       propString.equals("False") ||
+                       propString.equals("F"))
+            {
+                returnVal = PropertyState.DISABLED;
+            }
+        }
+        return returnVal;
+    }
+
+    private static boolean isBooleanPropTrueVerbose(String p) {
+        String propString = System.getProperty(p);
+        if (propString != null) {
+            if (propString.equals("True") ||
+                propString.equals("T"))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static void initJavaFlags() {
+        java.security.AccessController.doPrivileged(
+                (PrivilegedAction<Object>) () -> {
+                    PropertyState oglState = getBooleanProp("sun.java2d.opengl", PropertyState.UNSPECIFIED);
+                    PropertyState metalState = getBooleanProp("sun.java2d.metal", PropertyState.UNSPECIFIED);
+
+                    // Handle invalid combinations to use the default rendering pipeline
+                    // Current default rendering pipeline is OpenGL
+                    // (The default can be changed to Metal in future just by toggling two states in this if condition block)
+                    if ((oglState == PropertyState.UNSPECIFIED && metalState == PropertyState.UNSPECIFIED) ||
+                        (oglState == PropertyState.DISABLED && metalState == PropertyState.DISABLED) ||
+                        (oglState == PropertyState.ENABLED && metalState == PropertyState.ENABLED)) {
+                        oglState = PropertyState.ENABLED; // Enable default pipeline
+                        metalState = PropertyState.DISABLED; // Disable non-default pipeline
+                    }
+
+                    if (metalState == PropertyState.UNSPECIFIED) {
+                        if (oglState == PropertyState.DISABLED) {
+                            oglEnabled = false;
+                            metalEnabled = true;
+                        } else {
+                            oglEnabled = true;
+                            metalEnabled = false;
+                        }
+                    } else if (metalState == PropertyState.ENABLED) {
+                        oglEnabled = false;
+                        metalEnabled = true;
+                    } else if (metalState == PropertyState.DISABLED) {
+                        oglEnabled = true;
+                        metalEnabled = false;
+                    }
+
+                    oglVerbose = isBooleanPropTrueVerbose("sun.java2d.opengl");
+                    metalVerbose = isBooleanPropTrueVerbose("sun.java2d.metal");
+
+                    if (oglEnabled && !metalEnabled) {
+                        // Check whether OGL is available
+                        if (!CGLGraphicsConfig.isCGLAvailable()) {
+                            if (oglVerbose) {
+                                System.out.println("Could not enable OpenGL pipeline (CGL not available)");
+                            }
+                            oglEnabled = false;
+                            metalEnabled = MTLGraphicsConfig.isMetalAvailable();
+                        }
+                    } else if (metalEnabled && !oglEnabled) {
+                        // Check whether Metal framework is available
+                        if (!MTLGraphicsConfig.isMetalAvailable()) {
+                            if (metalVerbose) {
+                                System.out.println("Could not enable Metal pipeline (Metal framework not available)");
+                            }
+                            metalEnabled = false;
+                            oglEnabled = CGLGraphicsConfig.isCGLAvailable();
+                        }
+                    }
+
+                    // At this point one of the rendering pipeline must be enabled.
+                    if (!metalEnabled && !oglEnabled) {
+                        throw new InternalError("Error - unable to initialize any rendering pipeline.");
+                    }
+
+                    return null;
+                });
+    }
+
+    public static boolean isMetalEnabled() {
+        return metalEnabled;
+    }
+
+    public static boolean isMetalVerbose() {
+        return metalVerbose;
+    }
+
+    public static boolean isOGLEnabled() {
+        return oglEnabled;
+    }
+
+    public static boolean isOGLVerbose() {
+        return oglVerbose;
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/MacosxSurfaceManagerFactory.java b/src/java.desktop/macosx/classes/sun/java2d/MacosxSurfaceManagerFactory.java
index a18b1fb0ecf..93fac3fb22a 100644
--- a/src/java.desktop/macosx/classes/sun/java2d/MacosxSurfaceManagerFactory.java
+++ b/src/java.desktop/macosx/classes/sun/java2d/MacosxSurfaceManagerFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,8 @@ package sun.java2d;
 
 import sun.awt.image.SunVolatileImage;
 import sun.awt.image.VolatileSurfaceManager;
+import sun.awt.CGraphicsDevice;
+import sun.java2d.metal.MTLVolatileSurfaceManager;
 import sun.java2d.opengl.CGLVolatileSurfaceManager;
 
 /**
@@ -42,13 +44,14 @@ public class MacosxSurfaceManagerFactory extends SurfaceManagerFactory {
      * as a way for the caller to pass pipeline-specific context data to
      * the VolatileSurfaceManager (such as a backbuffer handle, for example).
      *
-     * For Mac OS X, this method returns either an CGL-specific
+     * For Mac OS X, this method returns either an CGL/MTL-specific
      * VolatileSurfaceManager based on the GraphicsConfiguration
      * under which the SunVolatileImage was created.
      */
     public VolatileSurfaceManager createVolatileManager(SunVolatileImage vImg,
                                                         Object context)
     {
-        return new CGLVolatileSurfaceManager(vImg, context);
+        return CGraphicsDevice.usingMetalPipeline() ? new MTLVolatileSurfaceManager(vImg, context) :
+                new CGLVolatileSurfaceManager(vImg, context);
     }
 }
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLBlitLoops.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLBlitLoops.java
new file mode 100644
index 00000000000..6be497ee9ce
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLBlitLoops.java
@@ -0,0 +1,905 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.SurfaceData;
+import sun.java2d.loops.Blit;
+import sun.java2d.loops.CompositeType;
+import sun.java2d.loops.GraphicsPrimitive;
+import sun.java2d.loops.GraphicsPrimitiveMgr;
+import sun.java2d.loops.ScaledBlit;
+import sun.java2d.loops.SurfaceType;
+import sun.java2d.loops.TransformBlit;
+import sun.java2d.pipe.Region;
+import sun.java2d.pipe.RenderBuffer;
+import sun.java2d.pipe.RenderQueue;
+import sun.java2d.pipe.hw.AccelSurface;
+
+import java.awt.AlphaComposite;
+import java.awt.Composite;
+import java.awt.Transparency;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.lang.annotation.Native;
+import java.lang.ref.WeakReference;
+
+import static sun.java2d.pipe.BufferedOpCodes.BLIT;
+import static sun.java2d.pipe.BufferedOpCodes.SURFACE_TO_SW_BLIT;
+
+final class MTLBlitLoops {
+
+    static void register() {
+        Blit blitIntArgbPreToSurface =
+                new MTLSwToSurfaceBlit(SurfaceType.IntArgbPre,
+                        MTLSurfaceData.PF_INT_ARGB_PRE);
+        Blit blitIntArgbPreToTexture =
+                new MTLSwToTextureBlit(SurfaceType.IntArgbPre,
+                        MTLSurfaceData.PF_INT_ARGB_PRE);
+        TransformBlit transformBlitIntArgbPreToSurface =
+                new MTLSwToSurfaceTransform(SurfaceType.IntArgbPre,
+                        MTLSurfaceData.PF_INT_ARGB_PRE);
+        MTLSurfaceToSwBlit blitSurfaceToIntArgbPre =
+                new MTLSurfaceToSwBlit(SurfaceType.IntArgbPre,
+                        MTLSurfaceData.PF_INT_ARGB_PRE);
+
+        GraphicsPrimitive[] primitives = {
+                // surface->surface ops
+                new MTLSurfaceToSurfaceBlit(),
+                new MTLSurfaceToSurfaceScale(),
+                new MTLSurfaceToSurfaceTransform(),
+
+                // render-to-texture surface->surface ops
+                new MTLRTTSurfaceToSurfaceBlit(),
+                new MTLRTTSurfaceToSurfaceScale(),
+                new MTLRTTSurfaceToSurfaceTransform(),
+
+                // surface->sw ops
+                new MTLSurfaceToSwBlit(SurfaceType.IntArgb,
+                        MTLSurfaceData.PF_INT_ARGB),
+                blitSurfaceToIntArgbPre,
+
+                // sw->surface ops
+                blitIntArgbPreToSurface,
+                new MTLSwToSurfaceBlit(SurfaceType.IntRgb,
+                        MTLSurfaceData.PF_INT_RGB),
+                new MTLSwToSurfaceBlit(SurfaceType.IntRgbx,
+                        MTLSurfaceData.PF_INT_RGBX),
+                new MTLSwToSurfaceBlit(SurfaceType.IntBgr,
+                        MTLSurfaceData.PF_INT_BGR),
+                new MTLSwToSurfaceBlit(SurfaceType.IntBgrx,
+                        MTLSurfaceData.PF_INT_BGRX),
+                new MTLGeneralBlit(MTLSurfaceData.MTLSurface,
+                        CompositeType.AnyAlpha,
+                        blitIntArgbPreToSurface),
+
+                new MTLAnyCompositeBlit(MTLSurfaceData.MTLSurface,
+                        blitSurfaceToIntArgbPre,
+                        blitSurfaceToIntArgbPre,
+                        blitIntArgbPreToSurface),
+                new MTLAnyCompositeBlit(SurfaceType.Any,
+                        null,
+                        blitSurfaceToIntArgbPre,
+                        blitIntArgbPreToSurface),
+
+                new MTLSwToSurfaceScale(SurfaceType.IntRgb,
+                        MTLSurfaceData.PF_INT_RGB),
+                new MTLSwToSurfaceScale(SurfaceType.IntRgbx,
+                        MTLSurfaceData.PF_INT_RGBX),
+                new MTLSwToSurfaceScale(SurfaceType.IntBgr,
+                        MTLSurfaceData.PF_INT_BGR),
+                new MTLSwToSurfaceScale(SurfaceType.IntBgrx,
+                        MTLSurfaceData.PF_INT_BGRX),
+                new MTLSwToSurfaceScale(SurfaceType.IntArgbPre,
+                        MTLSurfaceData.PF_INT_ARGB_PRE),
+
+                new MTLSwToSurfaceTransform(SurfaceType.IntRgb,
+                        MTLSurfaceData.PF_INT_RGB),
+                new MTLSwToSurfaceTransform(SurfaceType.IntRgbx,
+                        MTLSurfaceData.PF_INT_RGBX),
+                new MTLSwToSurfaceTransform(SurfaceType.IntBgr,
+                        MTLSurfaceData.PF_INT_BGR),
+                new MTLSwToSurfaceTransform(SurfaceType.IntBgrx,
+                        MTLSurfaceData.PF_INT_BGRX),
+                transformBlitIntArgbPreToSurface,
+
+                new MTLGeneralTransformedBlit(transformBlitIntArgbPreToSurface),
+
+                // texture->surface ops
+                new MTLTextureToSurfaceBlit(),
+                new MTLTextureToSurfaceScale(),
+                new MTLTextureToSurfaceTransform(),
+
+                // sw->texture ops
+                blitIntArgbPreToTexture,
+                new MTLSwToTextureBlit(SurfaceType.IntRgb,
+                        MTLSurfaceData.PF_INT_RGB),
+                new MTLSwToTextureBlit(SurfaceType.IntRgbx,
+                        MTLSurfaceData.PF_INT_RGBX),
+                new MTLSwToTextureBlit(SurfaceType.IntBgr,
+                        MTLSurfaceData.PF_INT_BGR),
+                new MTLSwToTextureBlit(SurfaceType.IntBgrx,
+                        MTLSurfaceData.PF_INT_BGRX),
+                new MTLGeneralBlit(MTLSurfaceData.MTLTexture,
+                        CompositeType.SrcNoEa,
+                        blitIntArgbPreToTexture),
+        };
+        GraphicsPrimitiveMgr.register(primitives);
+    }
+
+    /**
+     * The following offsets are used to pack the parameters in
+     * createPackedParams().  (They are also used at the native level when
+     * unpacking the params.)
+     */
+    @Native private static final int OFFSET_SRCTYPE = 16;
+    @Native private static final int OFFSET_HINT    =  8;
+    @Native private static final int OFFSET_TEXTURE =  3;
+    @Native private static final int OFFSET_RTT     =  2;
+    @Native private static final int OFFSET_XFORM   =  1;
+    @Native private static final int OFFSET_ISOBLIT =  0;
+
+    /**
+     * Packs the given parameters into a single int value in order to save
+     * space on the rendering queue.
+     */
+    private static int createPackedParams(boolean isoblit, boolean texture,
+                                          boolean rtt, boolean xform,
+                                          int hint, int srctype)
+    {
+        return
+                ((srctype           << OFFSET_SRCTYPE) |
+                        (hint              << OFFSET_HINT   ) |
+                        ((texture ? 1 : 0) << OFFSET_TEXTURE) |
+                        ((rtt     ? 1 : 0) << OFFSET_RTT    ) |
+                        ((xform   ? 1 : 0) << OFFSET_XFORM  ) |
+                        ((isoblit ? 1 : 0) << OFFSET_ISOBLIT));
+    }
+
+    /**
+     * Enqueues a BLIT operation with the given parameters.  Note that the
+     * RenderQueue lock must be held before calling this method.
+     */
+    private static void enqueueBlit(RenderQueue rq,
+                                    SurfaceData src, SurfaceData dst,
+                                    int packedParams,
+                                    int sx1, int sy1,
+                                    int sx2, int sy2,
+                                    double dx1, double dy1,
+                                    double dx2, double dy2)
+    {
+        // assert rq.lock.isHeldByCurrentThread();
+        RenderBuffer buf = rq.getBuffer();
+        rq.ensureCapacityAndAlignment(72, 24);
+        buf.putInt(BLIT);
+        buf.putInt(packedParams);
+        buf.putInt(sx1).putInt(sy1);
+        buf.putInt(sx2).putInt(sy2);
+        buf.putDouble(dx1).putDouble(dy1);
+        buf.putDouble(dx2).putDouble(dy2);
+        buf.putLong(src.getNativeOps());
+        buf.putLong(dst.getNativeOps());
+    }
+
+    static void Blit(SurfaceData srcData, SurfaceData dstData,
+                     Composite comp, Region clip,
+                     AffineTransform xform, int hint,
+                     int sx1, int sy1,
+                     int sx2, int sy2,
+                     double dx1, double dy1,
+                     double dx2, double dy2,
+                     int srctype, boolean texture)
+    {
+        int ctxflags = 0;
+        if (srcData.getTransparency() == Transparency.OPAQUE) {
+            ctxflags |= MTLContext.SRC_IS_OPAQUE;
+        }
+
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            // make sure the RenderQueue keeps a hard reference to the
+            // source (sysmem) SurfaceData to prevent it from being
+            // disposed while the operation is processed on the QFT
+            rq.addReference(srcData);
+
+            MTLSurfaceData mtlDst = (MTLSurfaceData)dstData;
+            if (texture) {
+                // make sure we have a current context before uploading
+                // the sysmem data to the texture object
+                MTLGraphicsConfig gc = mtlDst.getMTLGraphicsConfig();
+                MTLContext.setScratchSurface(gc);
+            } else {
+                MTLContext.validateContext(mtlDst, mtlDst,
+                        clip, comp, xform, null, null,
+                        ctxflags);
+            }
+
+            int packedParams = createPackedParams(false, texture,
+                    false /*unused*/, xform != null,
+                    hint, srctype);
+            enqueueBlit(rq, srcData, dstData,
+                    packedParams,
+                    sx1, sy1, sx2, sy2,
+                    dx1, dy1, dx2, dy2);
+
+            // always flush immediately, since we (currently) have no means
+            // of tracking changes to the system memory surface
+            rq.flushNow();
+        } finally {
+            rq.unlock();
+        }
+    }
+
+    /**
+     * Note: The srcImg and biop parameters are only used when invoked
+     * from the MTLBufImgOps.renderImageWithOp() method; in all other cases,
+     * this method can be called with null values for those two parameters,
+     * and they will be effectively ignored.
+     */
+    static void IsoBlit(SurfaceData srcData, SurfaceData dstData,
+                        BufferedImage srcImg, BufferedImageOp biop,
+                        Composite comp, Region clip,
+                        AffineTransform xform, int hint,
+                        int sx1, int sy1,
+                        int sx2, int sy2,
+                        double dx1, double dy1,
+                        double dx2, double dy2,
+                        boolean texture)
+    {
+        int ctxflags = 0;
+        if (srcData.getTransparency() == Transparency.OPAQUE) {
+            ctxflags |= MTLContext.SRC_IS_OPAQUE;
+        }
+
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            MTLSurfaceData mtlSrc = (MTLSurfaceData)srcData;
+            MTLSurfaceData mtlDst = (MTLSurfaceData)dstData;
+            int srctype = mtlSrc.getType();
+            boolean rtt;
+            MTLSurfaceData srcCtxData;
+            if (srctype == MTLSurfaceData.TEXTURE) {
+                // the source is a regular texture object; we substitute
+                // the destination surface for the purposes of making a
+                // context current
+                rtt = false;
+                srcCtxData = mtlDst;
+            } else {
+                // the source is a pbuffer, backbuffer, or render-to-texture
+                // surface; we set rtt to true to differentiate this kind
+                // of surface from a regular texture object
+                rtt = true;
+                if (srctype == AccelSurface.RT_TEXTURE) {
+                    srcCtxData = mtlDst;
+                } else {
+                    srcCtxData = mtlSrc;
+                }
+            }
+
+            MTLContext.validateContext(srcCtxData, mtlDst,
+                    clip, comp, xform, null, null,
+                    ctxflags);
+
+            if (biop != null) {
+                MTLBufImgOps.enableBufImgOp(rq, mtlSrc, srcImg, biop);
+            }
+
+            int packedParams = createPackedParams(true, texture,
+                    false /*unused*/, xform != null,
+                    hint, 0 /*unused*/);
+            enqueueBlit(rq, srcData, dstData,
+                    packedParams,
+                    sx1, sy1, sx2, sy2,
+                    dx1, dy1, dx2, dy2);
+
+            if (biop != null) {
+                MTLBufImgOps.disableBufImgOp(rq, biop);
+            }
+
+            if (rtt && mtlDst.isOnScreen()) {
+                // we only have to flush immediately when copying from a
+                // (non-texture) surface to the screen; otherwise Swing apps
+                // might appear unresponsive until the auto-flush completes
+                rq.flushNow();
+            }
+        } finally {
+            rq.unlock();
+        }
+    }
+}
+
+class MTLSurfaceToSurfaceBlit extends Blit {
+
+    MTLSurfaceToSurfaceBlit() {
+        super(MTLSurfaceData.MTLSurface,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Blit(SurfaceData src, SurfaceData dst,
+                     Composite comp, Region clip,
+                     int sx, int sy, int dx, int dy, int w, int h)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                false);
+    }
+}
+
+class MTLSurfaceToSurfaceScale extends ScaledBlit {
+
+    MTLSurfaceToSurfaceScale() {
+        super(MTLSurfaceData.MTLSurface,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Scale(SurfaceData src, SurfaceData dst,
+                      Composite comp, Region clip,
+                      int sx1, int sy1,
+                      int sx2, int sy2,
+                      double dx1, double dy1,
+                      double dx2, double dy2)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx1, sy1, sx2, sy2,
+                dx1, dy1, dx2, dy2,
+                false);
+    }
+}
+
+class MTLSurfaceToSurfaceTransform extends TransformBlit {
+
+    MTLSurfaceToSurfaceTransform() {
+        super(MTLSurfaceData.MTLSurface,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Transform(SurfaceData src, SurfaceData dst,
+                          Composite comp, Region clip,
+                          AffineTransform at, int hint,
+                          int sx, int sy, int dx, int dy,
+                          int w, int h)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, at, hint,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                false);
+    }
+}
+
+class MTLRTTSurfaceToSurfaceBlit extends Blit {
+
+    MTLRTTSurfaceToSurfaceBlit() {
+        super(MTLSurfaceData.MTLSurfaceRTT,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Blit(SurfaceData src, SurfaceData dst,
+                     Composite comp, Region clip,
+                     int sx, int sy, int dx, int dy, int w, int h)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                true);
+    }
+}
+
+class MTLRTTSurfaceToSurfaceScale extends ScaledBlit {
+
+    MTLRTTSurfaceToSurfaceScale() {
+        super(MTLSurfaceData.MTLSurfaceRTT,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Scale(SurfaceData src, SurfaceData dst,
+                      Composite comp, Region clip,
+                      int sx1, int sy1,
+                      int sx2, int sy2,
+                      double dx1, double dy1,
+                      double dx2, double dy2)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx1, sy1, sx2, sy2,
+                dx1, dy1, dx2, dy2,
+                true);
+    }
+}
+
+class MTLRTTSurfaceToSurfaceTransform extends TransformBlit {
+
+    MTLRTTSurfaceToSurfaceTransform() {
+        super(MTLSurfaceData.MTLSurfaceRTT,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Transform(SurfaceData src, SurfaceData dst,
+                          Composite comp, Region clip,
+                          AffineTransform at, int hint,
+                          int sx, int sy, int dx, int dy, int w, int h)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, at, hint,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                true);
+    }
+}
+
+final class MTLSurfaceToSwBlit extends Blit {
+
+    private final int typeval;
+    private WeakReference<SurfaceData> srcTmp;
+
+    // destination will actually be ArgbPre or Argb
+    MTLSurfaceToSwBlit(final SurfaceType dstType, final int typeval) {
+        super(MTLSurfaceData.MTLSurface,
+                CompositeType.SrcNoEa,
+                dstType);
+        this.typeval = typeval;
+    }
+
+    private synchronized void complexClipBlit(SurfaceData src, SurfaceData dst,
+                                              Composite comp, Region clip,
+                                              int sx, int sy, int dx, int dy,
+                                              int w, int h) {
+        SurfaceData cachedSrc = null;
+        if (srcTmp != null) {
+            // use cached intermediate surface, if available
+            cachedSrc = srcTmp.get();
+        }
+
+        // We can convert argb_pre data from MTL surface in two places:
+        // - During MTL surface -> SW blit
+        // - During SW -> SW blit
+        // The first one is faster when we use opaque MTL surface, because in
+        // this case we simply skip conversion and use color components as is.
+        // Because of this we align intermediate buffer type with type of
+        // destination not source.
+        final int type = typeval == MTLSurfaceData.PF_INT_ARGB_PRE ?
+                BufferedImage.TYPE_INT_ARGB_PRE :
+                BufferedImage.TYPE_INT_ARGB;
+
+        src = convertFrom(this, src, sx, sy, w, h, cachedSrc, type);
+
+        // copy intermediate SW to destination SW using complex clip
+        final Blit performop = Blit.getFromCache(src.getSurfaceType(),
+                CompositeType.SrcNoEa,
+                dst.getSurfaceType());
+        performop.Blit(src, dst, comp, clip, 0, 0, dx, dy, w, h);
+
+        if (src != cachedSrc) {
+            // cache the intermediate surface
+            srcTmp = new WeakReference<>(src);
+        }
+    }
+
+    public void Blit(SurfaceData src, SurfaceData dst,
+                     Composite comp, Region clip,
+                     int sx, int sy, int dx, int dy,
+                     int w, int h)
+    {
+        if (clip != null) {
+            clip = clip.getIntersectionXYWH(dx, dy, w, h);
+            // At the end this method will flush the RenderQueue, we should exit
+            // from it as soon as possible.
+            if (clip.isEmpty()) {
+                return;
+            }
+            sx += clip.getLoX() - dx;
+            sy += clip.getLoY() - dy;
+            dx = clip.getLoX();
+            dy = clip.getLoY();
+            w = clip.getWidth();
+            h = clip.getHeight();
+
+            if (!clip.isRectangular()) {
+                complexClipBlit(src, dst, comp, clip, sx, sy, dx, dy, w, h);
+                return;
+            }
+        }
+
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            // make sure the RenderQueue keeps a hard reference to the
+            // destination (sysmem) SurfaceData to prevent it from being
+            // disposed while the operation is processed on the QFT
+            rq.addReference(dst);
+
+            RenderBuffer buf = rq.getBuffer();
+            MTLContext.validateContext((MTLSurfaceData)src);
+
+            rq.ensureCapacityAndAlignment(48, 32);
+            buf.putInt(SURFACE_TO_SW_BLIT);
+            buf.putInt(sx).putInt(sy);
+            buf.putInt(dx).putInt(dy);
+            buf.putInt(w).putInt(h);
+            buf.putInt(typeval);
+            buf.putLong(src.getNativeOps());
+            buf.putLong(dst.getNativeOps());
+
+            // always flush immediately
+            rq.flushNow();
+        } finally {
+            rq.unlock();
+        }
+    }
+}
+
+class MTLSwToSurfaceBlit extends Blit {
+
+    private int typeval;
+
+    MTLSwToSurfaceBlit(SurfaceType srcType, int typeval) {
+        super(srcType,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+        this.typeval = typeval;
+    }
+
+    public void Blit(SurfaceData src, SurfaceData dst,
+                     Composite comp, Region clip,
+                     int sx, int sy, int dx, int dy, int w, int h)
+    {
+        MTLBlitLoops.Blit(src, dst,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                typeval, false);
+    }
+}
+
+class MTLSwToSurfaceScale extends ScaledBlit {
+
+    private int typeval;
+
+    MTLSwToSurfaceScale(SurfaceType srcType, int typeval) {
+        super(srcType,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+        this.typeval = typeval;
+    }
+
+    public void Scale(SurfaceData src, SurfaceData dst,
+                      Composite comp, Region clip,
+                      int sx1, int sy1,
+                      int sx2, int sy2,
+                      double dx1, double dy1,
+                      double dx2, double dy2)
+    {
+        MTLBlitLoops.Blit(src, dst,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx1, sy1, sx2, sy2,
+                dx1, dy1, dx2, dy2,
+                typeval, false);
+    }
+}
+
+class MTLSwToSurfaceTransform extends TransformBlit {
+
+    private int typeval;
+
+    MTLSwToSurfaceTransform(SurfaceType srcType, int typeval) {
+        super(srcType,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+        this.typeval = typeval;
+    }
+
+    public void Transform(SurfaceData src, SurfaceData dst,
+                          Composite comp, Region clip,
+                          AffineTransform at, int hint,
+                          int sx, int sy, int dx, int dy, int w, int h)
+    {
+        MTLBlitLoops.Blit(src, dst,
+                comp, clip, at, hint,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                typeval, false);
+    }
+}
+
+class MTLSwToTextureBlit extends Blit {
+
+    private int typeval;
+
+    MTLSwToTextureBlit(SurfaceType srcType, int typeval) {
+        super(srcType,
+                CompositeType.SrcNoEa,
+                MTLSurfaceData.MTLTexture);
+        this.typeval = typeval;
+    }
+
+    public void Blit(SurfaceData src, SurfaceData dst,
+                     Composite comp, Region clip,
+                     int sx, int sy, int dx, int dy, int w, int h)
+    {
+        MTLBlitLoops.Blit(src, dst,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                typeval, true);
+    }
+}
+
+class MTLTextureToSurfaceBlit extends Blit {
+
+    MTLTextureToSurfaceBlit() {
+        super(MTLSurfaceData.MTLTexture,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Blit(SurfaceData src, SurfaceData dst,
+                     Composite comp, Region clip,
+                     int sx, int sy, int dx, int dy, int w, int h)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                true);
+    }
+}
+
+class MTLTextureToSurfaceScale extends ScaledBlit {
+
+    MTLTextureToSurfaceScale() {
+        super(MTLSurfaceData.MTLTexture,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Scale(SurfaceData src, SurfaceData dst,
+                      Composite comp, Region clip,
+                      int sx1, int sy1,
+                      int sx2, int sy2,
+                      double dx1, double dy1,
+                      double dx2, double dy2)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, null,
+                AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
+                sx1, sy1, sx2, sy2,
+                dx1, dy1, dx2, dy2,
+                true);
+    }
+}
+
+class MTLTextureToSurfaceTransform extends TransformBlit {
+
+    MTLTextureToSurfaceTransform() {
+        super(MTLSurfaceData.MTLTexture,
+                CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+    }
+
+    public void Transform(SurfaceData src, SurfaceData dst,
+                          Composite comp, Region clip,
+                          AffineTransform at, int hint,
+                          int sx, int sy, int dx, int dy,
+                          int w, int h)
+    {
+        MTLBlitLoops.IsoBlit(src, dst,
+                null, null,
+                comp, clip, at, hint,
+                sx, sy, sx+w, sy+h,
+                dx, dy, dx+w, dy+h,
+                true);
+    }
+}
+
+/**
+ * This general Blit implementation converts any source surface to an
+ * intermediate IntArgbPre surface, and then uses the more specific
+ * IntArgbPre->MTLSurface/Texture loop to get the intermediate
+ * (premultiplied) surface down to Metal using simple blit.
+ */
+class MTLGeneralBlit extends Blit {
+
+    private final Blit performop;
+    private WeakReference<SurfaceData> srcTmp;
+
+    MTLGeneralBlit(SurfaceType dstType,
+                   CompositeType compType,
+                   Blit performop)
+    {
+        super(SurfaceType.Any, compType, dstType);
+        this.performop = performop;
+    }
+
+    public synchronized void Blit(SurfaceData src, SurfaceData dst,
+                                  Composite comp, Region clip,
+                                  int sx, int sy, int dx, int dy,
+                                  int w, int h)
+    {
+        Blit convertsrc = Blit.getFromCache(src.getSurfaceType(),
+                CompositeType.SrcNoEa,
+                SurfaceType.IntArgbPre);
+
+        SurfaceData cachedSrc = null;
+        if (srcTmp != null) {
+            // use cached intermediate surface, if available
+            cachedSrc = srcTmp.get();
+        }
+
+        // convert source to IntArgbPre
+        src = convertFrom(convertsrc, src, sx, sy, w, h,
+                cachedSrc, BufferedImage.TYPE_INT_ARGB_PRE);
+
+        // copy IntArgbPre intermediate surface to Metal surface
+        performop.Blit(src, dst, comp, clip,
+                0, 0, dx, dy, w, h);
+
+        if (src != cachedSrc) {
+            // cache the intermediate surface
+            srcTmp = new WeakReference<>(src);
+        }
+    }
+}
+
+/**
+ * This general TransformedBlit implementation converts any source surface to an
+ * intermediate IntArgbPre surface, and then uses the more specific
+ * IntArgbPre->MTLSurface/Texture loop to get the intermediate
+ * (premultiplied) surface down to Metal using simple transformBlit.
+ */
+final class MTLGeneralTransformedBlit extends TransformBlit {
+
+    private final TransformBlit performop;
+    private WeakReference<SurfaceData> srcTmp;
+
+    MTLGeneralTransformedBlit(final TransformBlit performop) {
+        super(SurfaceType.Any, CompositeType.AnyAlpha,
+                MTLSurfaceData.MTLSurface);
+        this.performop = performop;
+    }
+
+    @Override
+    public synchronized void Transform(SurfaceData src, SurfaceData dst,
+                                       Composite comp, Region clip,
+                                       AffineTransform at, int hint, int srcx,
+                                       int srcy, int dstx, int dsty, int width,
+                                       int height){
+        Blit convertsrc = Blit.getFromCache(src.getSurfaceType(),
+                CompositeType.SrcNoEa,
+                SurfaceType.IntArgbPre);
+        // use cached intermediate surface, if available
+        final SurfaceData cachedSrc = srcTmp != null ? srcTmp.get() : null;
+        // convert source to IntArgbPre
+        src = convertFrom(convertsrc, src, srcx, srcy, width, height, cachedSrc,
+                BufferedImage.TYPE_INT_ARGB_PRE);
+
+        // transform IntArgbPre intermediate surface to Metal surface
+        performop.Transform(src, dst, comp, clip, at, hint, 0, 0, dstx, dsty,
+                width, height);
+
+        if (src != cachedSrc) {
+            // cache the intermediate surface
+            srcTmp = new WeakReference<>(src);
+        }
+    }
+}
+
+/**
+ * This general MTLAnyCompositeBlit implementation can convert any source/target
+ * surface to an intermediate surface using convertsrc/convertdst loops, applies
+ * necessary composite operation, and then uses convertresult loop to get the
+ * intermediate surface down to Metal.
+ */
+final class MTLAnyCompositeBlit extends Blit {
+
+    private WeakReference<SurfaceData> dstTmp;
+    private WeakReference<SurfaceData> srcTmp;
+    private final Blit convertsrc;
+    private final Blit convertdst;
+    private final Blit convertresult;
+
+    MTLAnyCompositeBlit(SurfaceType srctype, Blit convertsrc, Blit convertdst,
+                        Blit convertresult) {
+        super(srctype, CompositeType.Any, MTLSurfaceData.MTLSurface);
+        this.convertsrc = convertsrc;
+        this.convertdst = convertdst;
+        this.convertresult = convertresult;
+    }
+
+    public synchronized void Blit(SurfaceData src, SurfaceData dst,
+                                  Composite comp, Region clip,
+                                  int sx, int sy, int dx, int dy,
+                                  int w, int h)
+    {
+        if (convertsrc != null) {
+            SurfaceData cachedSrc = null;
+            if (srcTmp != null) {
+                // use cached intermediate surface, if available
+                cachedSrc = srcTmp.get();
+            }
+            // convert source to IntArgbPre
+            src = convertFrom(convertsrc, src, sx, sy, w, h, cachedSrc,
+                    BufferedImage.TYPE_INT_ARGB_PRE);
+            if (src != cachedSrc) {
+                // cache the intermediate surface
+                srcTmp = new WeakReference<>(src);
+            }
+        }
+
+        SurfaceData cachedDst = null;
+
+        if (dstTmp != null) {
+            // use cached intermediate surface, if available
+            cachedDst = dstTmp.get();
+        }
+
+        // convert destination to IntArgbPre
+        SurfaceData dstBuffer = convertFrom(convertdst, dst, dx, dy, w, h,
+                cachedDst, BufferedImage.TYPE_INT_ARGB_PRE);
+        Region bufferClip =
+                clip == null ? null : clip.getTranslatedRegion(-dx, -dy);
+
+        Blit performop = Blit.getFromCache(src.getSurfaceType(),
+                CompositeType.Any, dstBuffer.getSurfaceType());
+        performop.Blit(src, dstBuffer, comp, bufferClip, sx, sy, 0, 0, w, h);
+
+        if (dstBuffer != cachedDst) {
+            // cache the intermediate surface
+            dstTmp = new WeakReference<>(dstBuffer);
+        }
+        // now blit the buffer back to the destination
+        convertresult.Blit(dstBuffer, dst, AlphaComposite.Src, clip, 0, 0, dx,
+                dy, w, h);
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLBufImgOps.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLBufImgOps.java
new file mode 100644
index 00000000000..fded8f6c72b
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLBufImgOps.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.SunGraphics2D;
+import sun.java2d.SurfaceData;
+import sun.java2d.loops.CompositeType;
+import sun.java2d.pipe.BufferedBufImgOps;
+
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ConvolveOp;
+import java.awt.image.LookupOp;
+import java.awt.image.RescaleOp;
+
+import static sun.java2d.metal.MTLContext.MTLContextCaps.CAPS_EXT_BIOP_SHADER;
+
+class MTLBufImgOps extends BufferedBufImgOps {
+
+    /**
+     * This method is called from MTLDrawImage.transformImage() only.  It
+     * validates the provided BufferedImageOp to determine whether the op
+     * is one that can be accelerated by the MTL pipeline.  If the operation
+     * cannot be completed for any reason, this method returns false;
+     * otherwise, the given BufferedImage is rendered to the destination
+     * using the provided BufferedImageOp and this method returns true.
+     */
+    static boolean renderImageWithOp(SunGraphics2D sg, BufferedImage img,
+                                     BufferedImageOp biop, int x, int y)
+    {
+        // Validate the provided BufferedImage (make sure it is one that
+        // is supported, and that its properties are acceleratable)
+        if (biop instanceof ConvolveOp) {
+            if (!isConvolveOpValid((ConvolveOp)biop)) {
+                return false;
+            }
+        } else if (biop instanceof RescaleOp) {
+            if (!isRescaleOpValid((RescaleOp)biop, img)) {
+                return false;
+            }
+        } else if (biop instanceof LookupOp) {
+            if (!isLookupOpValid((LookupOp)biop, img)) {
+                return false;
+            }
+        } else {
+            // No acceleration for other BufferedImageOps (yet)
+            return false;
+        }
+
+        SurfaceData dstData = sg.surfaceData;
+        if (!(dstData instanceof MTLSurfaceData) ||
+                (sg.interpolationType == AffineTransformOp.TYPE_BICUBIC) ||
+                (sg.compositeState > SunGraphics2D.COMP_ALPHA))
+        {
+            return false;
+        }
+
+        SurfaceData srcData =
+                dstData.getSourceSurfaceData(img, SunGraphics2D.TRANSFORM_ISIDENT,
+                        CompositeType.SrcOver, null);
+        if (!(srcData instanceof MTLSurfaceData)) {
+            // REMIND: this hack tries to ensure that we have a cached texture
+            srcData =
+                    dstData.getSourceSurfaceData(img, SunGraphics2D.TRANSFORM_ISIDENT,
+                            CompositeType.SrcOver, null);
+            if (!(srcData instanceof MTLSurfaceData)) {
+                return false;
+            }
+        }
+
+        // Verify that the source surface is actually a texture and
+        // that the operation is supported
+        MTLSurfaceData mtlSrc = (MTLSurfaceData)srcData;
+        MTLGraphicsConfig gc = mtlSrc.getMTLGraphicsConfig();
+        if (mtlSrc.getType() != MTLSurfaceData.TEXTURE ||
+                !gc.isCapPresent(CAPS_EXT_BIOP_SHADER))
+        {
+            return false;
+        }
+
+        int sw = img.getWidth();
+        int sh = img.getHeight();
+        MTLBlitLoops.IsoBlit(srcData, dstData,
+                img, biop,
+                sg.composite, sg.getCompClip(),
+                sg.transform, sg.interpolationType,
+                0, 0, sw, sh,
+                x, y, x+sw, y+sh,
+                true);
+
+        return true;
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLContext.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLContext.java
new file mode 100644
index 00000000000..86e90770857
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLContext.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.pipe.BufferedContext;
+import sun.java2d.pipe.RenderBuffer;
+import sun.java2d.pipe.RenderQueue;
+import sun.java2d.pipe.hw.ContextCapabilities;
+
+import java.lang.annotation.Native;
+
+import static sun.java2d.pipe.BufferedOpCodes.INVALIDATE_CONTEXT;
+import static sun.java2d.pipe.BufferedOpCodes.SET_SCRATCH_SURFACE;
+
+/**
+ * Note that the RenderQueue lock must be acquired before calling any of
+ * the methods in this class.
+ */
+final class MTLContext extends BufferedContext {
+
+    public MTLContext(RenderQueue rq) {
+        super(rq);
+    }
+
+    /**
+     * Convenience method that delegates to setScratchSurface() below.
+     */
+    static void setScratchSurface(MTLGraphicsConfig gc) {
+        setScratchSurface(gc.getNativeConfigInfo());
+    }
+
+    /**
+     * Makes the given GraphicsConfig's context current to its associated
+     * "scratch surface".  Each GraphicsConfig maintains a native context
+     * (MTLDevice) as well as a native MTLTexture
+     * known as the "scratch surface".  By making the context current to the
+     * scratch surface, we are assured that we have a current context for
+     * the relevant GraphicsConfig, and can therefore perform operations
+     * depending on the capabilities of that GraphicsConfig.
+     * This method should be used for operations with an MTL texture
+     * as the destination surface (e.g. a sw->texture blit loop), or in those
+     * situations where we may not otherwise have a current context (e.g.
+     * when disposing a texture-based surface).
+     */
+    public static void setScratchSurface(long pConfigInfo) {
+        // assert MTLRenderQueue.getInstance().lock.isHeldByCurrentThread();
+
+        // invalidate the current context
+        currentContext = null;
+
+        // set the scratch context
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        RenderBuffer buf = rq.getBuffer();
+        rq.ensureCapacityAndAlignment(12, 4);
+        buf.putInt(SET_SCRATCH_SURFACE);
+        buf.putLong(pConfigInfo);
+    }
+
+    /**
+     * Invalidates the currentContext field to ensure that we properly
+     * revalidate the MTLContext (make it current, etc.) next time through
+     * the validate() method.  This is typically invoked from methods
+     * that affect the current context state (e.g. disposing a context or
+     * surface).
+     */
+    public static void invalidateCurrentContext() {
+        // assert MTLRenderQueue.getInstance().lock.isHeldByCurrentThread();
+
+        // invalidate the current Java-level context so that we
+        // revalidate everything the next time around
+        if (currentContext != null) {
+            currentContext.invalidateContext();
+            currentContext = null;
+        }
+
+        // invalidate the context reference at the native level, and
+        // then flush the queue so that we have no pending operations
+        // dependent on the current context
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.ensureCapacity(4);
+        rq.getBuffer().putInt(INVALIDATE_CONTEXT);
+        rq.flushNow();
+    }
+
+    public static class MTLContextCaps extends ContextCapabilities {
+
+        /** Indicates that the context is doublebuffered. */
+        @Native
+        public static final int CAPS_DOUBLEBUFFERED   = (FIRST_PRIVATE_CAP << 0);
+        /**
+         * This cap will only be set if the lcdshader system property has been
+         * enabled and the hardware supports the minimum number of texture units
+         */
+        @Native
+        static final int CAPS_EXT_LCD_SHADER   = (FIRST_PRIVATE_CAP << 1);
+        /**
+         * This cap will only be set if the biopshader system property has been
+         * enabled and the hardware meets our minimum requirements.
+         */
+        @Native
+        public static final int CAPS_EXT_BIOP_SHADER  = (FIRST_PRIVATE_CAP << 2);
+        /**
+         * This cap will only be set if the gradshader system property has been
+         * enabled and the hardware meets our minimum requirements.
+         */
+        @Native
+        static final int CAPS_EXT_GRAD_SHADER  = (FIRST_PRIVATE_CAP << 3);
+
+        public MTLContextCaps(int caps, String adapterId) {
+            super(caps, adapterId);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(super.toString());
+            if ((caps & CAPS_DOUBLEBUFFERED) != 0) {
+                sb.append("CAPS_DOUBLEBUFFERED|");
+            }
+            if ((caps & CAPS_EXT_LCD_SHADER) != 0) {
+                sb.append("CAPS_EXT_LCD_SHADER|");
+            }
+            if ((caps & CAPS_EXT_BIOP_SHADER) != 0) {
+                sb.append("CAPS_BIOP_SHADER|");
+            }
+            if ((caps & CAPS_EXT_GRAD_SHADER) != 0) {
+                sb.append("CAPS_EXT_GRAD_SHADER|");
+            }
+            return sb.toString();
+        }
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLDrawImage.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLDrawImage.java
new file mode 100644
index 00000000000..06ef5c4c089
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLDrawImage.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.SunGraphics2D;
+import sun.java2d.SurfaceData;
+import sun.java2d.loops.SurfaceType;
+import sun.java2d.loops.TransformBlit;
+import sun.java2d.pipe.DrawImage;
+
+import java.awt.Color;
+import java.awt.Image;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+
+public class MTLDrawImage extends DrawImage {
+
+    @Override
+    protected void renderImageXform(SunGraphics2D sg, Image img,
+                                    AffineTransform tx, int interpType,
+                                    int sx1, int sy1, int sx2, int sy2,
+                                    Color bgColor)
+    {
+        // punt to the MediaLib-based transformImage() in the superclass if:
+        //     - bicubic interpolation is specified
+        //     - a background color is specified and will be used
+        //     - the source surface is neither a texture nor render-to-texture
+        //       surface, and a non-default interpolation hint is specified
+        //       (we can only control the filtering for texture->surface
+        //       copies)
+        //         REMIND: we should tweak the sw->texture->surface
+        //         transform case to handle filtering appropriately
+        //         (see 4841762)...
+        //     - an appropriate TransformBlit primitive could not be found
+        if (interpType != AffineTransformOp.TYPE_BICUBIC) {
+            SurfaceData dstData = sg.surfaceData;
+            SurfaceData srcData =
+                    dstData.getSourceSurfaceData(img,
+                            SunGraphics2D.TRANSFORM_GENERIC,
+                            sg.imageComp,
+                            bgColor);
+
+            if (srcData != null &&
+                    !isBgOperation(srcData, bgColor) &&
+                    (srcData.getSurfaceType() == MTLSurfaceData.MTLTexture ||
+                            srcData.getSurfaceType() == MTLSurfaceData.MTLSurfaceRTT ||
+                            interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR))
+            {
+                SurfaceType srcType = srcData.getSurfaceType();
+                SurfaceType dstType = dstData.getSurfaceType();
+                TransformBlit blit = TransformBlit.getFromCache(srcType,
+                        sg.imageComp,
+                        dstType);
+
+                if (blit != null) {
+                    blit.Transform(srcData, dstData,
+                            sg.composite, sg.getCompClip(),
+                            tx, interpType,
+                            sx1, sy1, 0, 0, sx2-sx1, sy2-sy1);
+                    return;
+                }
+            }
+        }
+
+        super.renderImageXform(sg, img, tx, interpType,
+                sx1, sy1, sx2, sy2, bgColor);
+    }
+
+    @Override
+    public void transformImage(SunGraphics2D sg, BufferedImage img,
+                               BufferedImageOp op, int x, int y)
+    {
+        if (op != null) {
+            if (op instanceof AffineTransformOp) {
+                AffineTransformOp atop = (AffineTransformOp) op;
+                transformImage(sg, img, x, y,
+                        atop.getTransform(),
+                        atop.getInterpolationType());
+                return;
+            } else {
+                if (MTLBufImgOps.renderImageWithOp(sg, img, op, x, y)) {
+                    return;
+                }
+            }
+            img = op.filter(img, null);
+        }
+        copyImage(sg, img, x, y, null);
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLGraphicsConfig.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLGraphicsConfig.java
new file mode 100644
index 00000000000..24602587e17
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLGraphicsConfig.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.awt.CGraphicsConfig;
+import sun.awt.CGraphicsDevice;
+import sun.awt.image.OffScreenImage;
+import sun.awt.image.SunVolatileImage;
+import sun.awt.image.SurfaceManager;
+import sun.java2d.Disposer;
+import sun.java2d.DisposerRecord;
+import sun.java2d.Surface;
+import sun.java2d.SurfaceData;
+import sun.java2d.pipe.hw.AccelGraphicsConfig;
+import sun.java2d.pipe.hw.AccelSurface;
+import sun.java2d.pipe.hw.AccelTypedVolatileImage;
+import sun.java2d.pipe.hw.ContextCapabilities;
+import sun.lwawt.LWComponentPeer;
+import sun.lwawt.macosx.CFRetainedResource;
+
+import java.awt.AWTException;
+import java.awt.BufferCapabilities;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.ImageCapabilities;
+import java.awt.Rectangle;
+import java.awt.Transparency;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DirectColorModel;
+import java.awt.image.VolatileImage;
+import java.awt.image.WritableRaster;
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import static sun.java2d.metal.MTLContext.MTLContextCaps.CAPS_EXT_GRAD_SHADER;
+import static sun.java2d.pipe.hw.AccelSurface.TEXTURE;
+import static sun.java2d.pipe.hw.AccelSurface.RT_TEXTURE;
+import static sun.java2d.pipe.hw.ContextCapabilities.*;
+
+import static sun.java2d.metal.MTLContext.MTLContextCaps.CAPS_EXT_BIOP_SHADER;
+
+public final class MTLGraphicsConfig extends CGraphicsConfig
+        implements AccelGraphicsConfig, SurfaceManager.ProxiedGraphicsConfig
+{
+    private static boolean mtlAvailable;
+    private static ImageCapabilities imageCaps = new MTLImageCaps();
+
+    private static final String mtlShadersLib = AccessController.doPrivileged(
+            (PrivilegedAction<String>) () ->
+                    System.getProperty("java.home", "") + File.separator +
+                            "lib" + File.separator + "shaders.metallib");
+
+
+    private BufferCapabilities bufferCaps;
+    private long pConfigInfo;
+    private ContextCapabilities mtlCaps;
+    private final MTLContext context;
+    private final Object disposerReferent = new Object();
+    private final int maxTextureSize;
+
+    private static native boolean isMetalFrameworkAvailable();
+    private static native boolean tryLoadMetalLibrary(int displayID, String shaderLib);
+    private static native long getMTLConfigInfo(int displayID, String mtlShadersLib);
+
+    /**
+     * Returns maximum texture size supported by Metal. Must be
+     * called under MTLRQ lock.
+     */
+    private static native int nativeGetMaxTextureSize();
+
+    static {
+        mtlAvailable = isMetalFrameworkAvailable();
+    }
+
+    private MTLGraphicsConfig(CGraphicsDevice device,
+                              long configInfo, int maxTextureSize,
+                              ContextCapabilities mtlCaps) {
+        super(device);
+
+        this.pConfigInfo = configInfo;
+        this.mtlCaps = mtlCaps;
+        this.maxTextureSize = maxTextureSize;
+        context = new MTLContext(MTLRenderQueue.getInstance());
+        // add a record to the Disposer so that we destroy the native
+        // MTLGraphicsConfigInfo data when this object goes away
+        Disposer.addRecord(disposerReferent,
+                new MTLGCDisposerRecord(pConfigInfo));
+    }
+
+    @Override
+    public Object getProxyKey() {
+        return this;
+    }
+
+    public SurfaceData createManagedSurface(int w, int h, int transparency) {
+        return MTLSurfaceData.createData(this, w, h,
+                getColorModel(transparency),
+                null,
+                MTLSurfaceData.TEXTURE);
+    }
+
+    public static MTLGraphicsConfig getConfig(CGraphicsDevice device,
+                                              int displayID)
+    {
+        if (!mtlAvailable) {
+            return null;
+        }
+
+        if (!tryLoadMetalLibrary(displayID, mtlShadersLib)) {
+            return null;
+        }
+
+        long cfginfo = 0;
+        int textureSize = 0;
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            // getMTLConfigInfo() creates and destroys temporary
+            // surfaces/contexts, so we should first invalidate the current
+            // Java-level context and flush the queue...
+            MTLContext.invalidateCurrentContext();
+            cfginfo = getMTLConfigInfo(displayID, mtlShadersLib);
+            if (cfginfo != 0L) {
+                textureSize = nativeGetMaxTextureSize();
+                // TODO : This clamping code is same as in OpenGL.
+                // Whether we need such clamping or not in case of Metal
+                // will be pursued under 8260644
+                textureSize = textureSize <= 16384 ? textureSize / 2 : 8192;
+                MTLContext.setScratchSurface(cfginfo);
+            }
+        } finally {
+            rq.unlock();
+        }
+        if (cfginfo == 0) {
+            return null;
+        }
+
+        ContextCapabilities caps = new MTLContext.MTLContextCaps(
+                CAPS_PS30 | CAPS_PS20 |
+                        CAPS_RT_TEXTURE_ALPHA | CAPS_RT_TEXTURE_OPAQUE |
+                        CAPS_MULTITEXTURE | CAPS_TEXNONPOW2 | CAPS_TEXNONSQUARE |
+                        CAPS_EXT_BIOP_SHADER | CAPS_EXT_GRAD_SHADER,
+                null);
+        return new MTLGraphicsConfig(device, cfginfo, textureSize, caps);
+    }
+
+    public static boolean isMetalAvailable() {
+        return mtlAvailable;
+    }
+
+    /**
+     * Returns true if the provided capability bit is present for this config.
+     * See MTLContext.java for a list of supported capabilities.
+     */
+    public boolean isCapPresent(int cap) {
+        return ((mtlCaps.getCaps() & cap) != 0);
+    }
+
+    public long getNativeConfigInfo() {
+        return pConfigInfo;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see sun.java2d.pipe.hw.BufferedContextProvider#getContext
+     */
+    @Override
+    public MTLContext getContext() {
+        return context;
+    }
+
+    @Override
+    public BufferedImage createCompatibleImage(int width, int height) {
+        ColorModel model = new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
+        WritableRaster
+                raster = model.createCompatibleWritableRaster(width, height);
+        return new BufferedImage(model, raster, model.isAlphaPremultiplied(),
+                null);
+    }
+
+    @Override
+    public ColorModel getColorModel(int transparency) {
+        switch (transparency) {
+            case Transparency.OPAQUE:
+                // REMIND: once the ColorModel spec is changed, this should be
+                //         an opaque premultiplied DCM...
+                return new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
+            case Transparency.BITMASK:
+                return new DirectColorModel(25, 0xff0000, 0xff00, 0xff, 0x1000000);
+            case Transparency.TRANSLUCENT:
+                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+                return new DirectColorModel(cs, 32,
+                        0xff0000, 0xff00, 0xff, 0xff000000,
+                        true, DataBuffer.TYPE_INT);
+            default:
+                return null;
+        }
+    }
+
+    public boolean isDoubleBuffered() {
+        return true;
+    }
+
+    private static class MTLGCDisposerRecord implements DisposerRecord {
+        private long pCfgInfo;
+        public MTLGCDisposerRecord(long pCfgInfo) {
+            this.pCfgInfo = pCfgInfo;
+        }
+        public void dispose() {
+            if (pCfgInfo != 0) {
+                MTLRenderQueue.disposeGraphicsConfig(pCfgInfo);
+                pCfgInfo = 0;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return ("MTLGraphicsConfig[" + getDevice().getIDstring() + "]");
+    }
+
+    @Override
+    public SurfaceData createSurfaceData(CFRetainedResource layer) {
+        return MTLSurfaceData.createData((MTLLayer) layer);
+    }
+
+    @Override
+    public Image createAcceleratedImage(Component target,
+                                        int width, int height)
+    {
+        ColorModel model = getColorModel(Transparency.OPAQUE);
+        WritableRaster wr = model.createCompatibleWritableRaster(width, height);
+        return new OffScreenImage(target, model, wr,
+                model.isAlphaPremultiplied());
+    }
+
+    @Override
+    public void assertOperationSupported(final int numBuffers,
+                                         final BufferCapabilities caps)
+            throws AWTException {
+        // Assume this method is never called with numBuffers != 2, as 0 is
+        // unsupported, and 1 corresponds to a SingleBufferStrategy which
+        // doesn't depend on the peer. Screen is considered as a separate
+        // "buffer".
+        if (numBuffers != 2) {
+            throw new AWTException("Only double buffering is supported");
+        }
+        final BufferCapabilities configCaps = getBufferCapabilities();
+        if (!configCaps.isPageFlipping()) {
+            throw new AWTException("Page flipping is not supported");
+        }
+        if (caps.getFlipContents() == BufferCapabilities.FlipContents.PRIOR) {
+            throw new AWTException("FlipContents.PRIOR is not supported");
+        }
+    }
+
+    @Override
+    public Image createBackBuffer(final LWComponentPeer<?, ?> peer) {
+        final Rectangle r = peer.getBounds();
+        // It is possible for the component to have size 0x0, adjust it to
+        // be at least 1x1 to avoid IAE
+        final int w = Math.max(1, r.width);
+        final int h = Math.max(1, r.height);
+        final int transparency = peer.isTranslucent() ? Transparency.TRANSLUCENT
+                : Transparency.OPAQUE;
+        return new SunVolatileImage(this, w, h, transparency, null);
+    }
+
+    @Override
+    public void destroyBackBuffer(final Image backBuffer) {
+        if (backBuffer != null) {
+            backBuffer.flush();
+        }
+    }
+
+    @Override
+    public void flip(final LWComponentPeer<?, ?> peer, final Image backBuffer,
+                     final int x1, final int y1, final int x2, final int y2,
+                     final BufferCapabilities.FlipContents flipAction) {
+        final Graphics g = peer.getGraphics();
+        try {
+            g.drawImage(backBuffer, x1, y1, x2, y2, x1, y1, x2, y2, null);
+        } finally {
+            g.dispose();
+        }
+        if (flipAction == BufferCapabilities.FlipContents.BACKGROUND) {
+            final Graphics2D bg = (Graphics2D) backBuffer.getGraphics();
+            try {
+                bg.setBackground(peer.getBackground());
+                bg.clearRect(0, 0, backBuffer.getWidth(null),
+                        backBuffer.getHeight(null));
+            } finally {
+                bg.dispose();
+            }
+        }
+    }
+
+    private static class MTLBufferCaps extends BufferCapabilities {
+        public MTLBufferCaps(boolean dblBuf) {
+            super(imageCaps, imageCaps,
+                    dblBuf ? FlipContents.UNDEFINED : null);
+        }
+    }
+
+    @Override
+    public BufferCapabilities getBufferCapabilities() {
+        if (bufferCaps == null) {
+            bufferCaps = new MTLBufferCaps(isDoubleBuffered());
+        }
+        return bufferCaps;
+    }
+
+    private static class MTLImageCaps extends ImageCapabilities {
+        private MTLImageCaps() {
+            super(true);
+        }
+        public boolean isTrueVolatile() {
+            return true;
+        }
+    }
+
+    @Override
+    public ImageCapabilities getImageCapabilities() {
+        return imageCaps;
+    }
+
+    @Override
+    public VolatileImage createCompatibleVolatileImage(int width, int height,
+                                                       int transparency,
+                                                       int type) {
+        if ((type != RT_TEXTURE && type != TEXTURE) ||
+            transparency == Transparency.BITMASK) {
+            return null;
+        }
+
+        SunVolatileImage vi = new AccelTypedVolatileImage(this, width, height,
+                transparency, type);
+        Surface sd = vi.getDestSurface();
+        if (!(sd instanceof AccelSurface) ||
+                ((AccelSurface)sd).getType() != type)
+        {
+            vi.flush();
+            vi = null;
+        }
+
+        return vi;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see sun.java2d.pipe.hw.AccelGraphicsConfig#getContextCapabilities
+     */
+    @Override
+    public ContextCapabilities getContextCapabilities() {
+        return mtlCaps;
+    }
+
+    @Override
+    public int getMaxTextureWidth() {
+        return Math.max(maxTextureSize / getDevice().getScaleFactor(),
+                getBounds().width);
+    }
+
+    @Override
+    public int getMaxTextureHeight() {
+        return Math.max(maxTextureSize / getDevice().getScaleFactor(),
+                getBounds().height);
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLLayer.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLLayer.java
new file mode 100644
index 00000000000..9f65387420f
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLLayer.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.NullSurfaceData;
+import sun.java2d.SurfaceData;
+import sun.lwawt.LWWindowPeer;
+import sun.lwawt.macosx.CFRetainedResource;
+
+import java.awt.GraphicsConfiguration;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Transparency;
+
+public class MTLLayer extends CFRetainedResource {
+
+    private native long nativeCreateLayer();
+    private static native void nativeSetScale(long layerPtr, double scale);
+
+    // Pass the insets to native code to make adjustments in blitTexture
+    private static native void nativeSetInsets(long layerPtr, int top, int left);
+    private static native void validate(long layerPtr, MTLSurfaceData mtlsd);
+    private static native void blitTexture(long layerPtr);
+
+    private LWWindowPeer peer;
+    private int scale = 1;
+
+    private SurfaceData surfaceData; // represents intermediate buffer (texture)
+
+    public MTLLayer(LWWindowPeer peer) {
+        super(0, true);
+
+        setPtr(nativeCreateLayer());
+        this.peer = peer;
+    }
+
+    public long getPointer() {
+        return ptr;
+    }
+
+    public Rectangle getBounds() {
+        return peer.getBounds();
+    }
+
+    public GraphicsConfiguration getGraphicsConfiguration() {
+        return peer.getGraphicsConfiguration();
+    }
+
+    public boolean isOpaque() {
+        return !peer.isTranslucent();
+    }
+
+    public int getTransparency() {
+        return isOpaque() ? Transparency.OPAQUE : Transparency.TRANSLUCENT;
+    }
+
+    public Object getDestination() {
+        return peer.getTarget();
+    }
+
+    public SurfaceData replaceSurfaceData() {
+        if (getBounds().isEmpty()) {
+            surfaceData = NullSurfaceData.theInstance;
+            return surfaceData;
+        }
+
+        // the layer redirects all painting to the buffer's graphics
+        // and blits the buffer to the layer surface (in display callback)
+        MTLGraphicsConfig gc = (MTLGraphicsConfig)getGraphicsConfiguration();
+        surfaceData = gc.createSurfaceData(this);
+        setScale(gc.getDevice().getScaleFactor());
+        Insets insets = peer.getInsets();
+        execute(ptr -> nativeSetInsets(ptr, insets.top, insets.left));
+        // the layer holds a reference to the buffer, which in
+        // turn has a reference back to this layer
+        if (surfaceData instanceof MTLSurfaceData) {
+            validate((MTLSurfaceData)surfaceData);
+        }
+
+        return surfaceData;
+    }
+
+    public SurfaceData getSurfaceData() {
+        return surfaceData;
+    }
+
+    public void validate(final MTLSurfaceData mtlsd) {
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            execute(ptr -> validate(ptr, mtlsd));
+        } finally {
+            rq.unlock();
+        }
+    }
+
+    @Override
+    public void dispose() {
+        // break the connection between the layer and the buffer
+        validate(null);
+        SurfaceData oldData = surfaceData;
+        surfaceData = NullSurfaceData.theInstance;;
+        if (oldData != null) {
+            oldData.flush();
+        }
+        super.dispose();
+    }
+
+    private void setScale(final int _scale) {
+        if (scale != _scale) {
+            scale = _scale;
+            execute(ptr -> nativeSetScale(ptr, scale));
+        }
+    }
+
+    // ----------------------------------------------------------------------
+    // NATIVE CALLBACKS
+    // ----------------------------------------------------------------------
+
+    private void drawInMTLContext() {
+        // tell the flusher thread not to update the intermediate buffer
+        // until we are done blitting from it
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            execute(ptr -> blitTexture(ptr));
+        } finally {
+            rq.unlock();
+        }
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLMaskBlit.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLMaskBlit.java
new file mode 100644
index 00000000000..15f0ada65c5
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLMaskBlit.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.SurfaceData;
+import sun.java2d.loops.CompositeType;
+import sun.java2d.loops.GraphicsPrimitive;
+import sun.java2d.loops.GraphicsPrimitiveMgr;
+import sun.java2d.loops.SurfaceType;
+import sun.java2d.pipe.BufferedMaskBlit;
+import sun.java2d.pipe.Region;
+
+import java.awt.Composite;
+
+import static sun.java2d.loops.CompositeType.SrcNoEa;
+import static sun.java2d.loops.CompositeType.SrcOver;
+import static sun.java2d.loops.SurfaceType.*;
+
+class MTLMaskBlit extends BufferedMaskBlit {
+
+    static void register() {
+        GraphicsPrimitive[] primitives = {
+                new MTLMaskBlit(IntArgb,    SrcOver),
+                new MTLMaskBlit(IntArgbPre, SrcOver),
+                new MTLMaskBlit(IntRgb,     SrcOver),
+                new MTLMaskBlit(IntRgb,     SrcNoEa),
+                new MTLMaskBlit(IntBgr,     SrcOver),
+                new MTLMaskBlit(IntBgr,     SrcNoEa),
+        };
+        GraphicsPrimitiveMgr.register(primitives);
+    }
+
+    private MTLMaskBlit(SurfaceType srcType,
+                        CompositeType compType)
+    {
+        super(MTLRenderQueue.getInstance(),
+                srcType, compType, MTLSurfaceData.MTLSurface);
+    }
+
+    @Override
+    protected void validateContext(SurfaceData dstData,
+                                   Composite comp, Region clip)
+    {
+        MTLSurfaceData mtlDst = (MTLSurfaceData)dstData;
+        MTLContext.validateContext(mtlDst, mtlDst,
+                clip, comp, null, null, null,
+                MTLContext.NO_CONTEXT_FLAGS);
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLMaskFill.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLMaskFill.java
new file mode 100644
index 00000000000..1be0c67f1e5
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLMaskFill.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.java2d.metal;
+
+import sun.java2d.InvalidPipeException;
+import sun.java2d.SunGraphics2D;
+import sun.java2d.loops.CompositeType;
+import sun.java2d.loops.GraphicsPrimitive;
+import sun.java2d.loops.GraphicsPrimitiveMgr;
+import sun.java2d.loops.SurfaceType;
+import sun.java2d.pipe.BufferedMaskFill;
+
+import java.awt.Composite;
+
+import static sun.java2d.loops.CompositeType.SrcNoEa;
+import static sun.java2d.loops.CompositeType.SrcOver;
+import static sun.java2d.loops.SurfaceType.*;
+
+class MTLMaskFill extends BufferedMaskFill {
+
+    static void register() {
+        GraphicsPrimitive[] primitives = {
+                new MTLMaskFill(AnyColor,                  SrcOver),
+                new MTLMaskFill(OpaqueColor,               SrcNoEa),
+                new MTLMaskFill(GradientPaint,             SrcOver),
+                new MTLMaskFill(OpaqueGradientPaint,       SrcNoEa),
+                new MTLMaskFill(LinearGradientPaint,       SrcOver),
+                new MTLMaskFill(OpaqueLinearGradientPaint, SrcNoEa),
+                new MTLMaskFill(RadialGradientPaint,       SrcOver),
+                new MTLMaskFill(OpaqueRadialGradientPaint, SrcNoEa),
+                new MTLMaskFill(TexturePaint,              SrcOver),
+                new MTLMaskFill(OpaqueTexturePaint,        SrcNoEa),
+        };
+        GraphicsPrimitiveMgr.register(primitives);
+    }
+
+    protected MTLMaskFill(SurfaceType srcType, CompositeType compType) {
+        super(MTLRenderQueue.getInstance(),
+                srcType, compType, MTLSurfaceData.MTLSurface);
+    }
+
+    @Override
+    protected native void maskFill(int x, int y, int w, int h,
+                                   int maskoff, int maskscan, int masklen,
+                                   byte[] mask);
+
+    @Override
+    protected void validateContext(SunGraphics2D sg2d,
+                                   Composite comp, int ctxflags)
+    {
+        MTLSurfaceData dstData;
+        try {
+            dstData = (MTLSurfaceData) sg2d.surfaceData;
+        } catch (ClassCastException e) {
+            throw new InvalidPipeException("wrong surface data type: " +
+                    sg2d.surfaceData);
+        }
+
+        MTLContext.validateContext(dstData, dstData,
+                sg2d.getCompClip(), comp,
+                null, sg2d.paint, sg2d, ctxflags);
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLPaints.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLPaints.java
new file mode 100644
index 00000000000..12f789deb35
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLPaints.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.java2d.metal;
+
+import sun.java2d.SunGraphics2D;
+import sun.java2d.SurfaceData;
+import sun.java2d.loops.CompositeType;
+
+import java.awt.LinearGradientPaint;
+import java.awt.MultipleGradientPaint;
+import java.awt.TexturePaint;
+
+import java.awt.MultipleGradientPaint.ColorSpaceType;
+import java.awt.MultipleGradientPaint.CycleMethod;
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+import static sun.java2d.metal.MTLContext.MTLContextCaps.CAPS_EXT_GRAD_SHADER;
+import static sun.java2d.pipe.BufferedPaints.MULTI_MAX_FRACTIONS;
+
+abstract class MTLPaints {
+
+    /**
+     * Holds all registered implementations, using the corresponding
+     * SunGraphics2D.PAINT_* constant as the hash key.
+     */
+    private static Map<Integer, MTLPaints> impls =
+            new HashMap<Integer, MTLPaints>(4, 1.0f);
+
+    static {
+        impls.put(SunGraphics2D.PAINT_GRADIENT, new Gradient());
+        impls.put(SunGraphics2D.PAINT_LIN_GRADIENT, new LinearGradient());
+        impls.put(SunGraphics2D.PAINT_RAD_GRADIENT, new RadialGradient());
+        impls.put(SunGraphics2D.PAINT_TEXTURE, new Texture());
+    }
+
+    /**
+     * Attempts to locate an implementation corresponding to the paint state
+     * of the provided SunGraphics2D object.  If no implementation can be
+     * found, or if the paint cannot be accelerated under the conditions
+     * of the SunGraphics2D, this method returns false; otherwise, returns
+     * true.
+     */
+    static boolean isValid(SunGraphics2D sg2d) {
+        MTLPaints impl = impls.get(sg2d.paintState);
+        return (impl != null && impl.isPaintValid(sg2d));
+    }
+
+    /**
+     * Returns true if this implementation is able to accelerate the
+     * Paint object associated with, and under the conditions of, the
+     * provided SunGraphics2D instance; otherwise returns false.
+     */
+    abstract boolean isPaintValid(SunGraphics2D sg2d);
+
+    /************************* GradientPaint support ****************************/
+
+    private static class Gradient extends MTLPaints {
+        private Gradient() {}
+
+        /**
+         * There are no restrictions for accelerating GradientPaint, so
+         * this method always returns true.
+         */
+        @Override
+        boolean isPaintValid(SunGraphics2D sg2d) {
+            return true;
+        }
+    }
+
+    /************************** TexturePaint support ****************************/
+
+    private static class Texture extends MTLPaints {
+        private Texture() {}
+
+        /**
+         * Returns true if the given TexturePaint instance can be used by the
+         * accelerated MTLPaints.Texture implementation.  A TexturePaint is
+         * considered valid if the following conditions are met:
+         *   - the texture image dimensions are power-of-two
+         *   - the texture image can be (or is already) cached in an Metal
+         *     texture object
+         */
+        @Override
+        boolean isPaintValid(SunGraphics2D sg2d) {
+            TexturePaint paint = (TexturePaint)sg2d.paint;
+            MTLSurfaceData dstData = (MTLSurfaceData)sg2d.surfaceData;
+            BufferedImage bi = paint.getImage();
+
+            SurfaceData srcData =
+                    dstData.getSourceSurfaceData(bi,
+                            SunGraphics2D.TRANSFORM_ISIDENT,
+                            CompositeType.SrcOver, null);
+            if (!(srcData instanceof MTLSurfaceData)) {
+                // REMIND: this is a hack that attempts to cache the system
+                //         memory image from the TexturePaint instance into an
+                //         Metal texture...
+                srcData =
+                        dstData.getSourceSurfaceData(bi,
+                                SunGraphics2D.TRANSFORM_ISIDENT,
+                                CompositeType.SrcOver, null);
+                if (!(srcData instanceof MTLSurfaceData)) {
+                    return false;
+                }
+            }
+
+            // verify that the source surface is actually a texture
+            MTLSurfaceData mtlData = (MTLSurfaceData)srcData;
+            if (mtlData.getType() != MTLSurfaceData.TEXTURE) {
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    /****************** Shared MultipleGradientPaint support ********************/
+
+    private abstract static class MultiGradient extends MTLPaints {
+        protected MultiGradient() {}
+
+        /**
+         * Returns true if the given MultipleGradientPaint instance can be
+         * used by the accelerated MTLPaints.MultiGradient implementation.
+         * A MultipleGradientPaint is considered valid if the following
+         * conditions are met:
+         *   - the number of gradient "stops" is <= MAX_FRACTIONS
+         *   - the destination has support for fragment shaders
+         */
+        @Override
+        boolean isPaintValid(SunGraphics2D sg2d) {
+            MultipleGradientPaint paint = (MultipleGradientPaint)sg2d.paint;
+            // REMIND: ugh, this creates garbage; would be nicer if
+            // we had a MultipleGradientPaint.getNumStops() method...
+            if (paint.getFractions().length > MULTI_MAX_FRACTIONS) {
+                return false;
+            }
+
+            MTLSurfaceData dstData = (MTLSurfaceData)sg2d.surfaceData;
+            MTLGraphicsConfig gc = dstData.getMTLGraphicsConfig();
+            if (!gc.isCapPresent(CAPS_EXT_GRAD_SHADER)) {
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    /********************** LinearGradientPaint support *************************/
+
+    private static class LinearGradient extends MultiGradient {
+        private LinearGradient() {}
+
+        @Override
+        boolean isPaintValid(SunGraphics2D sg2d) {
+            LinearGradientPaint paint = (LinearGradientPaint)sg2d.paint;
+
+            if (paint.getFractions().length == 2 &&
+                    paint.getCycleMethod() != CycleMethod.REPEAT &&
+                    paint.getColorSpace() != ColorSpaceType.LINEAR_RGB)
+            {
+                // we can delegate to the optimized two-color gradient
+                // codepath, which does not require fragment shader support
+                return true;
+            }
+
+            return super.isPaintValid(sg2d);
+        }
+    }
+
+    /********************** RadialGradientPaint support *************************/
+
+    private static class RadialGradient extends MultiGradient {
+        private RadialGradient() {}
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLRenderQueue.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLRenderQueue.java
new file mode 100644
index 00000000000..ea5c8f71ef0
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLRenderQueue.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.awt.util.ThreadGroupUtils;
+import sun.java2d.pipe.RenderBuffer;
+import sun.java2d.pipe.RenderQueue;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import static sun.java2d.pipe.BufferedOpCodes.DISPOSE_CONFIG;
+import static sun.java2d.pipe.BufferedOpCodes.SYNC;
+
+/**
+ * MTL-specific implementation of RenderQueue.  This class provides a
+ * single (daemon) thread that is responsible for periodically flushing
+ * the queue, thus ensuring that only one thread communicates with the native
+ * OpenGL libraries for the entire process.
+ */
+public class MTLRenderQueue extends RenderQueue {
+
+    private static MTLRenderQueue theInstance;
+    private final QueueFlusher flusher;
+
+    private MTLRenderQueue() {
+        /*
+         * The thread must be a member of a thread group
+         * which will not get GCed before VM exit.
+         */
+        flusher = AccessController.doPrivileged((PrivilegedAction<QueueFlusher>) QueueFlusher::new);
+    }
+
+    /**
+     * Returns the single MTLRenderQueue instance.  If it has not yet been
+     * initialized, this method will first construct the single instance
+     * before returning it.
+     */
+    public static synchronized MTLRenderQueue getInstance() {
+        if (theInstance == null) {
+            theInstance = new MTLRenderQueue();
+        }
+        return theInstance;
+    }
+
+    /**
+     * Flushes the single MTLRenderQueue instance synchronously.  If an
+     * MTLRenderQueue has not yet been instantiated, this method is a no-op.
+     * This method is useful in the case of Toolkit.sync(), in which we want
+     * to flush the MTL pipeline, but only if the MTL pipeline is currently
+     * enabled.  Since this class has few external dependencies, callers need
+     * not be concerned that calling this method will trigger initialization
+     * of the MTL pipeline and related classes.
+     */
+    public static void sync() {
+        if (theInstance != null) {
+            theInstance.lock();
+            try {
+                theInstance.ensureCapacity(4);
+                theInstance.getBuffer().putInt(SYNC);
+                theInstance.flushNow();
+            } finally {
+                theInstance.unlock();
+            }
+        }
+    }
+
+    /**
+     * Disposes the native memory associated with the given native
+     * graphics config info pointer on the single queue flushing thread.
+     */
+    public static void disposeGraphicsConfig(long pConfigInfo) {
+        MTLRenderQueue rq = getInstance();
+        rq.lock();
+        try {
+            // make sure we make the context associated with the given
+            // GraphicsConfig current before disposing the native resources
+            MTLContext.setScratchSurface(pConfigInfo);
+
+            RenderBuffer buf = rq.getBuffer();
+            rq.ensureCapacityAndAlignment(12, 4);
+            buf.putInt(DISPOSE_CONFIG);
+            buf.putLong(pConfigInfo);
+
+            // this call is expected to complete synchronously, so flush now
+            rq.flushNow();
+        } finally {
+            rq.unlock();
+        }
+    }
+
+    /**
+     * Returns true if the current thread is the MTL QueueFlusher thread.
+     */
+    public static boolean isQueueFlusherThread() {
+        return (Thread.currentThread() == getInstance().flusher.thread);
+    }
+
+
+    @Override
+    public void flushNow() {
+        // assert lock.isHeldByCurrentThread();
+        try {
+            flusher.flushNow();
+        } catch (Exception e) {
+            System.err.println("exception in flushNow:");
+            e.printStackTrace();
+        }
+    }
+
+    public void flushAndInvokeNow(Runnable r) {
+        // assert lock.isHeldByCurrentThread();
+        try {
+            flusher.flushAndInvokeNow(r);
+        } catch (Exception e) {
+            System.err.println("exception in flushAndInvokeNow:");
+            e.printStackTrace();
+        }
+    }
+
+    private native void flushBuffer(long buf, int limit);
+
+    private void flushBuffer() {
+        // assert lock.isHeldByCurrentThread();
+        int limit = buf.position();
+        if (limit > 0) {
+            // process the queue
+            flushBuffer(buf.getAddress(), limit);
+        }
+        // reset the buffer position
+        buf.clear();
+        // clear the set of references, since we no longer need them
+        refSet.clear();
+    }
+
+    private class QueueFlusher implements Runnable {
+        private boolean needsFlush;
+        private Runnable task;
+        private Error error;
+        private final Thread thread;
+
+        public QueueFlusher() {
+            String name = "Java2D Queue Flusher";
+            thread = new Thread(ThreadGroupUtils.getRootThreadGroup(),
+                    this, name, 0, false);
+            thread.setDaemon(true);
+            thread.setPriority(Thread.MAX_PRIORITY);
+            thread.start();
+        }
+
+        public synchronized void flushNow() {
+            // wake up the flusher
+            needsFlush = true;
+            notify();
+
+            // wait for flush to complete
+            while (needsFlush) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+
+            // re-throw any error that may have occurred during the flush
+            if (error != null) {
+                throw error;
+            }
+        }
+
+        public synchronized void flushAndInvokeNow(Runnable task) {
+            this.task = task;
+            flushNow();
+        }
+
+        public synchronized void run() {
+            boolean timedOut = false;
+            while (true) {
+                while (!needsFlush) {
+                    try {
+                        timedOut = false;
+                        /*
+                         * Wait until we're woken up with a flushNow() call,
+                         * or the timeout period elapses (so that we can
+                         * flush the queue periodically).
+                         */
+                        wait(100);
+                        /*
+                         * We will automatically flush the queue if the
+                         * following conditions apply:
+                         *   - the wait() timed out
+                         *   - we can lock the queue (without blocking)
+                         *   - there is something in the queue to flush
+                         * Otherwise, just continue (we'll flush eventually).
+                         */
+                        if (!needsFlush && (timedOut = tryLock())) {
+                            if (buf.position() > 0) {
+                                needsFlush = true;
+                            } else {
+                                unlock();
+                            }
+                        }
+                    } catch (InterruptedException e) {
+                    }
+                }
+                try {
+                    // reset the throwable state
+                    error = null;
+                    // flush the buffer now
+                    flushBuffer();
+                    // if there's a task, invoke that now as well
+                    if (task != null) {
+                        task.run();
+                    }
+                } catch (Error e) {
+                    error = e;
+                } catch (Exception x) {
+                    System.err.println("exception in QueueFlusher:");
+                    x.printStackTrace();
+                } finally {
+                    if (timedOut) {
+                        unlock();
+                    }
+                    task = null;
+                    // allow the waiting thread to continue
+                    needsFlush = false;
+                    notify();
+                }
+            }
+        }
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLRenderer.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLRenderer.java
new file mode 100644
index 00000000000..5ca368d446e
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLRenderer.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.InvalidPipeException;
+import sun.java2d.SunGraphics2D;
+import sun.java2d.loops.GraphicsPrimitive;
+import sun.java2d.pipe.BufferedRenderPipe;
+import sun.java2d.pipe.ParallelogramPipe;
+import sun.java2d.pipe.RenderQueue;
+import sun.java2d.pipe.SpanIterator;
+
+import java.awt.Transparency;
+import java.awt.geom.Path2D;
+
+import static sun.java2d.pipe.BufferedOpCodes.COPY_AREA;
+
+class MTLRenderer extends BufferedRenderPipe {
+
+    MTLRenderer(RenderQueue rq) {
+        super(rq);
+    }
+
+    @Override
+    protected void validateContext(SunGraphics2D sg2d) {
+        int ctxflags =
+                sg2d.paint.getTransparency() == Transparency.OPAQUE ?
+                        MTLContext.SRC_IS_OPAQUE : MTLContext.NO_CONTEXT_FLAGS;
+        MTLSurfaceData dstData;
+        try {
+            dstData = (MTLSurfaceData)sg2d.surfaceData;
+        } catch (ClassCastException e) {
+            throw new InvalidPipeException("wrong surface data type: " + sg2d.surfaceData);
+        }
+        MTLContext.validateContext(dstData, dstData,
+                sg2d.getCompClip(), sg2d.composite,
+                null, sg2d.paint, sg2d, ctxflags);
+    }
+
+    @Override
+    protected void validateContextAA(SunGraphics2D sg2d) {
+        int ctxflags = MTLContext.NO_CONTEXT_FLAGS;
+        MTLSurfaceData dstData;
+        try {
+            dstData = (MTLSurfaceData)sg2d.surfaceData;
+        } catch (ClassCastException e) {
+            throw new InvalidPipeException("wrong surface data type: " + sg2d.surfaceData);
+        }
+        MTLContext.validateContext(dstData, dstData,
+                sg2d.getCompClip(), sg2d.composite,
+                null, sg2d.paint, sg2d, ctxflags);
+    }
+
+    void copyArea(SunGraphics2D sg2d,
+                  int x, int y, int w, int h, int dx, int dy)
+    {
+        rq.lock();
+        try {
+            int ctxflags =
+                    sg2d.surfaceData.getTransparency() == Transparency.OPAQUE ?
+                            MTLContext.SRC_IS_OPAQUE : MTLContext.NO_CONTEXT_FLAGS;
+            MTLSurfaceData dstData;
+            try {
+                dstData = (MTLSurfaceData)sg2d.surfaceData;
+            } catch (ClassCastException e) {
+                throw new InvalidPipeException("wrong surface data type: " + sg2d.surfaceData);
+            }
+            MTLContext.validateContext(dstData, dstData,
+                    sg2d.getCompClip(), sg2d.composite,
+                    null, null, null, ctxflags);
+
+            rq.ensureCapacity(28);
+            buf.putInt(COPY_AREA);
+            buf.putInt(x).putInt(y).putInt(w).putInt(h);
+            buf.putInt(dx).putInt(dy);
+        } finally {
+            rq.unlock();
+        }
+    }
+
+    @Override
+    protected native void drawPoly(int[] xPoints, int[] yPoints,
+                                   int nPoints, boolean isClosed,
+                                   int transX, int transY);
+
+    MTLRenderer traceWrap() {
+        return new Tracer(this);
+    }
+
+    private class Tracer extends MTLRenderer {
+        private MTLRenderer mtlr;
+        Tracer(MTLRenderer mtlr) {
+            super(mtlr.rq);
+            this.mtlr = mtlr;
+        }
+        public ParallelogramPipe getAAParallelogramPipe() {
+            final ParallelogramPipe realpipe = mtlr.getAAParallelogramPipe();
+            return new ParallelogramPipe() {
+                public void fillParallelogram(SunGraphics2D sg2d,
+                                              double ux1, double uy1,
+                                              double ux2, double uy2,
+                                              double x, double y,
+                                              double dx1, double dy1,
+                                              double dx2, double dy2)
+                {
+                    GraphicsPrimitive.tracePrimitive("MTLFillAAParallelogram");
+                    realpipe.fillParallelogram(sg2d,
+                            ux1, uy1, ux2, uy2,
+                            x, y, dx1, dy1, dx2, dy2);
+                }
+                public void drawParallelogram(SunGraphics2D sg2d,
+                                              double ux1, double uy1,
+                                              double ux2, double uy2,
+                                              double x, double y,
+                                              double dx1, double dy1,
+                                              double dx2, double dy2,
+                                              double lw1, double lw2)
+                {
+                    GraphicsPrimitive.tracePrimitive("MTLDrawAAParallelogram");
+                    realpipe.drawParallelogram(sg2d,
+                            ux1, uy1, ux2, uy2,
+                            x, y, dx1, dy1, dx2, dy2,
+                            lw1, lw2);
+                }
+            };
+        }
+        protected void validateContext(SunGraphics2D sg2d) {
+            mtlr.validateContext(sg2d);
+        }
+        public void drawLine(SunGraphics2D sg2d,
+                             int x1, int y1, int x2, int y2)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLDrawLine");
+            mtlr.drawLine(sg2d, x1, y1, x2, y2);
+        }
+        public void drawRect(SunGraphics2D sg2d, int x, int y, int w, int h) {
+            GraphicsPrimitive.tracePrimitive("MTLDrawRect");
+            mtlr.drawRect(sg2d, x, y, w, h);
+        }
+        protected void drawPoly(SunGraphics2D sg2d,
+                                int[] xPoints, int[] yPoints,
+                                int nPoints, boolean isClosed)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLDrawPoly");
+            mtlr.drawPoly(sg2d, xPoints, yPoints, nPoints, isClosed);
+        }
+        public void fillRect(SunGraphics2D sg2d, int x, int y, int w, int h) {
+            GraphicsPrimitive.tracePrimitive("MTLFillRect");
+            mtlr.fillRect(sg2d, x, y, w, h);
+        }
+        protected void drawPath(SunGraphics2D sg2d,
+                                Path2D.Float p2df, int transx, int transy)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLDrawPath");
+            mtlr.drawPath(sg2d, p2df, transx, transy);
+        }
+        protected void fillPath(SunGraphics2D sg2d,
+                                Path2D.Float p2df, int transx, int transy)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLFillPath");
+            mtlr.fillPath(sg2d, p2df, transx, transy);
+        }
+        protected void fillSpans(SunGraphics2D sg2d, SpanIterator si,
+                                 int transx, int transy)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLFillSpans");
+            mtlr.fillSpans(sg2d, si, transx, transy);
+        }
+        public void fillParallelogram(SunGraphics2D sg2d,
+                                      double ux1, double uy1,
+                                      double ux2, double uy2,
+                                      double x, double y,
+                                      double dx1, double dy1,
+                                      double dx2, double dy2)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLFillParallelogram");
+            mtlr.fillParallelogram(sg2d,
+                    ux1, uy1, ux2, uy2,
+                    x, y, dx1, dy1, dx2, dy2);
+        }
+        public void drawParallelogram(SunGraphics2D sg2d,
+                                      double ux1, double uy1,
+                                      double ux2, double uy2,
+                                      double x, double y,
+                                      double dx1, double dy1,
+                                      double dx2, double dy2,
+                                      double lw1, double lw2)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLDrawParallelogram");
+            mtlr.drawParallelogram(sg2d,
+                    ux1, uy1, ux2, uy2,
+                    x, y, dx1, dy1, dx2, dy2, lw1, lw2);
+        }
+        public void copyArea(SunGraphics2D sg2d,
+                             int x, int y, int w, int h, int dx, int dy)
+        {
+            GraphicsPrimitive.tracePrimitive("MTLCopyArea");
+            mtlr.copyArea(sg2d, x, y, w, h, dx, dy);
+        }
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLSurfaceData.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLSurfaceData.java
new file mode 100644
index 00000000000..9204bffda6a
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLSurfaceData.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.awt.SunHints;
+import sun.awt.image.PixelConverter;
+import sun.java2d.SunGraphics2D;
+import sun.java2d.SurfaceData;
+import sun.java2d.SurfaceDataProxy;
+import sun.java2d.loops.CompositeType;
+import sun.java2d.loops.GraphicsPrimitive;
+import sun.java2d.loops.MaskFill;
+import sun.java2d.loops.SurfaceType;
+import sun.java2d.pipe.ParallelogramPipe;
+import sun.java2d.pipe.PixelToParallelogramConverter;
+import sun.java2d.pipe.RenderBuffer;
+import sun.java2d.pipe.TextPipe;
+import sun.java2d.pipe.hw.AccelSurface;
+
+import java.awt.AlphaComposite;
+import java.awt.Composite;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.Transparency;
+
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+
+import static sun.java2d.pipe.BufferedOpCodes.DISPOSE_SURFACE;
+import static sun.java2d.pipe.BufferedOpCodes.FLUSH_SURFACE;
+import static sun.java2d.pipe.hw.ContextCapabilities.CAPS_MULTITEXTURE;
+import static sun.java2d.pipe.hw.ContextCapabilities.CAPS_PS30;
+
+
+public abstract class MTLSurfaceData extends SurfaceData
+        implements AccelSurface {
+
+    /**
+     * Pixel formats
+     */
+    public static final int PF_INT_ARGB        = 0;
+    public static final int PF_INT_ARGB_PRE    = 1;
+    public static final int PF_INT_RGB         = 2;
+    public static final int PF_INT_RGBX        = 3;
+    public static final int PF_INT_BGR         = 4;
+    public static final int PF_INT_BGRX        = 5;
+    public static final int PF_USHORT_565_RGB  = 6;
+    public static final int PF_USHORT_555_RGB  = 7;
+    public static final int PF_USHORT_555_RGBX = 8;
+    public static final int PF_BYTE_GRAY       = 9;
+    public static final int PF_USHORT_GRAY     = 10;
+    public static final int PF_3BYTE_BGR       = 11;
+    /**
+     * SurfaceTypes
+     */
+
+    private static final String DESC_MTL_SURFACE = "MTL Surface";
+    private static final String DESC_MTL_SURFACE_RTT =
+            "MTL Surface (render-to-texture)";
+    private static final String DESC_MTL_TEXTURE = "MTL Texture";
+
+
+    static final SurfaceType MTLSurface =
+            SurfaceType.Any.deriveSubType(DESC_MTL_SURFACE,
+                    PixelConverter.ArgbPre.instance);
+    static final SurfaceType MTLSurfaceRTT =
+            MTLSurface.deriveSubType(DESC_MTL_SURFACE_RTT);
+    static final SurfaceType MTLTexture =
+            SurfaceType.Any.deriveSubType(DESC_MTL_TEXTURE);
+
+    protected static MTLRenderer mtlRenderPipe;
+    protected static PixelToParallelogramConverter mtlTxRenderPipe;
+    protected static ParallelogramPipe mtlAAPgramPipe;
+    protected static MTLTextRenderer mtlTextPipe;
+    protected static MTLDrawImage mtlImagePipe;
+
+    static {
+        if (!GraphicsEnvironment.isHeadless()) {
+            MTLRenderQueue rq = MTLRenderQueue.getInstance();
+            mtlImagePipe = new MTLDrawImage();
+            mtlTextPipe = new MTLTextRenderer(rq);
+            mtlRenderPipe = new MTLRenderer(rq);
+            if (GraphicsPrimitive.tracingEnabled()) {
+                mtlTextPipe = mtlTextPipe.traceWrap();
+                //The wrapped mtlRenderPipe will wrap the AA pipe as well...
+                //mtlAAPgramPipe = mtlRenderPipe.traceWrap();
+            }
+            mtlAAPgramPipe = mtlRenderPipe.getAAParallelogramPipe();
+            mtlTxRenderPipe =
+                    new PixelToParallelogramConverter(mtlRenderPipe,
+                            mtlRenderPipe,
+                            1.0, 0.25, true);
+
+            MTLBlitLoops.register();
+            MTLMaskFill.register();
+            MTLMaskBlit.register();
+        }
+    }
+
+    protected final int scale;
+    protected final int width;
+    protected final int height;
+    protected int type;
+    private MTLGraphicsConfig graphicsConfig;
+    // these fields are set from the native code when the surface is
+    // initialized
+    private int nativeWidth;
+    private int nativeHeight;
+
+    /**
+     * Returns the appropriate SurfaceType corresponding to the given Metal
+     * surface type constant (e.g. TEXTURE -> MTLTexture).
+     */
+    private static SurfaceType getCustomSurfaceType(int mtlType) {
+        switch (mtlType) {
+            case TEXTURE:
+                return MTLTexture;
+            case RT_TEXTURE:
+                return MTLSurfaceRTT;
+            default:
+                return MTLSurface;
+        }
+    }
+
+    private native void initOps(MTLGraphicsConfig gc, long pConfigInfo, long pPeerData, long layerPtr,
+                                int xoff, int yoff, boolean isOpaque);
+
+    private MTLSurfaceData(MTLLayer layer, MTLGraphicsConfig gc,
+                           ColorModel cm, int type, int width, int height)
+    {
+        super(getCustomSurfaceType(type), cm);
+        this.graphicsConfig = gc;
+        this.type = type;
+        setBlitProxyKey(gc.getProxyKey());
+
+        // TEXTURE shouldn't be scaled, it is used for managed BufferedImages.
+        scale = type == TEXTURE ? 1 : gc.getDevice().getScaleFactor();
+        this.width = width * scale;
+        this.height = height * scale;
+
+        long pConfigInfo = gc.getNativeConfigInfo();
+        long layerPtr = 0L;
+        boolean isOpaque = true;
+        if (layer != null) {
+            layerPtr = layer.getPointer();
+            isOpaque = layer.isOpaque();
+        }
+        initOps(gc, pConfigInfo, 0, layerPtr, 0, 0, isOpaque);
+    }
+
+    @Override
+    public GraphicsConfiguration getDeviceConfiguration() {
+        return graphicsConfig;
+    }
+
+    /**
+     * Creates a SurfaceData object representing the intermediate buffer
+     * between the Java2D flusher thread and the AppKit thread.
+     */
+    public static MTLLayerSurfaceData createData(MTLLayer layer) {
+        MTLGraphicsConfig gc = (MTLGraphicsConfig)layer.getGraphicsConfiguration();
+        Rectangle r = layer.getBounds();
+        return new MTLLayerSurfaceData(layer, gc, r.width, r.height);
+    }
+
+    /**
+     * Creates a SurfaceData object representing an off-screen buffer
+     */
+    public static MTLOffScreenSurfaceData createData(MTLGraphicsConfig gc,
+                                                     int width, int height,
+                                                     ColorModel cm, Image image,
+                                                     int type) {
+        return new MTLOffScreenSurfaceData(gc, width, height, image, cm,
+                type);
+    }
+
+    @Override
+    public double getDefaultScaleX() {
+        return scale;
+    }
+
+    @Override
+    public double getDefaultScaleY() {
+        return scale;
+    }
+
+    @Override
+    public Rectangle getBounds() {
+        return new Rectangle(width, height);
+    }
+
+    protected native void clearWindow();
+
+    protected native boolean initTexture(long pData, boolean isOpaque, int width, int height);
+
+    protected native boolean initRTexture(long pData, boolean isOpaque, int width, int height);
+
+    protected native boolean initFlipBackbuffer(long pData);
+
+    @Override
+    public SurfaceDataProxy makeProxyFor(SurfaceData srcData) {
+        return MTLSurfaceDataProxy.createProxy(srcData, graphicsConfig);
+    }
+
+    /**
+     * Note: This should only be called from the QFT under the AWT lock.
+     * This method is kept separate from the initSurface() method below just
+     * to keep the code a bit cleaner.
+     */
+    private void initSurfaceNow(int width, int height) {
+        boolean isOpaque = (getTransparency() == Transparency.OPAQUE);
+        boolean success = false;
+
+        switch (type) {
+            case TEXTURE:
+                success = initTexture(getNativeOps(), isOpaque, width, height);
+                break;
+
+            case RT_TEXTURE:
+                success = initRTexture(getNativeOps(), isOpaque, width, height);
+                break;
+
+            case FLIP_BACKBUFFER:
+                success = initFlipBackbuffer(getNativeOps());
+                break;
+
+            default:
+                break;
+        }
+
+        if (!success) {
+            throw new OutOfMemoryError("can't create offscreen surface");
+        }
+    }
+
+    /**
+     * Initializes the appropriate Metal offscreen surface based on the value
+     * of the type parameter.  If the surface creation fails for any reason,
+     * an OutOfMemoryError will be thrown.
+     */
+    protected void initSurface(final int width, final int height) {
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            switch (type) {
+                case TEXTURE:
+                case RT_TEXTURE:
+                    // need to make sure the context is current before
+                    // creating the texture
+                    MTLContext.setScratchSurface(graphicsConfig);
+                    break;
+                default:
+                    break;
+            }
+            rq.flushAndInvokeNow(new Runnable() {
+                public void run() {
+                    initSurfaceNow(width, height);
+                }
+            });
+        } finally {
+            rq.unlock();
+        }
+    }
+
+    /**
+     * Returns the MTLContext for the GraphicsConfig associated with this
+     * surface.
+     */
+    public final MTLContext getContext() {
+        return graphicsConfig.getContext();
+    }
+
+    /**
+     * Returns the MTLGraphicsConfig associated with this surface.
+     */
+    final MTLGraphicsConfig getMTLGraphicsConfig() {
+        return graphicsConfig;
+    }
+
+    /**
+     * Returns one of the surface type constants defined above.
+     */
+    public final int getType() {
+        return type;
+    }
+
+    /**
+     * For now, we can only render LCD text if:
+     *   - the fragment shader extension is available, and
+     *   - the source color is opaque, and
+     *   - blending is SrcOverNoEa or disabled
+     *   - and the destination is opaque
+     *
+     * Eventually, we could enhance the native MTL text rendering code
+     * and remove the above restrictions, but that would require significantly
+     * more code just to support a few uncommon cases.
+     */
+    public boolean canRenderLCDText(SunGraphics2D sg2d) {
+        return
+              sg2d.surfaceData.getTransparency() == Transparency.OPAQUE &&
+              sg2d.paintState <= SunGraphics2D.PAINT_OPAQUECOLOR &&
+             (sg2d.compositeState <= SunGraphics2D.COMP_ISCOPY ||
+             (sg2d.compositeState <= SunGraphics2D.COMP_ALPHA && canHandleComposite(sg2d.composite)));
+    }
+
+    private boolean canHandleComposite(Composite c) {
+        if (c instanceof AlphaComposite) {
+            AlphaComposite ac = (AlphaComposite)c;
+
+            return ac.getRule() == AlphaComposite.SRC_OVER && ac.getAlpha() >= 1f;
+        }
+        return false;
+    }
+
+    public void validatePipe(SunGraphics2D sg2d) {
+        TextPipe textpipe;
+        boolean validated = false;
+
+        // MTLTextRenderer handles both AA and non-AA text, but
+        // only works with the following modes:
+        // (Note: For LCD text we only enter this code path if
+        // canRenderLCDText() has already validated that the mode is
+        // CompositeType.SrcNoEa (opaque color), which will be subsumed
+        // by the CompositeType.SrcNoEa (any color) test below.)
+
+        if (/* CompositeType.SrcNoEa (any color) */
+                (sg2d.compositeState <= SunGraphics2D.COMP_ISCOPY &&
+                        sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR)         ||
+
+                        /* CompositeType.SrcOver (any color) */
+                        (sg2d.compositeState == SunGraphics2D.COMP_ALPHA   &&
+                                sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR &&
+                                (((AlphaComposite)sg2d.composite).getRule() ==
+                                        AlphaComposite.SRC_OVER))                                 ||
+
+                        /* CompositeType.Xor (any color) */
+                        (sg2d.compositeState == SunGraphics2D.COMP_XOR &&
+                                sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR))
+        {
+            textpipe = mtlTextPipe;
+        } else {
+            // do this to initialize textpipe correctly; we will attempt
+            // to override the non-text pipes below
+            super.validatePipe(sg2d);
+            textpipe = sg2d.textpipe;
+            validated = true;
+        }
+
+        PixelToParallelogramConverter txPipe = null;
+        MTLRenderer nonTxPipe = null;
+
+        if (sg2d.antialiasHint != SunHints.INTVAL_ANTIALIAS_ON) {
+            if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) {
+                if (sg2d.compositeState <= SunGraphics2D.COMP_XOR) {
+                    txPipe = mtlTxRenderPipe;
+                    nonTxPipe = mtlRenderPipe;
+                }
+            } else if (sg2d.compositeState <= SunGraphics2D.COMP_ALPHA) {
+                if (MTLPaints.isValid(sg2d)) {
+                    txPipe = mtlTxRenderPipe;
+                    nonTxPipe = mtlRenderPipe;
+                }
+                // custom paints handled by super.validatePipe() below
+            }
+        } else {
+            if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) {
+                if (graphicsConfig.isCapPresent(CAPS_PS30) &&
+                        (sg2d.imageComp == CompositeType.SrcOverNoEa ||
+                                sg2d.imageComp == CompositeType.SrcOver))
+                {
+                    if (!validated) {
+                        super.validatePipe(sg2d);
+                        validated = true;
+                    }
+                    PixelToParallelogramConverter aaConverter =
+                            new PixelToParallelogramConverter(sg2d.shapepipe,
+                                    mtlAAPgramPipe,
+                                    1.0/8.0, 0.499,
+                                    false);
+                    sg2d.drawpipe = aaConverter;
+                    sg2d.fillpipe = aaConverter;
+                    sg2d.shapepipe = aaConverter;
+                } else if (sg2d.compositeState == SunGraphics2D.COMP_XOR) {
+                    // install the solid pipes when AA and XOR are both enabled
+                    txPipe = mtlTxRenderPipe;
+                    nonTxPipe = mtlRenderPipe;
+                }
+            }
+            // other cases handled by super.validatePipe() below
+        }
+
+        if (txPipe != null) {
+            if (sg2d.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
+                sg2d.drawpipe = txPipe;
+                sg2d.fillpipe = txPipe;
+            } else if (sg2d.strokeState != SunGraphics2D.STROKE_THIN) {
+                sg2d.drawpipe = txPipe;
+                sg2d.fillpipe = nonTxPipe;
+            } else {
+                sg2d.drawpipe = nonTxPipe;
+                sg2d.fillpipe = nonTxPipe;
+            }
+            // Note that we use the transforming pipe here because it
+            // will examine the shape and possibly perform an optimized
+            // operation if it can be simplified.  The simplifications
+            // will be valid for all STROKE and TRANSFORM types.
+            sg2d.shapepipe = txPipe;
+        } else {
+            if (!validated) {
+                super.validatePipe(sg2d);
+            }
+        }
+
+        // install the text pipe based on our earlier decision
+        sg2d.textpipe = textpipe;
+
+        // always override the image pipe with the specialized MTL pipe
+        sg2d.imagepipe = mtlImagePipe;
+    }
+
+    @Override
+    protected MaskFill getMaskFill(SunGraphics2D sg2d) {
+        if (sg2d.paintState > SunGraphics2D.PAINT_ALPHACOLOR) {
+            /*
+             * We can only accelerate non-Color MaskFill operations if
+             * all of the following conditions hold true:
+             *   - there is an implementation for the given paintState
+             *   - the current Paint can be accelerated for this destination
+             *   - multitexturing is available (since we need to modulate
+             *     the alpha mask texture with the paint texture)
+             *
+             * In all other cases, we return null, in which case the
+             * validation code will choose a more general software-based loop.
+             */
+            if (!MTLPaints.isValid(sg2d) ||
+                    !graphicsConfig.isCapPresent(CAPS_MULTITEXTURE))
+            {
+                return null;
+            }
+        }
+        return super.getMaskFill(sg2d);
+    }
+
+    public void flush() {
+        invalidate();
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            // make sure we have a current context before
+            // disposing the native resources (e.g. texture object)
+            MTLContext.setScratchSurface(graphicsConfig);
+
+            RenderBuffer buf = rq.getBuffer();
+            rq.ensureCapacityAndAlignment(12, 4);
+            buf.putInt(FLUSH_SURFACE);
+            buf.putLong(getNativeOps());
+
+            // this call is expected to complete synchronously, so flush now
+            rq.flushNow();
+        } finally {
+            rq.unlock();
+        }
+    }
+
+    public boolean isOnScreen() {
+        return false;
+    }
+
+    private native long getMTLTexturePointer(long pData);
+
+    /**
+     * Returns native resource of specified {@code resType} associated with
+     * this surface.
+     *
+     * Specifically, for {@code MTLSurfaceData} this method returns the
+     * the following:
+     * <pre>
+     * TEXTURE              - texture id
+     * </pre>
+     *
+     * Note: the resource returned by this method is only valid on the rendering
+     * thread.
+     *
+     * @return native resource of specified type or 0L if
+     * such resource doesn't exist or can not be retrieved.
+     * @see AccelSurface#getNativeResource
+     */
+    public long getNativeResource(int resType) {
+        if (resType == TEXTURE) {
+            return getMTLTexturePointer(getNativeOps());
+        }
+        return 0L;
+    }
+
+    public Raster getRaster(int x, int y, int w, int h) {
+        throw new InternalError("not implemented yet");
+    }
+
+    @Override
+    public boolean copyArea(SunGraphics2D sg2d, int x, int y, int w, int h,
+                            int dx, int dy) {
+        if (sg2d.compositeState >= SunGraphics2D.COMP_XOR) {
+            return false;
+        }
+        mtlRenderPipe.copyArea(sg2d, x, y, w, h, dx, dy);
+        return true;
+    }
+
+    public Rectangle getNativeBounds() {
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            return new Rectangle(nativeWidth, nativeHeight);
+        } finally {
+            rq.unlock();
+        }
+    }
+
+    /**
+     * A surface which implements an intermediate buffer between
+     * the Java2D flusher thread and the AppKit thread.
+     *
+     * This surface serves as a buffer attached to a MTLLayer and
+     * the layer redirects all painting to the buffer's graphics.
+     */
+    public static class MTLLayerSurfaceData extends MTLSurfaceData {
+
+        private final MTLLayer layer;
+
+        private MTLLayerSurfaceData(MTLLayer layer, MTLGraphicsConfig gc,
+                                   int width, int height) {
+            super(layer, gc, gc.getColorModel(), RT_TEXTURE, width, height);
+            this.layer = layer;
+            initSurface(this.width, this.height);
+        }
+
+        @Override
+        public SurfaceData getReplacement() {
+            return layer.getSurfaceData();
+        }
+
+        @Override
+        public boolean isOnScreen() {
+            return true;
+        }
+
+        @Override
+        public Object getDestination() {
+            return layer.getDestination();
+        }
+
+        @Override
+        public int getTransparency() {
+            return layer.getTransparency();
+        }
+
+        @Override
+        public void invalidate() {
+            super.invalidate();
+            clearWindow();
+        }
+    }
+
+    /**
+     * SurfaceData object representing an off-screen buffer
+     */
+    public static class MTLOffScreenSurfaceData extends MTLSurfaceData {
+        private final Image offscreenImage;
+
+        public MTLOffScreenSurfaceData(MTLGraphicsConfig gc, int width,
+                                       int height, Image image,
+                                       ColorModel cm, int type) {
+            super(null, gc, cm, type, width, height);
+            offscreenImage = image;
+            initSurface(this.width, this.height);
+        }
+
+        @Override
+        public SurfaceData getReplacement() {
+            return restoreContents(offscreenImage);
+        }
+
+        /**
+         * Returns destination Image associated with this SurfaceData.
+         */
+        @Override
+        public Object getDestination() {
+            return offscreenImage;
+        }
+    }
+
+
+    /**
+     * Disposes the native resources associated with the given MTLSurfaceData
+     * (referenced by the pData parameter).  This method is invoked from
+     * the native Dispose() method from the Disposer thread when the
+     * Java-level MTLSurfaceData object is about to go away.
+     */
+     public static void dispose(long pData, MTLGraphicsConfig gc) {
+        MTLRenderQueue rq = MTLRenderQueue.getInstance();
+        rq.lock();
+        try {
+            // make sure we have a current context before
+            // disposing the native resources (e.g. texture object)
+            MTLContext.setScratchSurface(gc);
+            RenderBuffer buf = rq.getBuffer();
+            rq.ensureCapacityAndAlignment(12, 4);
+            buf.putInt(DISPOSE_SURFACE);
+            buf.putLong(pData);
+
+            // this call is expected to complete synchronously, so flush now
+            rq.flushNow();
+        } finally {
+            rq.unlock();
+        }
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLSurfaceDataProxy.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLSurfaceDataProxy.java
new file mode 100644
index 00000000000..5d694c56d25
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLSurfaceDataProxy.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.java2d.SurfaceData;
+import sun.java2d.SurfaceDataProxy;
+import sun.java2d.loops.CompositeType;
+
+import java.awt.Color;
+import java.awt.Transparency;
+
+/**
+ * The proxy class contains the logic for when to replace a
+ * SurfaceData with a cached MTL Texture and the code to create
+ * the accelerated surfaces.
+ */
+public class MTLSurfaceDataProxy extends SurfaceDataProxy {
+    public static SurfaceDataProxy createProxy(SurfaceData srcData,
+                                               MTLGraphicsConfig dstConfig)
+    {
+        if (srcData instanceof MTLSurfaceData) {
+            // srcData must be a VolatileImage which either matches
+            // our pixel format or not - either way we do not cache it...
+            return UNCACHED;
+        }
+
+        return new MTLSurfaceDataProxy(dstConfig, srcData.getTransparency());
+    }
+
+    MTLGraphicsConfig mtlgc;
+    int transparency;
+
+    public MTLSurfaceDataProxy(MTLGraphicsConfig mtlgc, int transparency) {
+        this.mtlgc = mtlgc;
+        this.transparency = transparency;
+    }
+
+    @Override
+    public SurfaceData validateSurfaceData(SurfaceData srcData,
+                                           SurfaceData cachedData,
+                                           int w, int h)
+    {
+        if (cachedData == null) {
+            try {
+                cachedData = mtlgc.createManagedSurface(w, h, transparency);
+            } catch (OutOfMemoryError er) {
+                return null;
+            }
+        }
+        return cachedData;
+    }
+
+    @Override
+    public boolean isSupportedOperation(SurfaceData srcData,
+                                        int txtype,
+                                        CompositeType comp,
+                                        Color bgColor)
+    {
+        return comp.isDerivedFrom(CompositeType.AnyAlpha) &&
+                (bgColor == null || transparency == Transparency.OPAQUE);
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLTextRenderer.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLTextRenderer.java
new file mode 100644
index 00000000000..b1284e0d552
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLTextRenderer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.font.GlyphList;
+import sun.java2d.SunGraphics2D;
+import sun.java2d.loops.GraphicsPrimitive;
+import sun.java2d.pipe.BufferedTextPipe;
+import sun.java2d.pipe.RenderQueue;
+
+import java.awt.Composite;
+
+class MTLTextRenderer extends BufferedTextPipe {
+
+    MTLTextRenderer(RenderQueue rq) {
+        super(rq);
+    }
+
+    @Override
+    protected native void drawGlyphList(int numGlyphs, boolean usePositions,
+                                        boolean subPixPos, boolean rgbOrder,
+                                        int lcdContrast,
+                                        float glOrigX, float glOrigY,
+                                        long[] images, float[] positions);
+
+    @Override
+    protected void validateContext(SunGraphics2D sg2d, Composite comp) {
+        // assert rq.lock.isHeldByCurrentThread();
+        MTLSurfaceData mtlDst = (MTLSurfaceData)sg2d.surfaceData;
+        MTLContext.validateContext(mtlDst, mtlDst,
+                sg2d.getCompClip(), comp,
+                null, sg2d.paint, sg2d,
+                MTLContext.NO_CONTEXT_FLAGS);
+    }
+
+    MTLTextRenderer traceWrap() {
+        return new Tracer(this);
+    }
+
+    private static class Tracer extends MTLTextRenderer {
+        Tracer(MTLTextRenderer mtltr) {
+            super(mtltr.rq);
+        }
+        protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) {
+            GraphicsPrimitive.tracePrimitive("MTLDrawGlyphs");
+            super.drawGlyphList(sg2d, gl);
+        }
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/metal/MTLVolatileSurfaceManager.java b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLVolatileSurfaceManager.java
new file mode 100644
index 00000000000..4d337be1f61
--- /dev/null
+++ b/src/java.desktop/macosx/classes/sun/java2d/metal/MTLVolatileSurfaceManager.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.java2d.metal;
+
+import sun.awt.image.SunVolatileImage;
+import sun.awt.image.VolatileSurfaceManager;
+import sun.java2d.SurfaceData;
+
+import java.awt.GraphicsConfiguration;
+import java.awt.Transparency;
+import java.awt.image.ColorModel;
+import sun.java2d.pipe.hw.AccelSurface;
+
+public class MTLVolatileSurfaceManager extends VolatileSurfaceManager {
+
+    private final boolean accelerationEnabled;
+
+    public MTLVolatileSurfaceManager(SunVolatileImage vImg, Object context) {
+        super(vImg, context);
+
+        /*
+         * We will attempt to accelerate this image only
+         * if the image is not bitmask
+         */
+        int transparency = vImg.getTransparency();
+        accelerationEnabled = transparency != Transparency.BITMASK;
+    }
+
+    protected boolean isAccelerationEnabled() {
+        return accelerationEnabled;
+    }
+
+    /**
+     * Create a SurfaceData object (or init the backbuffer
+     * of an existing window if this is a double buffered GraphicsConfig)
+     */
+    protected SurfaceData initAcceleratedSurface() {
+        try {
+            MTLGraphicsConfig gc =
+                (MTLGraphicsConfig)vImg.getGraphicsConfig();
+            ColorModel cm = gc.getColorModel(vImg.getTransparency());
+            int type = vImg.getForcedAccelSurfaceType();
+            // if acceleration type is forced (type != UNDEFINED) then
+            // use the forced type, otherwise choose RT_TEXTURE
+            if (type == AccelSurface.UNDEFINED) {
+                type = AccelSurface.RT_TEXTURE;
+            }
+            return MTLSurfaceData.createData(gc,
+                                             vImg.getWidth(),
+                                             vImg.getHeight(),
+                                             cm, vImg, type);
+        } catch (NullPointerException | OutOfMemoryError ignored) {
+            return null;
+        }
+    }
+
+    @Override
+    protected boolean isConfigValid(GraphicsConfiguration gc) {
+        return ((gc == null) || (gc == vImg.getGraphicsConfig()));
+    }
+
+    @Override
+    public void initContents() {
+        if (vImg.getForcedAccelSurfaceType() != AccelSurface.TEXTURE) {
+            super.initContents();
+        }
+    }
+}
diff --git a/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLGraphicsConfig.java b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLGraphicsConfig.java
index a73d13dee57..bea5963383e 100644
--- a/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLGraphicsConfig.java
+++ b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLGraphicsConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -55,6 +55,7 @@ import sun.java2d.pipe.hw.AccelSurface;
 import sun.java2d.pipe.hw.AccelTypedVolatileImage;
 import sun.java2d.pipe.hw.ContextCapabilities;
 import sun.lwawt.LWComponentPeer;
+import sun.lwawt.macosx.CFRetainedResource;
 
 import static sun.java2d.opengl.OGLContext.OGLContextCaps.CAPS_DOUBLEBUFFERED;
 import static sun.java2d.opengl.OGLContext.OGLContextCaps.CAPS_EXT_FBOBJECT;
@@ -248,8 +249,8 @@ public final class CGLGraphicsConfig extends CGraphicsConfig
     }
 
     @Override
-    public SurfaceData createSurfaceData(CGLLayer layer) {
-        return CGLSurfaceData.createData(layer);
+    public SurfaceData createSurfaceData(CFRetainedResource layer) {
+        return CGLSurfaceData.createData((CGLLayer) layer);
     }
 
     @Override
diff --git a/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java b/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java
index 669433fac61..71845da22b0 100644
--- a/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java
+++ b/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -68,14 +68,17 @@ import javax.swing.SwingUtilities;
 
 import com.sun.java.swing.SwingUtilities3;
 import sun.awt.AWTAccessor;
+import sun.awt.CGraphicsDevice;
 import sun.awt.PaintEventDispatcher;
 import sun.awt.RepaintArea;
 import sun.awt.SunToolkit;
 import sun.awt.event.IgnorePaintEvent;
 import sun.awt.image.SunVolatileImage;
 import sun.java2d.SunGraphics2D;
+import sun.java2d.metal.MTLRenderQueue;
 import sun.java2d.opengl.OGLRenderQueue;
 import sun.java2d.pipe.Region;
+import sun.java2d.pipe.RenderQueue;
 import sun.util.logging.PlatformLogger;
 
 public abstract class LWComponentPeer<T extends Component, D extends JComponent>
@@ -1414,7 +1417,8 @@ public abstract class LWComponentPeer<T extends Component, D extends JComponent>
     }
 
     protected static final void flushOnscreenGraphics(){
-        final OGLRenderQueue rq = OGLRenderQueue.getInstance();
+        RenderQueue rq =  CGraphicsDevice.usingMetalPipeline() ?
+                MTLRenderQueue.getInstance() : OGLRenderQueue.getInstance();
         rq.lock();
         try {
             rq.flushNow();
diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformEmbeddedFrame.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformEmbeddedFrame.java
index 822c47d4984..a65cb25f635 100644
--- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformEmbeddedFrame.java
+++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformEmbeddedFrame.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -28,12 +28,16 @@ package sun.lwawt.macosx;
 import java.awt.*;
 import java.awt.event.FocusEvent;
 
+import sun.awt.CGraphicsDevice;
 import sun.java2d.SurfaceData;
+import sun.java2d.metal.MTLLayer;
 import sun.java2d.opengl.CGLLayer;
 import sun.lwawt.LWWindowPeer;
 import sun.lwawt.PlatformWindow;
+import sun.lwawt.macosx.CFRetainedResource;
 import sun.util.logging.PlatformLogger;
 
+
 /*
  * Provides a lightweight implementation of the EmbeddedFrame.
  */
@@ -42,7 +46,7 @@ public class CPlatformEmbeddedFrame implements PlatformWindow {
     private static final PlatformLogger focusLogger = PlatformLogger.getLogger(
             "sun.lwawt.macosx.focus.CPlatformEmbeddedFrame");
 
-    private CGLLayer windowLayer;
+    private CFRetainedResource windowLayer;
     private LWWindowPeer peer;
     private CEmbeddedFrame target;
 
@@ -52,7 +56,11 @@ public class CPlatformEmbeddedFrame implements PlatformWindow {
     @Override // PlatformWindow
     public void initialize(Window target, final LWWindowPeer peer, PlatformWindow owner) {
         this.peer = peer;
-        this.windowLayer = new CGLLayer(peer);
+        if (CGraphicsDevice.usingMetalPipeline()) {
+            this.windowLayer = new MTLLayer(peer);
+        } else {
+            this.windowLayer = new CGLLayer(peer);
+        }
         this.target = (CEmbeddedFrame)target;
     }
 
@@ -63,12 +71,20 @@ public class CPlatformEmbeddedFrame implements PlatformWindow {
 
     @Override
     public long getLayerPtr() {
-        return windowLayer.getPointer();
+        if (CGraphicsDevice.usingMetalPipeline()) {
+            return ((MTLLayer)windowLayer).getPointer();
+        } else {
+            return ((CGLLayer)windowLayer).getPointer();
+        }
     }
 
     @Override
     public void dispose() {
-        windowLayer.dispose();
+        if (CGraphicsDevice.usingMetalPipeline()) {
+            ((MTLLayer)windowLayer).dispose();
+        } else {
+            ((CGLLayer)windowLayer).dispose();
+        }
     }
 
     @Override
@@ -99,12 +115,20 @@ public class CPlatformEmbeddedFrame implements PlatformWindow {
 
     @Override
     public SurfaceData getScreenSurface() {
-        return windowLayer.getSurfaceData();
+        if ( CGraphicsDevice.usingMetalPipeline()) {
+            return ((MTLLayer)windowLayer).getSurfaceData();
+        } else {
+            return ((CGLLayer)windowLayer).getSurfaceData();
+        }
     }
 
     @Override
     public SurfaceData replaceSurfaceData() {
-        return windowLayer.replaceSurfaceData();
+        if (CGraphicsDevice.usingMetalPipeline()) {
+            return ((MTLLayer)windowLayer).replaceSurfaceData();
+        } else {
+            return ((CGLLayer)windowLayer).replaceSurfaceData();
+        }
     }
 
     @Override
diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformView.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformView.java
index 472ff164a65..039e4f3f664 100644
--- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformView.java
+++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -35,9 +35,12 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 import sun.awt.CGraphicsEnvironment;
+import sun.awt.CGraphicsDevice;
+import sun.java2d.metal.MTLLayer;
+import sun.lwawt.LWWindowPeer;
+
 import sun.java2d.SurfaceData;
 import sun.java2d.opengl.CGLLayer;
-import sun.lwawt.LWWindowPeer;
 
 public class CPlatformView extends CFRetainedResource {
     private native long nativeCreateView(int x, int y, int width, int height, long windowLayerPtr);
@@ -48,7 +51,7 @@ public class CPlatformView extends CFRetainedResource {
 
     private LWWindowPeer peer;
     private SurfaceData surfaceData;
-    private CGLLayer windowLayer;
+    private CFRetainedResource windowLayer;
     private CPlatformResponder responder;
 
     public CPlatformView() {
@@ -58,7 +61,7 @@ public class CPlatformView extends CFRetainedResource {
     public void initialize(LWWindowPeer peer, CPlatformResponder responder) {
         initializeBase(peer, responder);
 
-        this.windowLayer = createCGLayer();
+        this.windowLayer = CGraphicsDevice.usingMetalPipeline()? createMTLLayer() : createCGLayer();
         setPtr(nativeCreateView(0, 0, 0, 0, getWindowLayerPtr()));
     }
 
@@ -66,6 +69,11 @@ public class CPlatformView extends CFRetainedResource {
         return new CGLLayer(peer);
     }
 
+    public MTLLayer createMTLLayer() {
+        return new MTLLayer(peer);
+    }
+
+
     protected void initializeBase(LWWindowPeer peer, CPlatformResponder responder) {
         this.peer = peer;
         this.responder = responder;
@@ -96,7 +104,10 @@ public class CPlatformView extends CFRetainedResource {
     // PAINTING METHODS
     // ----------------------------------------------------------------------
     public SurfaceData replaceSurfaceData() {
-        surfaceData = windowLayer.replaceSurfaceData();
+        surfaceData = (CGraphicsDevice.usingMetalPipeline()) ?
+                    ((MTLLayer)windowLayer).replaceSurfaceData() :
+                    ((CGLLayer)windowLayer).replaceSurfaceData()
+        ;
         return surfaceData;
     }
 
@@ -111,7 +122,9 @@ public class CPlatformView extends CFRetainedResource {
     }
 
     public long getWindowLayerPtr() {
-        return windowLayer.getPointer();
+        return CGraphicsDevice.usingMetalPipeline() ?
+                ((MTLLayer)windowLayer).getPointer() :
+                ((CGLLayer)windowLayer).getPointer();
     }
 
     public void setAutoResizable(boolean toResize) {
diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CWarningWindow.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CWarningWindow.java
index 2b3d699fcd7..35d726f8f75 100644
--- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CWarningWindow.java
+++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CWarningWindow.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@ import sun.awt.AWTAccessor;
 import sun.awt.IconInfo;
 import sun.java2d.SunGraphics2D;
 import sun.java2d.SurfaceData;
+import sun.java2d.metal.MTLLayer;
 import sun.java2d.opengl.CGLLayer;
 import sun.lwawt.LWWindowPeer;
 import sun.lwawt.PlatformEventNotifier;
@@ -300,6 +301,23 @@ public final class CWarningWindow extends CPlatformWindow
                     }
                 };
             }
+            public MTLLayer createMTLLayer() {
+                return new MTLLayer(null) {
+                    public Rectangle getBounds() {
+                        return CWarningWindow.this.getBounds();
+                    }
+
+                    public GraphicsConfiguration getGraphicsConfiguration() {
+                        LWWindowPeer peer = ownerPeer.get();
+                        return peer.getGraphicsConfiguration();
+                    }
+
+                    public boolean isOpaque() {
+                        return false;
+                    }
+                };
+            }
+
         };
     }
 
diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java
index 6a3fb30128b..ea0fd33e8bf 100644
--- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java
+++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -109,6 +109,7 @@ import sun.awt.SunToolkit;
 import sun.awt.datatransfer.DataTransferer;
 import sun.awt.dnd.SunDragSourceContextPeer;
 import sun.awt.util.ThreadGroupUtils;
+import sun.java2d.metal.MTLRenderQueue;
 import sun.java2d.opengl.OGLRenderQueue;
 import sun.lwawt.LWComponentPeer;
 import sun.lwawt.LWCursorManager;
@@ -500,8 +501,12 @@ public final class LWCToolkit extends LWToolkit {
 
     @Override
     public void sync() {
-        // flush the OGL pipeline (this is a no-op if OGL is not enabled)
-        OGLRenderQueue.sync();
+        // flush the rendering pipeline
+        if (CGraphicsDevice.usingMetalPipeline()) {
+            MTLRenderQueue.sync();
+        } else {
+            OGLRenderQueue.sync();
+        }
         // setNeedsDisplay() selector was sent to the appropriate CALayer so now
         // we have to flush the native selectors queue.
         flushNativeSelectors();
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTSurfaceLayers.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTSurfaceLayers.m
index 1a5dcb03b99..1cc4abfaaad 100644
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTSurfaceLayers.m
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTSurfaceLayers.m
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@
 #import "JNIUtilities.h"
 
 #import <QuartzCore/CATransaction.h>
+#import <QuartzCore/CAMetalLayer.h>
 
 @implementation AWTSurfaceLayers
 
@@ -67,10 +68,11 @@
     }
 }
 
-// Updates back buffer size of the layer if it's an OpenGL layer
-// including all OpenGL sublayers
+// Updates back buffer size of the layer if it's an OpenGL/Metal layer
+// including all OpenGL/Metal sublayers
 + (void) repaintLayersRecursively:(CALayer*)aLayer {
-    if ([aLayer isKindOfClass:[CAOpenGLLayer class]]) {
+    if ([aLayer isKindOfClass:[CAOpenGLLayer class]] ||
+        [aLayer isKindOfClass:[CAMetalLayer class]]) {
         [aLayer setNeedsDisplay];
     }
     for(CALayer *child in aLayer.sublayers) {
@@ -92,7 +94,7 @@
 
 /*
  * Class:     sun_lwawt_macosx_CPlatformComponent
- * Method:    nativeCreateLayer
+ * Method:    nativeCreateComponent
  * Signature: ()J
  */
 JNIEXPORT jlong JNICALL
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.h
new file mode 100644
index 00000000000..bec085f93db
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef EncoderManager_h_Included
+#define EncoderManager_h_Included
+
+#import <Metal/Metal.h>
+
+#include "RenderOptions.h"
+
+@class MTLContex;
+
+/**
+ * The EncoderManager class used to obtain MTLRenderCommandEncoder (or MTLBlitCommandEncoder) corresponding
+ * to the current state of MTLContext.
+ *
+ * Due to performance issues (creation of MTLRenderCommandEncoder isn't cheap), each getXXXEncoder invocation
+ * updates properties of common (cached) encoder and returns this encoder.
+ *
+ * Base method getEncoder does the following:
+ *  1. Checks whether common encoder must be closed and recreated (some of encoder properties is 'persistent',
+ *  for example destination, stencil, or any other property of MTLRenderPassDescriptor)
+ *  2. Updates 'mutable' properties encoder: pipelineState (with corresponding buffers), clip, transform, e.t.c. To avoid
+ *  unnecessary calls of [encoder setXXX] this manager compares requested state with cached one.
+ */
+@interface EncoderManager : NSObject
+- (id _Nonnull)init;
+- (void)dealloc;
+
+- (void)setContext:(MTLContex * _Nonnull)mtlc;
+
+// returns encoder that renders/fills geometry with current paint and composite
+- (id<MTLRenderCommandEncoder> _Nonnull)getRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps;
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getAARenderEncoder:(const BMTLSDOps * _Nonnull)dstOps;
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getRenderEncoder:(id<MTLTexture> _Nonnull)dest
+                                             isDstOpaque:(bool)isOpaque;
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getAAShaderRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps;
+
+// returns encoder that renders/fills geometry with current composite and with given texture
+// (user must call [encoder setFragmentTexture] before any rendering)
+- (id<MTLRenderCommandEncoder> _Nonnull)getTextureEncoder:(const BMTLSDOps * _Nonnull)dstOps
+                                      isSrcOpaque:(bool)isSrcOpaque;
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
+                                               isSrcOpaque:(bool)isSrcOpaque
+                                               isDstOpaque:(bool)isDstOpaque;
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getLCDEncoder:(id<MTLTexture> _Nonnull)dest
+                                               isSrcOpaque:(bool)isSrcOpaque
+                                               isDstOpaque:(bool)isDstOpaque;
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getTextureEncoder:(id<MTLTexture> _Nonnull)dest
+                                      isSrcOpaque:(bool)isSrcOpaque
+                                      isDstOpaque:(bool)isDstOpaque
+                                    interpolation:(int)interpolation;
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getTextureEncoder:(id<MTLTexture> _Nonnull)dest
+                                              isSrcOpaque:(bool)isSrcOpaque
+                                              isDstOpaque:(bool)isDstOpaque
+                                            interpolation:(int)interpolation
+                                                     isAA:(jboolean)isAA;
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getTextEncoder:(const BMTLSDOps * _Nonnull)dstOps
+                                      isSrcOpaque:(bool)isSrcOpaque;
+
+// Base method to obtain any MTLRenderCommandEncoder
+- (id<MTLRenderCommandEncoder> _Nonnull) getEncoder:(id<MTLTexture> _Nonnull)dest
+                                       isDestOpaque:(jboolean)isDestOpaque
+                                      renderOptions:(const RenderOptions * _Nonnull)renderOptions;
+
+- (id<MTLBlitCommandEncoder> _Nonnull)createBlitEncoder;
+
+- (void)endEncoder;
+@end
+
+#endif // EncoderManager_h_Included
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.m
new file mode 100644
index 00000000000..7bd13b880f5
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.m
@@ -0,0 +1,465 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "EncoderManager.h"
+#include "MTLContext.h"
+#include "sun_java2d_SunGraphics2D.h"
+#import "common.h"
+
+// NOTE: uncomment to disable comparing cached encoder states with requested (for debugging)
+// #define ALWAYS_UPDATE_ENCODER_STATES
+
+const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
+
+// Internal utility class that represents the set of 'mutable' encoder properties
+@interface EncoderStates : NSObject
+@property (readonly) MTLClip * clip;
+
+- (id)init;
+- (void)dealloc;
+
+- (void)reset:(id<MTLTexture>)destination
+           isDstOpaque:(jboolean)isDstOpaque
+    isDstPremultiplied:(jboolean)isDstPremultiplied
+                  isAA:(jboolean)isAA
+                  isText:(jboolean)isText
+                  isLCD:(jboolean)isLCD;
+
+- (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
+              context:(MTLContext *)mtlc
+        renderOptions:(const RenderOptions *)renderOptions
+          forceUpdate:(jboolean)forceUpdate;
+@property (assign) jboolean aa;
+@property (assign) jboolean text;
+@property (assign) jboolean lcd;
+@property (assign) jboolean aaShader;
+@property (retain) MTLPaint* paint;
+@end
+
+@implementation EncoderStates {
+    MTLPipelineStatesStorage * _pipelineStateStorage;
+    id<MTLDevice> _device;
+
+    // Persistent encoder properties
+    id<MTLTexture> _destination;
+    SurfaceRasterFlags _dstFlags;
+
+    jboolean _isAA;
+    jboolean _isText;
+    jboolean _isLCD;
+    jboolean _isAAShader;
+
+    //
+    // Cached 'mutable' states of encoder
+    //
+
+    // Composite rule and source raster flags (it affects the CAD-multipliers (of pipelineState))
+    MTLComposite * _composite;
+    SurfaceRasterFlags _srcFlags;
+
+    // Paint mode (it affects shaders (of pipelineState) and corresponding buffers)
+    MTLPaint * _paint;
+
+    // If true, indicates that encoder is used for texture drawing (user must do [encoder setFragmentTexture:] before drawing)
+    jboolean _isTexture;
+    int _interpolationMode;
+
+    // Clip rect or stencil
+    MTLClip * _clip;
+
+    // Transform (affects transformation inside vertex shader)
+    MTLTransform * _transform;
+}
+@synthesize aa = _isAA;
+@synthesize text = _isText;
+@synthesize lcd = _isLCD;
+@synthesize aaShader = _isAAShader;
+@synthesize paint = _paint;
+
+- (id)init {
+    self = [super init];
+    if (self) {
+        _destination = nil;
+        _composite = [[MTLComposite alloc] init];
+        _paint = [[MTLPaint alloc] init];
+        _transform = [[MTLTransform alloc] init];
+        _clip = [[MTLClip alloc] init];
+    }
+    return self;
+}
+
+- (void)dealloc {
+    [_composite release];
+    [_paint release];
+    [_transform release];
+    [super dealloc];
+}
+
+- (void)setContext:(MTLContext * _Nonnull)mtlc {
+    self->_pipelineStateStorage = mtlc.pipelineStateStorage;
+    self->_device = mtlc.device;
+}
+
+- (void)reset:(id<MTLTexture>)destination
+           isDstOpaque:(jboolean)isDstOpaque
+    isDstPremultiplied:(jboolean)isDstPremultiplied
+                  isAA:(jboolean)isAA
+                  isText:(jboolean)isText
+                  isLCD:(jboolean)isLCD {
+    _destination = destination;
+    _dstFlags.isOpaque = isDstOpaque;
+    _dstFlags.isPremultiplied = isDstPremultiplied;
+    _isAA = isAA;
+    _isText = isText;
+    _isLCD = isLCD;
+    // NOTE: probably it's better to invalidate/reset all cached states now
+}
+
+- (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
+              context:(MTLContext *)mtlc
+        renderOptions:(const RenderOptions *)renderOptions
+          forceUpdate:(jboolean)forceUpdate
+{
+    // 1. Process special case for stencil mask generation
+    if (mtlc.clip.stencilMaskGenerationInProgress == JNI_TRUE) {
+        // use separate pipeline state for stencil generation
+        if (forceUpdate || (_clip.stencilMaskGenerationInProgress != JNI_TRUE)) {
+            [_clip copyFrom:mtlc.clip];
+            [_clip setMaskGenerationPipelineState:encoder
+                                        destWidth:_destination.width
+                                       destHeight:_destination.height
+                             pipelineStateStorage:_pipelineStateStorage];
+        }
+
+        [self updateTransform:encoder transform:mtlc.transform forceUpdate:forceUpdate];
+        return;
+    }
+
+    // 2. Otherwise update all 'mutable' properties of encoder
+    [self updatePipelineState:encoder
+                      context:mtlc
+                renderOptions:renderOptions
+                  forceUpdate:forceUpdate];
+    [self updateTransform:encoder transform:mtlc.transform forceUpdate:forceUpdate];
+    [self updateClip:encoder clip:mtlc.clip forceUpdate:forceUpdate];
+}
+
+//
+// Internal methods that update states when necessary (compare with cached states)
+//
+
+// Updates pipelineState (and corresponding buffers) with use of paint+composite+flags
+- (void)updatePipelineState:(id<MTLRenderCommandEncoder>)encoder
+                    context:(MTLContext *)mtlc
+              renderOptions:(const RenderOptions *)renderOptions
+                forceUpdate:(jboolean)forceUpdate
+{
+    if (!forceUpdate
+        && [_paint isEqual:mtlc.paint]
+        && [_composite isEqual:mtlc.composite]
+        && (_isTexture == renderOptions->isTexture && (!renderOptions->isTexture || _interpolationMode == renderOptions->interpolation)) // interpolation is used only in texture mode
+        && _isAA == renderOptions->isAA
+        && _isAAShader == renderOptions->isAAShader
+        && _isText == renderOptions->isText
+        && _isLCD == renderOptions->isLCD
+        && _srcFlags.isOpaque == renderOptions->srcFlags.isOpaque && _srcFlags.isPremultiplied == renderOptions->srcFlags.isPremultiplied)
+        return;
+
+    self.paint = mtlc.paint;
+    [_composite copyFrom:mtlc.composite];
+    _isTexture = renderOptions->isTexture;
+    _interpolationMode = renderOptions->interpolation;
+    _isAA = renderOptions->isAA;
+    _isAAShader = renderOptions->isAAShader;
+    _isText = renderOptions->isText;
+    _isLCD = renderOptions->isLCD;
+    _srcFlags = renderOptions->srcFlags;
+
+    if ((jint)[mtlc.composite getCompositeState] == sun_java2d_SunGraphics2D_COMP_XOR) {
+
+        [mtlc.paint setXorModePipelineState:encoder
+                               context:mtlc
+                         renderOptions:renderOptions
+                  pipelineStateStorage:_pipelineStateStorage];
+    } else {
+        [mtlc.paint  setPipelineState:encoder
+                              context:mtlc
+                        renderOptions:renderOptions
+                 pipelineStateStorage:_pipelineStateStorage];
+    }
+}
+
+- (void) updateClip:(id<MTLRenderCommandEncoder>)encoder clip:(MTLClip *)clip forceUpdate:(jboolean)forceUpdate
+{
+    if (clip.stencilMaskGenerationInProgress == JNI_TRUE) {
+        // don't set setScissorOrStencil when generation in progress
+        return;
+    }
+
+    if (!forceUpdate && [_clip isEqual:clip])
+        return;
+
+    [_clip copyFrom:clip];
+    [_clip setScissorOrStencil:encoder
+                     destWidth:_destination.width
+                    destHeight:_destination.height
+                        device:_device];
+}
+
+- (void)updateTransform:(id <MTLRenderCommandEncoder>)encoder
+              transform:(MTLTransform *)transform
+            forceUpdate:(jboolean)forceUpdate
+{
+    if (!forceUpdate
+        && [_transform isEqual:transform])
+        return;
+
+    [_transform copyFrom:transform];
+    [_transform setVertexMatrix:encoder
+                        destWidth:_destination.width
+                       destHeight:_destination.height];
+}
+
+@end
+
+@implementation EncoderManager {
+    MTLContext * _mtlc; // used to obtain CommandBufferWrapper and Composite/Paint/Transform
+
+    id<MTLRenderCommandEncoder> _encoder;
+
+    // 'Persistent' properties of encoder
+    id<MTLTexture> _destination;
+    id<MTLTexture> _aaDestination;
+    BOOL _useStencil;
+
+    // 'Mutable' states of encoder
+    EncoderStates * _encoderStates;
+}
+
+- (id _Nonnull)init {
+    self = [super init];
+    if (self) {
+        _encoder = nil;
+        _destination = nil;
+        _aaDestination = nil;
+        _useStencil = NO;
+        _encoderStates = [[EncoderStates alloc] init];
+
+    }
+    return self;
+}
+
+- (void)dealloc {
+    [_encoderStates release];
+    [super dealloc];
+}
+
+- (void)setContext:(MTLContex * _Nonnull)mtlc {
+    self->_mtlc = mtlc;
+    [self->_encoderStates setContext:mtlc];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps
+{
+    return [self getRenderEncoder:dstOps->pTexture isDstOpaque:dstOps->isOpaque];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getAARenderEncoder:(const BMTLSDOps * _Nonnull)dstOps {
+  id<MTLTexture> dstTxt = dstOps->pTexture;
+  RenderOptions roptions = {JNI_FALSE, JNI_TRUE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, {dstOps->isOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
+  return [self getEncoder:dstTxt renderOptions:&roptions];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getAAShaderRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps
+{
+    RenderOptions roptions = {JNI_FALSE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, {dstOps->isOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_TRUE};
+    return [self getEncoder:dstOps->pTexture renderOptions:&roptions];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull)getRenderEncoder:(id<MTLTexture> _Nonnull)dest
+                                             isDstOpaque:(bool)isOpaque
+{
+    RenderOptions roptions = {JNI_FALSE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, {isOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
+    return [self getEncoder:dest renderOptions:&roptions];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(const BMTLSDOps * _Nonnull)dstOps
+                                      isSrcOpaque:(bool)isSrcOpaque
+{
+    return [self getTextureEncoder:dstOps->pTexture
+                       isSrcOpaque:isSrcOpaque
+                       isDstOpaque:dstOps->isOpaque
+                     interpolation:INTERPOLATION_NEAREST_NEIGHBOR];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
+                                               isSrcOpaque:(bool)isSrcOpaque
+                                               isDstOpaque:(bool)isDstOpaque
+{
+    return [self getTextureEncoder:dest
+                       isSrcOpaque:isSrcOpaque
+                       isDstOpaque:isDstOpaque
+                     interpolation:INTERPOLATION_NEAREST_NEIGHBOR
+                              isAA:JNI_FALSE];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getLCDEncoder:(id<MTLTexture> _Nonnull)dest
+                                               isSrcOpaque:(bool)isSrcOpaque
+                                               isDstOpaque:(bool)isDstOpaque
+{
+    RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, {isSrcOpaque, JNI_TRUE }, {isDstOpaque, JNI_TRUE}, JNI_FALSE, JNI_TRUE, JNI_FALSE};
+    return [self getEncoder:dest renderOptions:&roptions];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
+                                      isSrcOpaque:(bool)isSrcOpaque
+                                      isDstOpaque:(bool)isDstOpaque
+                                    interpolation:(int)interpolation
+                                             isAA:(jboolean)isAA
+{
+    RenderOptions roptions = {JNI_TRUE, isAA, interpolation, { isSrcOpaque, JNI_TRUE }, {isDstOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
+    return [self getEncoder:dest renderOptions:&roptions];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
+                                               isSrcOpaque:(bool)isSrcOpaque
+                                               isDstOpaque:(bool)isDstOpaque
+                                             interpolation:(int)interpolation
+{
+    return [self getTextureEncoder:dest isSrcOpaque:isSrcOpaque isDstOpaque:isDstOpaque interpolation:interpolation isAA:JNI_FALSE];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getTextEncoder:(const BMTLSDOps * _Nonnull)dstOps
+                                      isSrcOpaque:(bool)isSrcOpaque
+{
+    RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, { isSrcOpaque, JNI_TRUE }, {dstOps->isOpaque, JNI_TRUE}, JNI_TRUE, JNI_FALSE, JNI_FALSE};
+    return [self getEncoder:dstOps->pTexture renderOptions:&roptions];
+}
+
+- (id<MTLRenderCommandEncoder> _Nonnull) getEncoder:(id <MTLTexture> _Nonnull)dest
+                                      renderOptions:(const RenderOptions * _Nonnull)renderOptions
+{
+  //
+  // 1. check whether it's necessary to call endEncoder
+  //
+  jboolean needEnd = JNI_FALSE;
+  if (_encoder != nil) {
+    if (_destination != dest || renderOptions->isAA != _encoderStates.aa) {
+      J2dTraceLn2(J2D_TRACE_VERBOSE,
+                  "end common encoder because of dest change: %p -> %p",
+                  _destination, dest);
+      needEnd = JNI_TRUE;
+    } else if ((_useStencil == NO) != ([_mtlc.clip isShape] == NO)) {
+      // 1. When mode changes RECT -> SHAPE we must recreate encoder with
+      // stencilAttachment (todo: consider the case when current encoder already
+      // has stencil)
+      //
+      // 2. When mode changes SHAPE -> RECT it seems that we can use the same
+      // encoder with disabled stencil test, but [encoder
+      // setDepthStencilState:nil] causes crash, so we have to recreate encoder
+      // in such case
+      J2dTraceLn2(J2D_TRACE_VERBOSE,
+                  "end common encoder because toggle stencil: %d -> %d",
+                  (int)_useStencil, (int)[_mtlc.clip isShape]);
+      needEnd = JNI_TRUE;
+    }
+  }
+  if (needEnd)
+    [self endEncoder];
+
+  //
+  // 2. recreate encoder if necessary
+  //
+  jboolean forceUpdate = JNI_FALSE;
+#ifdef ALWAYS_UPDATE_ENCODER_STATES
+  forceUpdate = JNI_TRUE;
+#endif // ALWAYS_UPDATE_ENCODER_STATES
+
+  if (_encoder == nil) {
+    _destination = dest;
+    _useStencil = [_mtlc.clip isShape] && !_mtlc.clip.stencilMaskGenerationInProgress;
+    forceUpdate = JNI_TRUE;
+
+    MTLCommandBufferWrapper *cbw = [_mtlc getCommandBufferWrapper];
+    MTLRenderPassDescriptor *rpd =
+        [MTLRenderPassDescriptor renderPassDescriptor];
+    MTLRenderPassColorAttachmentDescriptor *ca = rpd.colorAttachments[0];
+    ca.texture = dest;
+
+    // TODO: Find out why we cannot use
+    // if (_mtlc.clip.stencilMaskGenerationInProgress == YES) {
+    //     ca.loadAction = MTLLoadActionClear;
+    //     ca.clearColor = MTLClearColorMake(0.0f, 0.0f,0.0f, 0.0f);
+    // }
+    // here to avoid creation of clearEncoder in beginShapeClip
+
+    ca.loadAction = MTLLoadActionLoad;
+    ca.storeAction = MTLStoreActionStore;
+
+    if (_useStencil && !renderOptions->isAA) {
+        // If you enable stencil testing or stencil writing, the
+        // MTLRenderPassDescriptor must include a stencil attachment.
+        rpd.stencilAttachment.loadAction = MTLLoadActionLoad;
+        rpd.stencilAttachment.storeAction = MTLStoreActionStore;
+        rpd.stencilAttachment.texture = _mtlc.clip.stencilTextureRef;
+    }
+
+    // J2dTraceLn1(J2D_TRACE_VERBOSE, "created render encoder to draw on
+    // tex=%p", dest);
+    _encoder = [[cbw getCommandBuffer] renderCommandEncoderWithDescriptor:rpd];
+
+    [_encoderStates reset:dest
+               isDstOpaque:renderOptions->dstFlags.isOpaque
+        isDstPremultiplied:YES
+                      isAA:renderOptions->isAA
+                      isText:renderOptions->isText
+                      isLCD:renderOptions->isLCD];
+  }
+
+  //
+  // 3. update encoder states
+  //
+  [_encoderStates updateEncoder:_encoder
+                        context:_mtlc
+                  renderOptions:renderOptions
+                    forceUpdate:forceUpdate];
+
+  return _encoder;
+}
+
+- (id<MTLBlitCommandEncoder> _Nonnull) createBlitEncoder {
+    [self endEncoder];
+    return [[[_mtlc getCommandBufferWrapper] getCommandBuffer] blitCommandEncoder];
+}
+
+- (void) endEncoder {
+    if (_encoder != nil) {
+      [_encoder endEncoding];
+      _encoder = nil;
+      _destination = nil;
+    }
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.h
new file mode 100644
index 00000000000..839615a044c
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLBlitLoops_h_Included
+#define MTLBlitLoops_h_Included
+
+#include "sun_java2d_metal_MTLBlitLoops.h"
+#include "MTLSurfaceDataBase.h"
+#include "MTLContext.h"
+
+#define OFFSET_SRCTYPE sun_java2d_metal_MTLBlitLoops_OFFSET_SRCTYPE
+#define OFFSET_HINT    sun_java2d_metal_MTLBlitLoops_OFFSET_HINT
+#define OFFSET_TEXTURE sun_java2d_metal_MTLBlitLoops_OFFSET_TEXTURE
+#define OFFSET_RTT     sun_java2d_metal_MTLBlitLoops_OFFSET_RTT
+#define OFFSET_XFORM   sun_java2d_metal_MTLBlitLoops_OFFSET_XFORM
+#define OFFSET_ISOBLIT sun_java2d_metal_MTLBlitLoops_OFFSET_ISOBLIT
+
+void MTLBlitLoops_IsoBlit(JNIEnv *env,
+                          MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
+                          jboolean xform, jint hint,
+                          jboolean texture,
+                          jint sx1, jint sy1,
+                          jint sx2, jint sy2,
+                          jdouble dx1, jdouble dy1,
+                          jdouble dx2, jdouble dy2);
+
+void MTLBlitLoops_Blit(JNIEnv *env,
+                       MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
+                       jboolean xform, jint hint,
+                       jint srctype, jboolean texture,
+                       jint sx1, jint sy1,
+                       jint sx2, jint sy2,
+                       jdouble dx1, jdouble dy1,
+                       jdouble dx2, jdouble dy2);
+
+void MTLBlitLoops_SurfaceToSwBlit(JNIEnv *env, MTLContext *mtlc,
+                                  jlong pSrcOps, jlong pDstOps, jint dsttype,
+                                  jint srcx, jint srcy,
+                                  jint dstx, jint dsty,
+                                  jint width, jint height);
+
+void MTLBlitLoops_CopyArea(JNIEnv *env,
+                           MTLContext *mtlc, BMTLSDOps *dstOps,
+                           jint x, jint y,
+                           jint width, jint height,
+                           jint dx, jint dy);
+
+void MTLBlitTex2Tex(MTLContext *mtlc, id<MTLTexture> src, id<MTLTexture> dest);
+
+void drawTex2Tex(MTLContext *mtlc,
+                        id<MTLTexture> src, id<MTLTexture> dst,
+                        jboolean isSrcOpaque, jboolean isDstOpaque, jint hint,
+                        jint sx1, jint sy1, jint sx2, jint sy2,
+                        jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2);
+
+#endif /* MTLBlitLoops_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.m
new file mode 100644
index 00000000000..97444da4c6b
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.m
@@ -0,0 +1,822 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <jni.h>
+#include <jlong.h>
+
+#include "SurfaceData.h"
+#include "MTLBlitLoops.h"
+#include "MTLRenderQueue.h"
+#include "MTLSurfaceData.h"
+#include "MTLUtils.h"
+#include "GraphicsPrimitiveMgr.h"
+
+#include <string.h> // memcpy
+#include "IntArgbPre.h"
+
+#import <Accelerate/Accelerate.h>
+
+#ifdef DEBUG
+#define TRACE_ISOBLIT
+#define TRACE_BLIT
+#endif //DEBUG
+//#define DEBUG_ISOBLIT
+//#define DEBUG_BLIT
+
+typedef struct {
+    // Consider deleting this field, since it's always MTLPixelFormatBGRA8Unorm
+    jboolean hasAlpha;
+    jboolean isPremult;
+    NSString* swizzleKernel;
+} MTLRasterFormatInfo;
+
+/**
+ * This table contains the "pixel formats" for all system memory surfaces
+ * that Metal is capable of handling, indexed by the "PF_" constants defined
+ * in MTLLSurfaceData.java.  These pixel formats contain information that is
+ * passed to Metal when copying from a system memory ("Sw") surface to
+ * an Metal surface
+ */
+MTLRasterFormatInfo RasterFormatInfos[] = {
+        { 1, 0, nil }, /* 0 - IntArgb      */ // Argb (in java notation)
+        { 1, 1, nil }, /* 1 - IntArgbPre   */
+        { 0, 1, @"rgb_to_rgba" }, /* 2 - IntRgb       */
+        { 0, 1, @"xrgb_to_rgba" }, /* 3 - IntRgbx      */
+        { 0, 1, @"bgr_to_rgba"  }, /* 4 - IntBgr       */
+        { 0, 1, @"xbgr_to_rgba" }, /* 5 - IntBgrx      */
+
+//        TODO: support 2-byte formats
+//        { GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV,
+//                2, 0, 1,                                     }, /* 7 - Ushort555Rgb */
+//        { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1,
+//                2, 0, 1,                                     }, /* 8 - Ushort555Rgbx*/
+//        { GL_LUMINANCE, GL_UNSIGNED_BYTE,
+//                1, 0, 1,                                     }, /* 9 - ByteGray     */
+//        { GL_LUMINANCE, GL_UNSIGNED_SHORT,
+//                2, 0, 1,                                     }, /*10 - UshortGray   */
+//        { GL_BGR,  GL_UNSIGNED_BYTE,
+//                1, 0, 1,                                     }, /*11 - ThreeByteBgr */
+};
+
+extern void J2dTraceImpl(int level, jboolean cr, const char *string, ...);
+
+void fillTxQuad(
+        struct TxtVertex * txQuadVerts,
+        jint sx1, jint sy1, jint sx2, jint sy2, jint sw, jint sh,
+        jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2, jdouble dw, jdouble dh
+) {
+    const float nsx1 = sx1/(float)sw;
+    const float nsy1 = sy1/(float)sh;
+    const float nsx2 = sx2/(float)sw;
+    const float nsy2 = sy2/(float)sh;
+
+    txQuadVerts[0].position[0] = dx1;
+    txQuadVerts[0].position[1] = dy1;
+    txQuadVerts[0].txtpos[0]   = nsx1;
+    txQuadVerts[0].txtpos[1]   = nsy1;
+
+    txQuadVerts[1].position[0] = dx2;
+    txQuadVerts[1].position[1] = dy1;
+    txQuadVerts[1].txtpos[0]   = nsx2;
+    txQuadVerts[1].txtpos[1]   = nsy1;
+
+    txQuadVerts[2].position[0] = dx2;
+    txQuadVerts[2].position[1] = dy2;
+    txQuadVerts[2].txtpos[0]   = nsx2;
+    txQuadVerts[2].txtpos[1]   = nsy2;
+
+    txQuadVerts[3].position[0] = dx2;
+    txQuadVerts[3].position[1] = dy2;
+    txQuadVerts[3].txtpos[0]   = nsx2;
+    txQuadVerts[3].txtpos[1]   = nsy2;
+
+    txQuadVerts[4].position[0] = dx1;
+    txQuadVerts[4].position[1] = dy2;
+    txQuadVerts[4].txtpos[0]   = nsx1;
+    txQuadVerts[4].txtpos[1]   = nsy2;
+
+    txQuadVerts[5].position[0] = dx1;
+    txQuadVerts[5].position[1] = dy1;
+    txQuadVerts[5].txtpos[0]   = nsx1;
+    txQuadVerts[5].txtpos[1]   = nsy1;
+}
+
+//#define TRACE_drawTex2Tex
+
+void drawTex2Tex(MTLContext *mtlc,
+                        id<MTLTexture> src, id<MTLTexture> dst,
+                        jboolean isSrcOpaque, jboolean isDstOpaque, jint hint,
+                        jint sx1, jint sy1, jint sx2, jint sy2,
+                        jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
+{
+#ifdef TRACE_drawTex2Tex
+    J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "drawTex2Tex: src tex=%p, dst tex=%p", src, dst);
+    J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "  sw=%d sh=%d dw=%d dh=%d", src.width, src.height, dst.width, dst.height);
+    J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "  sx1=%d sy1=%d sx2=%d sy2=%d", sx1, sy1, sx2, sy2);
+    J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "  dx1=%f dy1=%f dx2=%f dy2=%f", dx1, dy1, dx2, dy2);
+#endif //TRACE_drawTex2Tex
+
+    id<MTLRenderCommandEncoder> encoder = [mtlc.encoderManager getTextureEncoder:dst
+                                                                     isSrcOpaque:isSrcOpaque
+                                                                     isDstOpaque:isDstOpaque
+                                                                   interpolation:hint
+    ];
+
+    struct TxtVertex quadTxVerticesBuffer[6];
+    fillTxQuad(quadTxVerticesBuffer, sx1, sy1, sx2, sy2, src.width, src.height, dx1, dy1, dx2, dy2, dst.width, dst.height);
+
+    [encoder setVertexBytes:quadTxVerticesBuffer length:sizeof(quadTxVerticesBuffer) atIndex:MeshVertexBuffer];
+    [encoder setFragmentTexture:src atIndex: 0];
+    [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
+}
+
+static void
+replaceTextureRegion(MTLContext *mtlc, id<MTLTexture> dest, const SurfaceDataRasInfo *srcInfo,
+                     const MTLRasterFormatInfo *rfi,
+                     int dx1, int dy1, int dx2, int dy2) {
+    const int sw = srcInfo->bounds.x2 - srcInfo->bounds.x1;
+    const int sh = srcInfo->bounds.y2 - srcInfo->bounds.y1;
+    const int dw = dx2 - dx1;
+    const int dh = dy2 - dy1;
+    if (dw < sw || dh < sh) {
+        J2dTraceLn4(J2D_TRACE_ERROR, "replaceTextureRegion: dest size: (%d, %d) less than source size: (%d, %d)", dw, dh, sw, sh);
+        return;
+    }
+
+    const void *raster = srcInfo->rasBase;
+    raster += (NSUInteger)srcInfo->bounds.y1 * (NSUInteger)srcInfo->scanStride + (NSUInteger)srcInfo->bounds.x1 * (NSUInteger)srcInfo->pixelStride;
+
+    @autoreleasepool {
+        J2dTraceLn4(J2D_TRACE_VERBOSE, "replaceTextureRegion src (dw, dh) : [%d, %d] dest (dx1, dy1) =[%d, %d]",
+                    dw, dh, dx1, dy1);
+        id<MTLBuffer> buff = [[mtlc.device newBufferWithLength:(sw * sh * srcInfo->pixelStride) options:MTLResourceStorageModeManaged] autorelease];
+
+        // copy src pixels inside src bounds to buff
+        for (int row = 0; row < sh; row++) {
+            memcpy(buff.contents + (row * sw * srcInfo->pixelStride), raster, sw * srcInfo->pixelStride);
+            raster += (NSUInteger)srcInfo->scanStride;
+        }
+        [buff didModifyRange:NSMakeRange(0, buff.length)];
+
+        if (rfi->swizzleKernel != nil) {
+            id <MTLBuffer> swizzled = [[mtlc.device newBufferWithLength:(sw * sh * srcInfo->pixelStride) options:MTLResourceStorageModeManaged] autorelease];
+
+            // this should be cheap, since data is already on GPU
+            id<MTLCommandBuffer> cb = [mtlc createCommandBuffer];
+            id<MTLComputeCommandEncoder> computeEncoder = [cb computeCommandEncoder];
+            id<MTLComputePipelineState> computePipelineState = [mtlc.pipelineStateStorage
+                                                                getComputePipelineState:rfi->swizzleKernel];
+            [computeEncoder setComputePipelineState:computePipelineState];
+
+            [computeEncoder setBuffer:buff offset:0 atIndex:0];
+            [computeEncoder setBuffer:swizzled offset:0 atIndex:1];
+
+            NSUInteger threadGroupSize = computePipelineState.maxTotalThreadsPerThreadgroup;
+            if (threadGroupSize == 0) {
+               threadGroupSize = 1;
+            }
+            NSUInteger pixelCount = buff.length / srcInfo->pixelStride;
+            MTLSize threadsPerGroup = MTLSizeMake(threadGroupSize, 1, 1);
+            MTLSize threadGroups = MTLSizeMake((pixelCount + threadGroupSize - 1) / threadGroupSize,
+                                               1, 1);
+            [computeEncoder dispatchThreadgroups:threadGroups
+                           threadsPerThreadgroup:threadsPerGroup];
+            [computeEncoder endEncoding];
+            [cb commit];
+
+            buff = swizzled;
+        }
+
+        id<MTLBlitCommandEncoder> blitEncoder = [mtlc.encoderManager createBlitEncoder];
+        [blitEncoder copyFromBuffer:buff
+                       sourceOffset:0 sourceBytesPerRow:(sw * srcInfo->pixelStride)
+                sourceBytesPerImage:(sw * sh * srcInfo->pixelStride) sourceSize:MTLSizeMake(sw, sh, 1)
+                          toTexture:dest
+                   destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(dx1, dy1, 0)];
+        [blitEncoder endEncoding];
+        [mtlc.encoderManager endEncoder];
+
+        MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+        id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+        [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+            [cbwrapper release];
+        }];
+        [commandbuf commit];
+    }
+}
+
+/**
+ * Inner loop used for copying a source system memory ("Sw") surface to a
+ * destination MTL "Surface".  This method is invoked from
+ * MTLBlitLoops_Blit().
+ */
+
+static void
+MTLBlitSwToTextureViaPooledTexture(
+        MTLContext *mtlc, SurfaceDataRasInfo *srcInfo, BMTLSDOps * bmtlsdOps,
+        MTLRasterFormatInfo *rfi, jint hint,
+        jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
+{
+    int sw = srcInfo->bounds.x2 - srcInfo->bounds.x1;
+    int sh = srcInfo->bounds.y2 - srcInfo->bounds.y1;
+
+    sw = MIN(sw, MTL_GPU_FAMILY_MAC_TXT_SIZE);
+    sh = MIN(sh, MTL_GPU_FAMILY_MAC_TXT_SIZE);
+
+    id<MTLTexture> dest = bmtlsdOps->pTexture;
+
+    MTLPooledTextureHandle * texHandle = [mtlc.texturePool getTexture:sw height:sh format:MTLPixelFormatBGRA8Unorm];
+    if (texHandle == nil) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitSwToTextureViaPooledTexture: can't obtain temporary texture object from pool");
+        return;
+    }
+    [[mtlc getCommandBufferWrapper] registerPooledTexture:texHandle];
+
+    id<MTLTexture> texBuff = texHandle.texture;
+    replaceTextureRegion(mtlc, texBuff, srcInfo, rfi, 0, 0, sw, sh);
+
+    drawTex2Tex(mtlc, texBuff, dest, !rfi->hasAlpha, bmtlsdOps->isOpaque, hint,
+                0, 0, sw, sh, dx1, dy1, dx2, dy2);
+}
+
+static
+jboolean isIntegerAndUnscaled(
+        jint sx1, jint sy1, jint sx2, jint sy2,
+        jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2
+) {
+    const jdouble epsilon = 0.0001f;
+
+    // check that dx1,dy1 is integer
+    if (fabs(dx1 - (int)dx1) > epsilon || fabs(dy1 - (int)dy1) > epsilon) {
+        return JNI_FALSE;
+    }
+    // check that destSize equals srcSize
+    if (fabs(dx2 - dx1 - sx2 + sx1) > epsilon || fabs(dy2 - dy1 - sy2 + sy1) > epsilon) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+static
+jboolean clipDestCoords(
+        jdouble *dx1, jdouble *dy1, jdouble *dx2, jdouble *dy2,
+        jint *sx1, jint *sy1, jint *sx2, jint *sy2,
+        jint destW, jint destH, const MTLScissorRect * clipRect
+) {
+    // Trim destination rect by clip-rect (or dest.bounds)
+    const jint sw    = *sx2 - *sx1;
+    const jint sh    = *sy2 - *sy1;
+    const jdouble dw = *dx2 - *dx1;
+    const jdouble dh = *dy2 - *dy1;
+
+    jdouble dcx1 = 0;
+    jdouble dcx2 = destW;
+    jdouble dcy1 = 0;
+    jdouble dcy2 = destH;
+    if (clipRect != NULL) {
+        if (clipRect->x > dcx1)
+            dcx1 = clipRect->x;
+        const int maxX = clipRect->x + clipRect->width;
+        if (dcx2 > maxX)
+            dcx2 = maxX;
+        if (clipRect->y > dcy1)
+            dcy1 = clipRect->y;
+        const int maxY = clipRect->y + clipRect->height;
+        if (dcy2 > maxY)
+            dcy2 = maxY;
+
+        if (dcx1 >= dcx2) {
+            J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcx1=%1.2f, dcx2=%1.2f", dcx1, dcx2);
+            dcx1 = dcx2;
+        }
+        if (dcy1 >= dcy2) {
+            J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcy1=%1.2f, dcy2=%1.2f", dcy1, dcy2);
+            dcy1 = dcy2;
+        }
+    }
+    if (*dx2 <= dcx1 || *dx1 >= dcx2 || *dy2 <= dcy1 || *dy1 >= dcy2) {
+        J2dTraceLn(J2D_TRACE_INFO, "\tclipDestCoords: dest rect doesn't intersect clip area");
+        J2dTraceLn4(J2D_TRACE_INFO, "\tdx2=%1.4f <= dcx1=%1.4f || *dx1=%1.4f >= dcx2=%1.4f", *dx2, dcx1, *dx1, dcx2);
+        J2dTraceLn4(J2D_TRACE_INFO, "\t*dy2=%1.4f <= dcy1=%1.4f || *dy1=%1.4f >= dcy2=%1.4f", *dy2, dcy1, *dy1, dcy2);
+        return JNI_FALSE;
+    }
+    if (*dx1 < dcx1) {
+        J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdx1=%1.2f, will be clipped to %1.2f | sx1+=%d", *dx1, dcx1, (jint)((dcx1 - *dx1) * (sw/dw)));
+        *sx1 += (jint)((dcx1 - *dx1) * (sw/dw));
+        *dx1 = dcx1;
+    }
+    if (*dx2 > dcx2) {
+        J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdx2=%1.2f, will be clipped to %1.2f | sx2-=%d", *dx2, dcx2, (jint)((*dx2 - dcx2) * (sw/dw)));
+        *sx2 -= (jint)((*dx2 - dcx2) * (sw/dw));
+        *dx2 = dcx2;
+    }
+    if (*dy1 < dcy1) {
+        J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdy1=%1.2f, will be clipped to %1.2f | sy1+=%d", *dy1, dcy1, (jint)((dcy1 - *dy1) * (sh/dh)));
+        *sy1 += (jint)((dcy1 - *dy1) * (sh/dh));
+        *dy1 = dcy1;
+    }
+    if (*dy2 > dcy2) {
+        J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdy2=%1.2f, will be clipped to %1.2f | sy2-=%d", *dy2, dcy2, (jint)((*dy2 - dcy2) * (sh/dh)));
+        *sy2 -= (jint)((*dy2 - dcy2) * (sh/dh));
+        *dy2 = dcy2;
+    }
+    return JNI_TRUE;
+}
+
+/**
+ * General blit method for copying a native MTL surface to another MTL "Surface".
+ * Parameter texture == true forces to use 'texture' codepath (dest coordinates will always be integers).
+ * Parameter xform == true only when AffineTransform is used (invoked only from TransformBlit, dest coordinates will always be integers).
+ */
+void
+MTLBlitLoops_IsoBlit(JNIEnv *env,
+                     MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
+                     jboolean xform, jint hint, jboolean texture,
+                     jint sx1, jint sy1, jint sx2, jint sy2,
+                     jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
+{
+    BMTLSDOps *srcOps = (BMTLSDOps *)jlong_to_ptr(pSrcOps);
+    BMTLSDOps *dstOps = (BMTLSDOps *)jlong_to_ptr(pDstOps);
+
+    RETURN_IF_NULL(mtlc);
+    RETURN_IF_NULL(srcOps);
+    RETURN_IF_NULL(dstOps);
+    // Verify if we use a valid MTLContext
+    MTLSDOps *dstMTLOps = (MTLSDOps *)dstOps->privOps;
+    RETURN_IF_TRUE(dstMTLOps->configInfo != NULL && mtlc != dstMTLOps->configInfo->context);
+
+    MTLSDOps *srcMTLOps = (MTLSDOps *)srcOps->privOps;
+    RETURN_IF_TRUE(srcMTLOps->configInfo != NULL && mtlc != srcMTLOps->configInfo->context);
+
+    id<MTLTexture> srcTex = srcOps->pTexture;
+    id<MTLTexture> dstTex = dstOps->pTexture;
+    if (srcTex == nil || srcTex == nil) {
+        J2dTraceLn2(J2D_TRACE_ERROR, "MTLBlitLoops_IsoBlit: surface is null (stex=%p, dtex=%p)", srcTex, dstTex);
+        return;
+    }
+
+    const jint sw    = sx2 - sx1;
+    const jint sh    = sy2 - sy1;
+    const jdouble dw = dx2 - dx1;
+    const jdouble dh = dy2 - dy1;
+
+    if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) {
+        J2dTraceLn4(J2D_TRACE_WARNING, "MTLBlitLoops_IsoBlit: invalid dimensions: sw=%d, sh%d, dw=%d, dh=%d", sw, sh, dw, dh);
+        return;
+    }
+
+#ifdef DEBUG_ISOBLIT
+    if ((xform == JNI_TRUE) != (mtlc.useTransform == JNI_TRUE)) {
+        J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
+                "MTLBlitLoops_IsoBlit state error: xform=%d, mtlc.useTransform=%d, texture=%d",
+                xform, mtlc.useTransform, texture);
+    }
+#endif // DEBUG_ISOBLIT
+
+    if (!xform) {
+        clipDestCoords(
+                &dx1, &dy1, &dx2, &dy2,
+                &sx1, &sy1, &sx2, &sy2,
+                dstTex.width, dstTex.height, texture ? NULL : [mtlc.clip getRect]
+        );
+    }
+
+    SurfaceDataBounds bounds;
+    bounds.x1 = sx1;
+    bounds.y1 = sy1;
+    bounds.x2 = sx2;
+    bounds.y2 = sy2;
+    SurfaceData_IntersectBoundsXYXY(&bounds, 0, 0, srcOps->width, srcOps->height);
+
+    if (bounds.x2 <= bounds.x1 || bounds.y2 <= bounds.y1) {
+        J2dTraceLn(J2D_TRACE_VERBOSE, "MTLBlitLoops_IsoBlit: source rectangle doesn't intersect with source surface bounds");
+        J2dTraceLn6(J2D_TRACE_VERBOSE, "  sx1=%d sy1=%d sx2=%d sy2=%d sw=%d sh=%d", sx1, sy1, sx2, sy2, srcOps->width, srcOps->height);
+        J2dTraceLn4(J2D_TRACE_VERBOSE, "  dx1=%f dy1=%f dx2=%f dy2=%f", dx1, dy1, dx2, dy2);
+        return;
+    }
+
+    if (bounds.x1 != sx1) {
+        dx1 += (bounds.x1 - sx1) * (dw / sw);
+        sx1 = bounds.x1;
+    }
+    if (bounds.y1 != sy1) {
+        dy1 += (bounds.y1 - sy1) * (dh / sh);
+        sy1 = bounds.y1;
+    }
+    if (bounds.x2 != sx2) {
+        dx2 += (bounds.x2 - sx2) * (dw / sw);
+        sx2 = bounds.x2;
+    }
+    if (bounds.y2 != sy2) {
+        dy2 += (bounds.y2 - sy2) * (dh / sh);
+        sy2 = bounds.y2;
+    }
+
+#ifdef TRACE_ISOBLIT
+    J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE,
+         "MTLBlitLoops_IsoBlit [tx=%d, xf=%d, AC=%s]: src=%s, dst=%s | (%d, %d, %d, %d)->(%1.2f, %1.2f, %1.2f, %1.2f)",
+         texture, xform, [mtlc getCompositeDescription].cString,
+         getSurfaceDescription(srcOps).cString, getSurfaceDescription(dstOps).cString,
+         sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
+#endif //TRACE_ISOBLIT
+
+    if (!texture && !xform
+        && srcOps->isOpaque
+        && isIntegerAndUnscaled(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)
+        && (dstOps->isOpaque || !srcOps->isOpaque)
+    ) {
+#ifdef TRACE_ISOBLIT
+        J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via blitEncoder]");
+#endif //TRACE_ISOBLIT
+
+        id <MTLBlitCommandEncoder> blitEncoder = [mtlc.encoderManager createBlitEncoder];
+        [blitEncoder copyFromTexture:srcTex
+                         sourceSlice:0
+                         sourceLevel:0
+                        sourceOrigin:MTLOriginMake(sx1, sy1, 0)
+                          sourceSize:MTLSizeMake(sx2 - sx1, sy2 - sy1, 1)
+                           toTexture:dstTex
+                    destinationSlice:0
+                    destinationLevel:0
+                   destinationOrigin:MTLOriginMake(dx1, dy1, 0)];
+        [blitEncoder endEncoding];
+        return;
+    }
+
+#ifdef TRACE_ISOBLIT
+    J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via sampling]");
+#endif //TRACE_ISOBLIT
+    drawTex2Tex(mtlc, srcTex, dstTex,
+            srcOps->isOpaque, dstOps->isOpaque,
+            hint, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
+}
+
+/**
+ * General blit method for copying a system memory ("Sw") surface to a native MTL surface.
+ * Parameter texture == true only in SwToTextureBlit (straight copy from sw to texture), dest coordinates will always be integers.
+ * Parameter xform == true only when AffineTransform is used (invoked only from TransformBlit, dest coordinates will always be integers).
+ */
+void
+MTLBlitLoops_Blit(JNIEnv *env,
+                  MTLContext *mtlc, jlong pSrcOps, jlong pDstOps,
+                  jboolean xform, jint hint,
+                  jint srctype, jboolean texture,
+                  jint sx1, jint sy1, jint sx2, jint sy2,
+                  jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
+{
+    SurfaceDataOps *srcOps = (SurfaceDataOps *)jlong_to_ptr(pSrcOps);
+    BMTLSDOps *dstOps = (BMTLSDOps *)jlong_to_ptr(pDstOps);
+
+    RETURN_IF_NULL(mtlc);
+    RETURN_IF_NULL(srcOps);
+    RETURN_IF_NULL(dstOps);
+    // Verify if we use a valid MTLContext
+    MTLSDOps *dstMTLOps = (MTLSDOps *)dstOps->privOps;
+    RETURN_IF_TRUE(dstMTLOps->configInfo != NULL && mtlc != dstMTLOps->configInfo->context);
+
+    id<MTLTexture> dest = dstOps->pTexture;
+    if (dest == NULL) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitLoops_Blit: dest is null");
+        return;
+    }
+    if (srctype < 0 || srctype >= sizeof(RasterFormatInfos)/ sizeof(MTLRasterFormatInfo)) {
+        J2dTraceLn1(J2D_TRACE_ERROR, "MTLBlitLoops_Blit: source pixel format %d isn't supported", srctype);
+        return;
+    }
+    const jint sw    = sx2 - sx1;
+    const jint sh    = sy2 - sy1;
+    const jdouble dw = dx2 - dx1;
+    const jdouble dh = dy2 - dy1;
+
+    if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitLoops_Blit: invalid dimensions");
+        return;
+    }
+
+#ifdef DEBUG_BLIT
+    if (
+        (xform == JNI_TRUE) != (mtlc.useTransform == JNI_TRUE)
+        || (xform && texture)
+    ) {
+        J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
+                "MTLBlitLoops_Blit state error: xform=%d, mtlc.useTransform=%d, texture=%d",
+                xform, mtlc.useTransform, texture);
+    }
+    if (texture) {
+        if (!isIntegerAndUnscaled(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)) {
+            J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
+                    "MTLBlitLoops_Blit state error: texture=true, but src and dst dimensions aren't equal or dest coords aren't integers");
+        }
+        if (!dstOps->isOpaque && !RasterFormatInfos[srctype].hasAlpha) {
+            J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE,
+                    "MTLBlitLoops_Blit state error: texture=true, but dest has alpha and source hasn't alpha, can't use texture-codepath");
+        }
+    }
+#endif // DEBUG_BLIT
+    if (!xform) {
+        clipDestCoords(
+                &dx1, &dy1, &dx2, &dy2,
+                &sx1, &sy1, &sx2, &sy2,
+                dest.width, dest.height, texture ? NULL : [mtlc.clip getRect]
+        );
+    }
+
+    SurfaceDataRasInfo srcInfo;
+    srcInfo.bounds.x1 = sx1;
+    srcInfo.bounds.y1 = sy1;
+    srcInfo.bounds.x2 = sx2;
+    srcInfo.bounds.y2 = sy2;
+
+    // NOTE: This function will modify the contents of the bounds field to represent the maximum available raster data.
+    if (srcOps->Lock(env, srcOps, &srcInfo, SD_LOCK_READ) != SD_SUCCESS) {
+        J2dTraceLn(J2D_TRACE_WARNING, "MTLBlitLoops_Blit: could not acquire lock");
+        return;
+    }
+
+    if (srcInfo.bounds.x2 > srcInfo.bounds.x1 && srcInfo.bounds.y2 > srcInfo.bounds.y1) {
+        srcOps->GetRasInfo(env, srcOps, &srcInfo);
+        if (srcInfo.rasBase) {
+            if (srcInfo.bounds.x1 != sx1) {
+                const int dx = srcInfo.bounds.x1 - sx1;
+                dx1 += dx * (dw / sw);
+            }
+            if (srcInfo.bounds.y1 != sy1) {
+                const int dy = srcInfo.bounds.y1 - sy1;
+                dy1 += dy * (dh / sh);
+            }
+            if (srcInfo.bounds.x2 != sx2) {
+                const int dx = srcInfo.bounds.x2 - sx2;
+                dx2 += dx * (dw / sw);
+            }
+            if (srcInfo.bounds.y2 != sy2) {
+                const int dy = srcInfo.bounds.y2 - sy2;
+                dy2 += dy * (dh / sh);
+            }
+
+#ifdef TRACE_BLIT
+            J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE,
+                    "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)",
+                    texture, xform, [mtlc getCompositeDescription].cString,
+                    getSurfaceDescription(dstOps).cString, srcOps,
+                    sx2 - sx1, sy2 - sy1,
+                    RasterFormatInfos[srctype].hasAlpha ? 0 : 1, RasterFormatInfos[srctype].isPremult ? 1 : 0,
+                    sx1, sy1, sx2, sy2,
+                    dx1, dy1, dx2, dy2);
+#endif //TRACE_BLIT
+
+            MTLRasterFormatInfo rfi = RasterFormatInfos[srctype];
+
+            if (texture) {
+                replaceTextureRegion(mtlc, dest, &srcInfo, &rfi, (int) dx1, (int) dy1, (int) dx2, (int) dy2);
+            } else {
+                MTLBlitSwToTextureViaPooledTexture(mtlc, &srcInfo, dstOps, &rfi, hint, dx1, dy1, dx2, dy2);
+            }
+        }
+        SurfaceData_InvokeRelease(env, srcOps, &srcInfo);
+    }
+    SurfaceData_InvokeUnlock(env, srcOps, &srcInfo);
+}
+
+void copyFromMTLBuffer(void *pDst, id<MTLBuffer> srcBuf, NSUInteger offset, NSUInteger len, BOOL convertFromArgbPre) {
+    char *pSrc = (char*)srcBuf.contents + offset;
+    if (convertFromArgbPre) {
+        NSUInteger pixelLen = len >> 2;
+        for (NSUInteger i = 0; i < pixelLen; i++) {
+            LoadIntArgbPreTo1IntArgb((jint*)pSrc, 0, i, ((jint*)pDst)[i]);
+        }
+    } else {
+        memcpy(pDst, pSrc, len);
+    }
+}
+
+/**
+ * Specialized blit method for copying a native MTL "Surface" (pbuffer,
+ * window, etc.) to a system memory ("Sw") surface.
+ */
+void
+MTLBlitLoops_SurfaceToSwBlit(JNIEnv *env, MTLContext *mtlc,
+                             jlong pSrcOps, jlong pDstOps, jint dsttype,
+                             jint srcx, jint srcy, jint dstx, jint dsty,
+                             jint width, jint height)
+{
+    J2dTraceLn6(J2D_TRACE_VERBOSE, "MTLBlitLoops_SurfaceToSwBlit: sx=%d sy=%d w=%d h=%d dx=%d dy=%d", srcx, srcy, width, height, dstx, dsty);
+
+    BMTLSDOps *srcOps = (BMTLSDOps *)jlong_to_ptr(pSrcOps);
+    SurfaceDataOps *dstOps = (SurfaceDataOps *)jlong_to_ptr(pDstOps);
+    SurfaceDataRasInfo srcInfo, dstInfo;
+
+    if (dsttype < 0 || dsttype >= sizeof(RasterFormatInfos)/ sizeof(MTLRasterFormatInfo)) {
+        J2dTraceLn1(J2D_TRACE_ERROR, "MTLBlitLoops_SurfaceToSwBlit: destination pixel format %d isn't supported", dsttype);
+        return;
+    }
+
+    if (width <= 0 || height <= 0) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLBlitLoops_SurfaceToSwBlit: dimensions are non-positive");
+        return;
+    }
+
+    RETURN_IF_NULL(srcOps);
+    RETURN_IF_NULL(dstOps);
+    RETURN_IF_NULL(mtlc);
+    RETURN_IF_TRUE(width < 0);
+    RETURN_IF_TRUE(height < 0);
+    NSUInteger w = (NSUInteger)width;
+    NSUInteger h = (NSUInteger)height;
+
+    srcInfo.bounds.x1 = srcx;
+    srcInfo.bounds.y1 = srcy;
+    srcInfo.bounds.x2 = srcx + width;
+    srcInfo.bounds.y2 = srcy + height;
+    dstInfo.bounds.x1 = dstx;
+    dstInfo.bounds.y1 = dsty;
+    dstInfo.bounds.x2 = dstx + width;
+    dstInfo.bounds.y2 = dsty + height;
+
+    if (dstOps->Lock(env, dstOps, &dstInfo, SD_LOCK_WRITE) != SD_SUCCESS) {
+        J2dTraceLn(J2D_TRACE_WARNING,"MTLBlitLoops_SurfaceToSwBlit: could not acquire dst lock");
+        return;
+    }
+
+    SurfaceData_IntersectBoundsXYXY(&srcInfo.bounds,
+                                    0, 0, srcOps->width, srcOps->height);
+
+    SurfaceData_IntersectBlitBounds(&dstInfo.bounds, &srcInfo.bounds,
+                                    srcx - dstx, srcy - dsty);
+
+    if (srcInfo.bounds.x2 > srcInfo.bounds.x1 &&
+        srcInfo.bounds.y2 > srcInfo.bounds.y1)
+    {
+        dstOps->GetRasInfo(env, dstOps, &dstInfo);
+        if (dstInfo.rasBase) {
+            void *pDst = dstInfo.rasBase;
+
+            srcx = srcInfo.bounds.x1;
+            srcy = srcInfo.bounds.y1;
+            dstx = dstInfo.bounds.x1;
+            dsty = dstInfo.bounds.y1;
+            width = srcInfo.bounds.x2 - srcInfo.bounds.x1;
+            height = srcInfo.bounds.y2 - srcInfo.bounds.y1;
+
+            pDst = PtrPixelsRow(pDst, dstx, dstInfo.pixelStride);
+            pDst = PtrPixelsRow(pDst, dsty, dstInfo.scanStride);
+
+            // Metal texture is (0,0) at left-top
+            srcx = srcOps->xOffset + srcx;
+            srcy = srcOps->yOffset + srcy;
+            NSUInteger byteLength = w * h * 4; // NOTE: assume that src format is MTLPixelFormatBGRA8Unorm
+
+            // Create MTLBuffer (or use static)
+            id<MTLBuffer> mtlbuf;
+#ifdef USE_STATIC_BUFFER
+            // NOTE: theoretically we can use newBufferWithBytesNoCopy, but pDst must be allocated with special API
+            // mtlbuf = [mtlc.device
+            //          newBufferWithBytesNoCopy:pDst
+            //                            length:(NSUInteger) srcLength
+            //                           options:MTLResourceCPUCacheModeDefaultCache
+            //                       deallocator:nil];
+            //
+            // see https://developer.apple.com/documentation/metal/mtldevice/1433382-newbufferwithbytesnocopy?language=objc
+            //
+            // The storage allocation of the returned new MTLBuffer object is the same as the pointer input value.
+            // The existing memory allocation must be covered by a single VM region, typically allocated with vm_allocate or mmap.
+            // Memory allocated by malloc is specifically disallowed.
+
+            static id<MTLBuffer> mtlIntermediateBuffer = nil; // need to reimplement with MTLBufferManager
+            if (mtlIntermediateBuffer == nil || mtlIntermediateBuffer.length < srcLength) {
+                if (mtlIntermediateBuffer != nil) {
+                    [mtlIntermediateBuffer release];
+                }
+                mtlIntermediateBuffer = [mtlc.device newBufferWithLength:srcLength options:MTLResourceCPUCacheModeDefaultCache];
+            }
+            mtlbuf = mtlIntermediateBuffer;
+#else // USE_STATIC_BUFFER
+            mtlbuf = [mtlc.device newBufferWithLength:byteLength options:MTLResourceStorageModeShared];
+#endif // USE_STATIC_BUFFER
+
+            // Read from surface into MTLBuffer
+            // NOTE: using of separate blitCommandBuffer can produce errors (draw into surface (with general cmd-buf)
+            // can be unfinished when reading raster from blit cmd-buf).
+            // Consider to use [mtlc.encoderManager createBlitEncoder] and [mtlc commitCommandBuffer:JNI_TRUE];
+            J2dTraceLn1(J2D_TRACE_VERBOSE, "MTLBlitLoops_SurfaceToSwBlit: source texture %p", srcOps->pTexture);
+
+            id<MTLCommandBuffer> cb = [mtlc createCommandBuffer];
+            id<MTLBlitCommandEncoder> blitEncoder = [cb blitCommandEncoder];
+            [blitEncoder copyFromTexture:srcOps->pTexture
+                            sourceSlice:0
+                            sourceLevel:0
+                           sourceOrigin:MTLOriginMake(srcx, srcy, 0)
+                             sourceSize:MTLSizeMake(w, h, 1)
+                               toBuffer:mtlbuf
+                      destinationOffset:0 /*offset already taken in: pDst = PtrPixelsRow(pDst, dstx,  dstInfo.pixelStride)*/
+                 destinationBytesPerRow:w*4
+               destinationBytesPerImage:byteLength];
+            [blitEncoder endEncoding];
+
+            // Commit and wait for reading complete
+            [cb commit];
+            [cb waitUntilCompleted];
+
+            // Perform conversion if necessary
+            BOOL convertFromPre = !RasterFormatInfos[dsttype].isPremult && !srcOps->isOpaque;
+
+            if ((dstInfo.scanStride == w * dstInfo.pixelStride) &&
+                (height == (dstInfo.bounds.y2 - dstInfo.bounds.y1))) {
+                // mtlbuf.contents have same dimensions as of pDst
+                copyFromMTLBuffer(pDst, mtlbuf, 0, byteLength, convertFromPre);
+            } else {
+                // mtlbuf.contents have smaller dimensions than pDst
+                // copy each row from mtlbuf.contents at appropriate position in pDst
+                // Note : pDst is already addjusted for offsets using PtrAddBytes above
+
+                NSUInteger rowSize = w * dstInfo.pixelStride;
+                for (int y = 0; y < height; y++) {
+                    copyFromMTLBuffer(pDst, mtlbuf, y * rowSize, rowSize, convertFromPre);
+                    pDst = PtrAddBytes(pDst, dstInfo.scanStride);
+                }
+            }
+
+#ifndef USE_STATIC_BUFFER
+            [mtlbuf release];
+#endif // USE_STATIC_BUFFER
+        }
+        SurfaceData_InvokeRelease(env, dstOps, &dstInfo);
+    }
+    SurfaceData_InvokeUnlock(env, dstOps, &dstInfo);
+}
+
+void
+MTLBlitLoops_CopyArea(JNIEnv *env,
+                      MTLContext *mtlc, BMTLSDOps *dstOps,
+                      jint x, jint y, jint width, jint height,
+                      jint dx, jint dy)
+{
+#ifdef DEBUG
+    J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, "MTLBlitLoops_CopyArea: bdst=%p [tex=%p] %dx%d | src (%d, %d), %dx%d -> dst (%d, %d)",
+            dstOps, dstOps->pTexture, ((id<MTLTexture>)dstOps->pTexture).width, ((id<MTLTexture>)dstOps->pTexture).height, x, y, width, height, dx, dy);
+#endif //DEBUG
+    jint texWidth = ((id<MTLTexture>)dstOps->pTexture).width;
+    jint texHeight = ((id<MTLTexture>)dstOps->pTexture).height;
+
+    SurfaceDataBounds srcBounds, dstBounds;
+    srcBounds.x1 = x;
+    srcBounds.y1 = y;
+    srcBounds.x2 = srcBounds.x1 + width;
+    srcBounds.y2 = srcBounds.y1 + height;
+    dstBounds.x1 = x + dx;
+    dstBounds.y1 = y + dy;
+    dstBounds.x2 = dstBounds.x1 + width;
+    dstBounds.y2 = dstBounds.y1 + height;
+
+    SurfaceData_IntersectBoundsXYXY(&srcBounds, 0, 0, texWidth, texHeight);
+    SurfaceData_IntersectBoundsXYXY(&dstBounds, 0, 0, texWidth, texHeight);
+    SurfaceData_IntersectBlitBounds(&dstBounds, &srcBounds, -dx, -dy);
+
+    int srcWidth = (srcBounds.x2 - srcBounds.x1);
+    int srcHeight = (srcBounds.y2 - srcBounds.y1);
+
+   if ((srcBounds.x1 < srcBounds.x2 && srcBounds.y1 < srcBounds.y2) &&
+       (dstBounds.x1 < dstBounds.x2 && dstBounds.y1 < dstBounds.y2))
+   {
+        @autoreleasepool {
+            id<MTLCommandBuffer> cb = [mtlc createCommandBuffer];
+            id<MTLBlitCommandEncoder> blitEncoder = [cb blitCommandEncoder];
+
+            // Create an intrermediate buffer
+            int totalBuffsize = srcWidth * srcHeight * 4;
+            id <MTLBuffer> buff = [[mtlc.device newBufferWithLength:totalBuffsize options:MTLResourceStorageModePrivate] autorelease];
+
+            [blitEncoder copyFromTexture:dstOps->pTexture
+                    sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(srcBounds.x1, srcBounds.y1, 0) sourceSize:MTLSizeMake(srcWidth, srcHeight, 1)
+                     toBuffer:buff destinationOffset:0 destinationBytesPerRow:(srcWidth * 4) destinationBytesPerImage:totalBuffsize];
+
+            [blitEncoder copyFromBuffer:buff
+                    sourceOffset:0 sourceBytesPerRow:srcWidth*4 sourceBytesPerImage:totalBuffsize sourceSize:MTLSizeMake(srcWidth, srcHeight, 1)
+                    toTexture:dstOps->pTexture destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(dstBounds.x1, dstBounds.y1, 0)];
+            [blitEncoder endEncoding];
+
+            [cb commit];
+        }
+   }
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBufImgOps.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBufImgOps.h
new file mode 100644
index 00000000000..18d822c377f
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBufImgOps.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLBufImgOps_h_Included
+#define MTLBufImgOps_h_Included
+
+#include "MTLContext.h"
+
+@interface MTLRescaleOp : NSObject
+- (id)init:(jboolean)isNonPremult factors:(unsigned char *)factors offsets:(unsigned char *)offsets;
+- (jfloat *)getScaleFactors;
+- (jfloat *)getOffsets;
+- (NSString *)getDescription; // creates autorelease string
+
+@property (readonly) jboolean isNonPremult;
+@end
+
+@interface MTLConvolveOp : NSObject
+- (id)init:(jboolean)edgeZeroFill kernelWidth:(jint)kernelWidth
+                                 kernelHeight:(jint)kernelHeight
+                                     srcWidth:(jint)srcWidth
+                                    srcHeight:(jint)srcHeight
+                                       kernel:(unsigned char *)kernel
+                                       device:(id<MTLDevice>)device;
+- (void) dealloc;
+
+- (id<MTLBuffer>) getBuffer;
+- (const float *) getImgEdge;
+- (NSString *)getDescription; // creates autorelease string
+
+@property (readonly) jboolean isEdgeZeroFill;
+@property (readonly) int kernelSize;
+@end
+
+@interface MTLLookupOp : NSObject
+- (id)init:(jboolean)nonPremult shortData:(jboolean)shortData
+                                 numBands:(jint)numBands
+                               bandLength:(jint)bandLength
+                                   offset:(jint)offset
+                              tableValues:(void *)tableValues
+                                   device:(id<MTLDevice>)device;
+- (void) dealloc;
+
+- (jfloat *)getOffset;
+- (id<MTLTexture>) getLookupTexture;
+- (NSString *)getDescription; // creates autorelease string
+
+@property (readonly) jboolean isUseSrcAlpha;
+@property (readonly) jboolean isNonPremult;
+@end
+
+#endif /* MTLBufImgOps_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBufImgOps.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBufImgOps.m
new file mode 100644
index 00000000000..a36e211f748
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBufImgOps.m
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <jlong.h>
+
+#include "MTLBufImgOps.h"
+#include "MTLContext.h"
+#include "MTLRenderQueue.h"
+#include "MTLSurfaceDataBase.h"
+#include "GraphicsPrimitiveMgr.h"
+
+@implementation MTLRescaleOp {
+    jboolean _isNonPremult;
+    jfloat _normScaleFactors[4];
+    jfloat _normOffsets[4];
+}
+
+-(jfloat *) getScaleFactors {
+    return _normScaleFactors;
+}
+-(jfloat *) getOffsets {
+    return _normOffsets;
+}
+
+- (id)init:(jboolean)isNonPremult factors:(unsigned char *)factors offsets:(unsigned char *)offsets {
+    self = [super init];
+    if (self) {
+        J2dTraceLn1(J2D_TRACE_INFO,"Created MTLRescaleOp: isNonPremult=%d", isNonPremult);
+
+        _isNonPremult = isNonPremult;
+        _normScaleFactors[0] = NEXT_FLOAT(factors);
+        _normScaleFactors[1] = NEXT_FLOAT(factors);
+        _normScaleFactors[2] = NEXT_FLOAT(factors);
+        _normScaleFactors[3] = NEXT_FLOAT(factors);
+        _normOffsets[0] = NEXT_FLOAT(offsets);
+        _normOffsets[1] = NEXT_FLOAT(offsets);
+        _normOffsets[2] = NEXT_FLOAT(offsets);
+        _normOffsets[3] = NEXT_FLOAT(offsets);
+    }
+    return self;
+}
+
+- (NSString *)getDescription {
+    return [NSString stringWithFormat:@"rescale: nonPremult=%d", _isNonPremult];
+}
+@end
+
+@implementation MTLConvolveOp {
+    id<MTLBuffer> _buffer;
+    float _imgEdge[4];
+    int _kernelSize;
+    jboolean _isEdgeZeroFill;
+}
+
+- (id)init:(jboolean)edgeZeroFill kernelWidth:(jint)kernelWidth
+                                 kernelHeight:(jint)kernelHeight
+                                     srcWidth:(jint)srcWidth
+                                    srcHeight:(jint)srcHeight
+                                       kernel:(unsigned char *)kernel
+                                       device:(id<MTLDevice>)device {
+    self = [super init];
+    if (self) {
+        J2dTraceLn2(J2D_TRACE_INFO,"Created MTLConvolveOp: kernelW=%d kernelH=%d", kernelWidth, kernelHeight);
+        _isEdgeZeroFill = edgeZeroFill;
+
+        _kernelSize = kernelWidth * kernelHeight;
+        _buffer = [device newBufferWithLength:_kernelSize*sizeof(vector_float3) options:MTLResourceStorageModeShared];
+
+        float * kernelVals = [_buffer contents];
+        int kIndex = 0;
+        for (int i = -kernelHeight/2; i < kernelHeight/2+1; i++) {
+            for (int j = -kernelWidth/2; j < kernelWidth/2+1; j++) {
+                kernelVals[kIndex+0] = j/(float)srcWidth;
+                kernelVals[kIndex+1] = i/(float)srcHeight;
+                kernelVals[kIndex+2] = NEXT_FLOAT(kernel);
+                kIndex += 3;
+            }
+        }
+
+        _imgEdge[0] = (kernelWidth/2)/(float)srcWidth;
+        _imgEdge[1] = (kernelHeight/2)/(float)srcHeight;
+        _imgEdge[2] = 1 - _imgEdge[0];
+        _imgEdge[3] = 1 - _imgEdge[1];
+    }
+    return self;
+}
+
+- (void) dealloc {
+    [_buffer release];
+    [super dealloc];
+}
+
+- (id<MTLBuffer>) getBuffer {
+    return _buffer;
+}
+
+- (const float *) getImgEdge {
+    return _imgEdge;
+}
+
+- (NSString *)getDescription {
+    return [NSString stringWithFormat:@"convolve: isEdgeZeroFill=%d", _isEdgeZeroFill];
+}
+@end
+
+
+@implementation MTLLookupOp {
+    float _offset[4];
+    jboolean _isUseSrcAlpha;
+    jboolean _isNonPremult;
+
+    id<MTLTexture> _lookupTex;
+}
+
+- (id)init:(jboolean)nonPremult shortData:(jboolean)shortData
+                                 numBands:(jint)numBands
+                               bandLength:(jint)bandLength
+                                   offset:(jint)offset
+                              tableValues:(void *)tableValues
+                                   device:(id<MTLDevice>)device {
+    self = [super init];
+    if (self) {
+        J2dTraceLn4(J2D_TRACE_INFO,"Created MTLLookupOp: short=%d num=%d len=%d off=%d",
+                    shortData, numBands, bandLength, offset);
+
+        _isUseSrcAlpha = numBands != 4;
+        _isNonPremult = nonPremult;
+
+        _offset[0] = offset / 255.0f;
+        _offset[1] = _offset[0];
+        _offset[2] = _offset[0];
+        _offset[3] = _offset[0];
+
+        MTLTextureDescriptor *textureDescriptor =
+                [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatA8Unorm
+                                                                   width:(NSUInteger)256
+                                                                  height:(NSUInteger)4
+                                                               mipmapped:NO];
+
+        _lookupTex = [device newTextureWithDescriptor:textureDescriptor];
+
+        void *bands[4];
+        for (int i = 0; i < 4; i++) {
+            bands[i] = NULL;
+        }
+        int bytesPerElem = (shortData ? 2 : 1);
+        if (numBands == 1) {
+            // replicate the single band for R/G/B; alpha band is unused
+            for (int i = 0; i < 3; i++) {
+                bands[i] = tableValues;
+            }
+            bands[3] = NULL;
+        } else if (numBands == 3) {
+            // user supplied band for each of R/G/B; alpha band is unused
+            for (int i = 0; i < 3; i++) {
+                bands[i] = PtrPixelsBand(tableValues, i, bandLength, bytesPerElem);
+            }
+            bands[3] = NULL;
+        } else if (numBands == 4) {
+            // user supplied band for each of R/G/B/A
+            for (int i = 0; i < 4; i++) {
+                bands[i] = PtrPixelsBand(tableValues, i, bandLength, bytesPerElem);
+            }
+        }
+
+        for (int i = 0; i < 4; i++) {
+            if (bands[i] == NULL)
+                continue;
+
+            MTLRegion region = {
+                    {0, i, 0},
+                    {bandLength, 1,1}
+            };
+
+            [_lookupTex replaceRegion:region
+                                    mipmapLevel:0
+                                      withBytes:bands[i]
+                                    bytesPerRow:bandLength*bytesPerElem];
+        }
+    }
+    return self;
+}
+
+- (void) dealloc {
+    [_lookupTex release];
+    [super dealloc];
+}
+
+- (jfloat *) getOffset {
+    return _offset;
+}
+
+- (id<MTLTexture>) getLookupTexture {
+    return _lookupTex;
+}
+
+- (NSString *)getDescription {
+    return [NSString stringWithFormat:@"lookup: offset=%f", _offset[0]];
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLClip.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLClip.h
new file mode 100644
index 00000000000..1233e577ceb
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLClip.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import <limits.h>
+#ifndef MTLClip_h_Included
+#define MTLClip_h_Included
+
+#import <Metal/Metal.h>
+
+#include <jni.h>
+
+#include "MTLSurfaceDataBase.h"
+
+enum Clip {
+    NO_CLIP,
+    RECT_CLIP,
+    SHAPE_CLIP
+};
+
+@class MTLContext;
+@class MTLPipelineStatesStorage;
+
+/**
+ * The MTLClip class represents clip mode (rect or stencil)
+ * */
+
+@interface MTLClip : NSObject
+@property (readonly) id<MTLTexture> stencilTextureRef;
+@property (readonly) BOOL stencilMaskGenerationInProgress;
+@property NSUInteger shapeX;
+@property NSUInteger shapeY;
+@property NSUInteger shapeWidth;
+@property NSUInteger shapeHeight;
+
+- (id)init;
+- (BOOL)isEqual:(MTLClip *)other; // used to compare requested with cached
+- (void)copyFrom:(MTLClip *)other; // used to save cached
+
+- (BOOL)isShape;
+- (BOOL)isRect;
+
+// returns null when clipType != RECT_CLIP
+- (const MTLScissorRect *) getRect;
+
+- (void)reset;
+- (void)resetStencilState;
+- (void)setClipRectX1:(jint)x1 Y1:(jint)y1 X2:(jint)x2 Y2:(jint)y2;
+- (void)beginShapeClip:(BMTLSDOps *)dstOps context:(MTLContext *)mtlc;
+- (void)endShapeClip:(BMTLSDOps *)dstOps context:(MTLContext *)mtlc;
+
+- (void)setScissorOrStencil:(id<MTLRenderCommandEncoder>)encoder
+                  destWidth:(NSUInteger)dw
+                 destHeight:(NSUInteger)dh
+                     device:(id<MTLDevice>)device;
+
+- (void)setMaskGenerationPipelineState:(id<MTLRenderCommandEncoder>)encoder
+                             destWidth:(NSUInteger)dw
+                            destHeight:(NSUInteger)dh
+                  pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage;
+
+- (NSString *)getDescription __unused; // creates autorelease string
+@end
+
+#endif // MTLClip_h_Included
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLClip.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLClip.m
new file mode 100644
index 00000000000..32c3e94f2ba
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLClip.m
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "MTLClip.h"
+
+#include "MTLContext.h"
+#include "MTLStencilManager.h"
+#include "common.h"
+
+static MTLRenderPipelineDescriptor * templateStencilPipelineDesc = nil;
+
+static void initTemplatePipelineDescriptors() {
+    if (templateStencilPipelineDesc != nil)
+        return;
+
+    MTLVertexDescriptor *vertDesc = [[MTLVertexDescriptor new] autorelease];
+    vertDesc.attributes[VertexAttributePosition].format = MTLVertexFormatFloat2;
+    vertDesc.attributes[VertexAttributePosition].offset = 0;
+    vertDesc.attributes[VertexAttributePosition].bufferIndex = MeshVertexBuffer;
+    vertDesc.layouts[MeshVertexBuffer].stride = sizeof(struct Vertex);
+    vertDesc.layouts[MeshVertexBuffer].stepRate = 1;
+    vertDesc.layouts[MeshVertexBuffer].stepFunction = MTLVertexStepFunctionPerVertex;
+
+    templateStencilPipelineDesc = [MTLRenderPipelineDescriptor new];
+    templateStencilPipelineDesc.sampleCount = 1;
+    templateStencilPipelineDesc.vertexDescriptor = vertDesc;
+    templateStencilPipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatR8Uint; // A byte buffer format
+    templateStencilPipelineDesc.label = @"template_stencil";
+}
+
+@implementation MTLClip {
+    jint _clipType;
+    MTLScissorRect  _clipRect;
+    MTLContext* _mtlc;
+    BMTLSDOps*  _dstOps;
+    BOOL _stencilMaskGenerationInProgress;
+    BOOL _clipReady;
+    MTLOrigin _clipShapeOrigin;
+    MTLSize _clipShapeSize;
+}
+
+- (id)init {
+    self = [super init];
+    if (self) {
+        _clipType = NO_CLIP;
+        _mtlc = nil;
+        _dstOps = NULL;
+        _stencilMaskGenerationInProgress = NO;
+        _clipReady = NO;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLClip *)other {
+    if (self == other)
+        return YES;
+    if (_stencilMaskGenerationInProgress == JNI_TRUE)
+        return other->_stencilMaskGenerationInProgress == JNI_TRUE;
+    if (_clipType != other->_clipType)
+        return NO;
+    if (_clipType == NO_CLIP)
+        return YES;
+    if (_clipType == RECT_CLIP) {
+        return _clipRect.x == other->_clipRect.x && _clipRect.y == other->_clipRect.y
+               && _clipRect.width == other->_clipRect.width && _clipRect.height == other->_clipRect.height;
+    }
+
+    // NOTE: can compare stencil-data pointers here
+    return YES;
+}
+
+- (BOOL)isShape {
+    return _clipType == SHAPE_CLIP;
+}
+
+- (BOOL)isRect __unused {
+    return _clipType == RECT_CLIP;
+}
+
+- (const MTLScissorRect * _Nullable) getRect {
+    return _clipType == RECT_CLIP ? &_clipRect : NULL;
+}
+
+- (void)copyFrom:(MTLClip *)other {
+    _clipType = other->_clipType;
+    _stencilMaskGenerationInProgress = other->_stencilMaskGenerationInProgress;
+    _dstOps = other->_dstOps;
+    _mtlc = other->_mtlc;
+    if (other->_clipType == RECT_CLIP) {
+        _clipRect = other->_clipRect;
+    }
+}
+
+- (void)reset {
+    _clipType = NO_CLIP;
+    _stencilMaskGenerationInProgress = JNI_FALSE;
+}
+
+
+- (void)setClipRectX1:(jint)x1 Y1:(jint)y1 X2:(jint)x2 Y2:(jint)y2 {
+    if (_clipType == SHAPE_CLIP) {
+        _dstOps = NULL;
+    }
+
+    if (x1 >= x2 || y1 >= y2) {
+        J2dTraceLn4(J2D_TRACE_ERROR, "MTLClip.setClipRect: invalid rect: x1=%d y1=%d x2=%d y2=%d", x1, y1, x2, y2);
+        _clipType = NO_CLIP;
+    }
+
+    const jint width = x2 - x1;
+    const jint height = y2 - y1;
+
+    J2dTraceLn4(J2D_TRACE_INFO, "MTLClip.setClipRect: x=%d y=%d w=%d h=%d", x1, y1, width, height);
+
+    _clipRect.x = (NSUInteger)((x1 >= 0) ? x1 : 0);
+    _clipRect.y = (NSUInteger)((y1 >= 0) ? y1 : 0);
+    _clipRect.width = (NSUInteger)((width >= 0) ? width : 0);
+    _clipRect.height = (NSUInteger)((height >= 0) ? height : 0);
+    _clipType = RECT_CLIP;
+}
+
+- (void)beginShapeClip:(BMTLSDOps *)dstOps context:(MTLContext *)mtlc {
+    _stencilMaskGenerationInProgress = YES;
+
+    if ((dstOps == NULL) || (dstOps->pStencilData == NULL) || (dstOps->pStencilTexture == NULL)) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLContext_beginShapeClip: stencil render target or stencil texture is NULL");
+        return;
+    }
+
+    // Clear the stencil render buffer & stencil texture
+    @autoreleasepool {
+        if (dstOps->width <= 0 || dstOps->height <= 0) {
+          return;
+        }
+
+        _clipShapeSize = MTLSizeMake(0, 0, 1);
+        _clipShapeOrigin = MTLOriginMake(0, 0, 0);
+
+        MTLRenderPassDescriptor* clearPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
+        // set color buffer properties
+        clearPassDescriptor.colorAttachments[0].texture = dstOps->pStencilData;
+        clearPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
+        clearPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0f, 0.0f,0.0f, 0.0f);
+
+        id<MTLCommandBuffer> commandBuf = [mtlc createCommandBuffer];
+        id <MTLRenderCommandEncoder> clearEncoder = [commandBuf renderCommandEncoderWithDescriptor:clearPassDescriptor];
+        [clearEncoder endEncoding];
+        [commandBuf commit];
+    }
+}
+
+- (void)endShapeClip:(BMTLSDOps *)dstOps context:(MTLContext *)mtlc {
+
+    if ((dstOps == NULL) || (dstOps->pStencilData == NULL) || (dstOps->pStencilTexture == NULL)) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLContext_endShapeClip: stencil render target or stencil texture is NULL");
+        return;
+    }
+
+    // Complete the rendering to the stencil buffer ------------
+    [mtlc.encoderManager endEncoder];
+
+    MTLCommandBufferWrapper* cbWrapper = [mtlc pullCommandBufferWrapper];
+
+    id<MTLCommandBuffer> commandBuffer = [cbWrapper getCommandBuffer];
+    [commandBuffer addCompletedHandler:^(id <MTLCommandBuffer> c) {
+        [cbWrapper release];
+    }];
+
+    [commandBuffer commit];
+
+    // Now the stencil data is ready, this needs to be used while rendering further
+    @autoreleasepool {
+        if (_clipShapeSize.width > 0 && _clipShapeSize.height > 0) {
+            id<MTLCommandBuffer> cb = [mtlc createCommandBuffer];
+            id<MTLBlitCommandEncoder> blitEncoder = [cb blitCommandEncoder];
+            [blitEncoder copyFromTexture:dstOps->pStencilData
+                             sourceSlice:0
+                             sourceLevel:0
+                            sourceOrigin:_clipShapeOrigin
+                              sourceSize:_clipShapeSize
+                                toBuffer:dstOps->pStencilDataBuf
+                       destinationOffset:0
+                  destinationBytesPerRow:_clipShapeSize.width
+                destinationBytesPerImage:_clipShapeSize.width*_clipShapeSize.height];
+            [blitEncoder endEncoding];
+            [cb commit];
+        }
+    }
+
+    _stencilMaskGenerationInProgress = JNI_FALSE;
+    _mtlc = mtlc;
+    _dstOps = dstOps;
+    _clipType = SHAPE_CLIP;
+    _clipReady = NO;
+}
+
+- (void)setMaskGenerationPipelineState:(id<MTLRenderCommandEncoder>)encoder
+                  destWidth:(NSUInteger)dw
+                 destHeight:(NSUInteger)dh
+       pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+
+    // A  PipelineState for rendering to a byte-buffered texture that will be used as a stencil
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:templateStencilPipelineDesc
+                                                                         vertexShaderId:@"vert_stencil"
+                                                                       fragmentShaderId:@"frag_stencil"];
+    [encoder setRenderPipelineState:pipelineState];
+
+    struct FrameUniforms uf; // color is ignored while writing to stencil buffer
+    memset(&uf, 0, sizeof(uf));
+    [encoder setVertexBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+
+    _clipRect.x = 0;
+    _clipRect.y = 0;
+    _clipRect.width = dw;
+    _clipRect.height = dh;
+
+    [encoder setScissorRect:_clipRect]; // just for insurance (to reset possible clip from previous drawing)
+}
+
+- (void)setScissorOrStencil:(id<MTLRenderCommandEncoder>)encoder
+                  destWidth:(NSUInteger)dw
+                 destHeight:(NSUInteger)dh
+                     device:(id<MTLDevice>)device
+{
+    if (_clipType == NO_CLIP || _clipType == SHAPE_CLIP) {
+        _clipRect.x = 0;
+        _clipRect.y = 0;
+        _clipRect.width = dw;
+        _clipRect.height = dh;
+    }
+
+    // Clamping clip rect to the destination area
+    MTLScissorRect rect = _clipRect;
+
+    if (rect.x > dw) {
+        rect.x = dw;
+    }
+
+    if (rect.y > dh) {
+        rect.y = dh;
+    }
+
+    if (rect.x + rect.width > dw) {
+        rect.width = dw - rect.x;
+    }
+
+    if (rect.y + rect.height > dh) {
+        rect.height = dh - rect.y;
+    }
+
+    [encoder setScissorRect:rect];
+    if (_clipType == NO_CLIP || _clipType == RECT_CLIP) {
+        // NOTE: It seems that we can use the same encoder (with disabled stencil test) when mode changes from SHAPE to RECT.
+        // But [encoder setDepthStencilState:nil] causes crash, so we have to recreate encoder in such case.
+        // So we can omit [encoder setDepthStencilState:nil] here.
+        return;
+    }
+
+    if (_clipType == SHAPE_CLIP) {
+        // Enable stencil test
+        [encoder setDepthStencilState:_mtlc.stencilManager.stencilState];
+        [encoder setStencilReferenceValue:0xFF];
+    }
+}
+
+- (NSString *)getDescription __unused {
+    if (_clipType == NO_CLIP) {
+        return @"NO_CLIP";
+    }
+    if (_clipType == RECT_CLIP) {
+        return [NSString stringWithFormat:@"RECT_CLIP [%lu,%lu - %lux%lu]", _clipRect.x, _clipRect.y, _clipRect.width, _clipRect.height];
+    }
+    return [NSString stringWithFormat:@"SHAPE_CLIP"];
+}
+
+- (id<MTLTexture>) stencilTextureRef {
+    if (_dstOps == NULL) return nil;
+
+    id <MTLTexture> _stencilTextureRef = _dstOps->pStencilTexture;
+
+    if (!_clipReady) {
+        @autoreleasepool {
+
+            MTLRenderPassDescriptor* clearPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
+            // set color buffer properties
+            clearPassDescriptor.stencilAttachment.texture = _stencilTextureRef;
+            clearPassDescriptor.stencilAttachment.clearStencil = 0;
+            clearPassDescriptor.stencilAttachment.loadAction = MTLLoadActionClear;
+
+            id<MTLCommandBuffer> commandBuf = [_mtlc createCommandBuffer];
+            id <MTLRenderCommandEncoder> clearEncoder = [commandBuf renderCommandEncoderWithDescriptor:clearPassDescriptor];
+            [clearEncoder endEncoding];
+            [commandBuf commit];
+
+            id <MTLCommandBuffer> cb = [_mtlc createCommandBuffer];
+            id <MTLBlitCommandEncoder> blitEncoder = [cb blitCommandEncoder];
+            id <MTLBuffer> _stencilDataBufRef = _dstOps->pStencilDataBuf;
+            [blitEncoder copyFromBuffer:_stencilDataBufRef
+                           sourceOffset:0
+                      sourceBytesPerRow:_clipShapeSize.width
+                    sourceBytesPerImage:_clipShapeSize.width * _clipShapeSize.height
+                             sourceSize:_clipShapeSize
+                              toTexture:_stencilTextureRef
+                       destinationSlice:0
+                       destinationLevel:0
+                      destinationOrigin:_clipShapeOrigin];
+            [blitEncoder endEncoding];
+            [cb commit];
+            _clipReady = YES;
+        }
+    }
+    return _stencilTextureRef;
+}
+
+- (NSUInteger)shapeX {
+    return _clipShapeOrigin.x;
+}
+
+- (void)setShapeX:(NSUInteger)shapeX {
+    _clipShapeOrigin.x = shapeX;
+}
+
+- (NSUInteger)shapeY {
+    return _clipShapeOrigin.y;
+}
+
+- (void)setShapeY:(NSUInteger)shapeY {
+    _clipShapeOrigin.y = shapeY;
+}
+
+- (NSUInteger)shapeWidth {
+    return _clipShapeSize.width;
+}
+
+- (void)setShapeWidth:(NSUInteger)shapeWidth {
+    _clipShapeSize.width = shapeWidth;
+}
+
+- (NSUInteger)shapeHeight {
+    return _clipShapeSize.height;
+}
+
+- (void)setShapeHeight:(NSUInteger)shapeHeight {
+    _clipShapeSize.height = shapeHeight;
+}
+
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLComposite.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLComposite.h
new file mode 100644
index 00000000000..e792ca1d4ab
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLComposite.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLComposite_h_Included
+#define MTLComposite_h_Included
+
+#import <Metal/Metal.h>
+
+#include <jni.h>
+
+#define FLT_EPS (0.001f)
+#define FLT_LT(x,y) ((x) < (y) - FLT_EPS)
+#define FLT_GE(x,y) ((x) >= (y) - FLT_EPS)
+#define FLT_LE(x,y) ((x) <= (y) + FLT_EPS)
+#define FLT_GT(x,y) ((x) > (y) + FLT_EPS)
+
+/**
+ * The MTLComposite class represents composite mode
+ * */
+
+@interface MTLComposite : NSObject
+- (id)init;
+- (BOOL)isEqual:(MTLComposite *)other; // used to compare requested with cached
+- (void)copyFrom:(MTLComposite *)other; // used to save cached
+
+- (void)setRule:(jint)rule; // sets extraAlpha=1
+- (void)setRule:(jint)rule extraAlpha:(jfloat)extraAlpha;
+- (void)reset;
+
+- (void)setXORComposite:(jint)color;
+- (void)setAlphaComposite:(jint)rule;
+
+
+- (jint)getCompositeState;
+- (jint)getRule;
+- (jint)getXorColor;
+- (jfloat)getExtraAlpha;
+
+- (NSString *)getDescription; // creates autorelease string
+@end
+
+#endif // MTLComposite_h_Included
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLComposite.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLComposite.m
new file mode 100644
index 00000000000..83a9230b168
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLComposite.m
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "MTLComposite.h"
+#include "sun_java2d_SunGraphics2D.h"
+#include "java_awt_AlphaComposite.h"
+
+@implementation MTLComposite {
+    jint _compState;
+    jint _compositeRule;
+    jint _xorPixel;
+    jfloat _extraAlpha;
+}
+
+- (id)init {
+    self = [super init];
+    if (self) {
+        _compositeRule = -1;
+        _compState = -1;
+        _xorPixel = 0;
+        _extraAlpha = 1;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLComposite *)other {
+    if (self == other)
+        return YES;
+
+    if (_compState == other->_compState) {
+        if (_compState == sun_java2d_SunGraphics2D_COMP_XOR) {
+            return _xorPixel == other->_xorPixel;
+        }
+
+        if (_compState == sun_java2d_SunGraphics2D_COMP_ALPHA) {
+            return _extraAlpha == other->_extraAlpha
+                   && _compositeRule == other->_compositeRule;
+        }
+    }
+
+    return NO;
+}
+
+- (void)copyFrom:(MTLComposite *)other {
+    _extraAlpha = other->_extraAlpha;
+    _compositeRule = other->_compositeRule;
+    _compState = other->_compState;
+    _xorPixel = other->_xorPixel;
+}
+
+- (void)setRule:(jint)rule {
+    _extraAlpha = 1.f;
+    _compositeRule = rule;
+}
+
+- (void)setRule:(jint)rule extraAlpha:(jfloat)extraAlpha {
+    _compState = sun_java2d_SunGraphics2D_COMP_ALPHA;
+    _extraAlpha = extraAlpha;
+    _compositeRule = rule;
+}
+
+- (void)reset {
+    _compState = sun_java2d_SunGraphics2D_COMP_ISCOPY;
+    _compositeRule = java_awt_AlphaComposite_SRC;
+    _extraAlpha = 1.f;
+}
+
+- (jint)getRule {
+    return _compositeRule;
+}
+
+- (NSString *)getDescription {
+    const char * result = "";
+    switch (_compositeRule) {
+        case java_awt_AlphaComposite_CLEAR:
+        {
+            result = "CLEAR";
+        }
+            break;
+        case java_awt_AlphaComposite_SRC:
+        {
+            result = "SRC";
+        }
+            break;
+        case java_awt_AlphaComposite_DST:
+        {
+            result = "DST";
+        }
+            break;
+        case java_awt_AlphaComposite_SRC_OVER:
+        {
+            result = "SRC_OVER";
+        }
+            break;
+        case java_awt_AlphaComposite_DST_OVER:
+        {
+            result = "DST_OVER";
+        }
+            break;
+        case java_awt_AlphaComposite_SRC_IN:
+        {
+            result = "SRC_IN";
+        }
+            break;
+        case java_awt_AlphaComposite_DST_IN:
+        {
+            result = "DST_IN";
+        }
+            break;
+        case java_awt_AlphaComposite_SRC_OUT:
+        {
+            result = "SRC_OUT";
+        }
+            break;
+        case java_awt_AlphaComposite_DST_OUT:
+        {
+            result = "DST_OUT";
+        }
+            break;
+        case java_awt_AlphaComposite_SRC_ATOP:
+        {
+            result = "SRC_ATOP";
+        }
+            break;
+        case java_awt_AlphaComposite_DST_ATOP:
+        {
+            result = "DST_ATOP";
+        }
+            break;
+        case java_awt_AlphaComposite_XOR:
+        {
+            result = "XOR";
+        }
+            break;
+        default:
+            result = "UNKNOWN";
+            break;
+    }
+    const double epsilon = 0.001f;
+    if (fabs(_extraAlpha - 1.f) > epsilon) {
+        return [NSString stringWithFormat:@"%s [%1.2f]", result, _extraAlpha];
+    }
+    return [NSString stringWithFormat:@"%s", result];
+}
+
+- (void)setAlphaComposite:(jint)rule {
+    _compState = sun_java2d_SunGraphics2D_COMP_ALPHA;
+    [self setRule:rule];
+}
+
+
+- (jint)getCompositeState {
+    return _compState;
+}
+
+
+-(void)setXORComposite:(jint)color {
+    _compState = sun_java2d_SunGraphics2D_COMP_XOR;
+    _xorPixel = color;
+}
+
+-(jint)getXorColor {
+    return _xorPixel;
+}
+
+- (jfloat)getExtraAlpha {
+    return _extraAlpha;
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.h
new file mode 100644
index 00000000000..d684f983eeb
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLContext_h_Included
+#define MTLContext_h_Included
+
+#include "sun_java2d_pipe_BufferedContext.h"
+#include "sun_java2d_metal_MTLContext_MTLContextCaps.h"
+
+#import <Metal/Metal.h>
+
+#include "MTLTexturePool.h"
+#include "MTLPipelineStatesStorage.h"
+#include "MTLTransform.h"
+#include "MTLComposite.h"
+#include "MTLPaints.h"
+#include "MTLClip.h"
+#include "EncoderManager.h"
+#include "MTLSamplerManager.h"
+
+@class MTLStencilManager;
+
+// Constant from
+// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
+#define MTL_GPU_FAMILY_MAC_TXT_SIZE 16384
+
+/**
+ * The MTLCommandBufferWrapper class contains command buffer and
+ * associated resources that will be released in completion handler
+ * */
+@interface MTLCommandBufferWrapper : NSObject
+- (id<MTLCommandBuffer>) getCommandBuffer;
+- (void) onComplete; // invoked from completion handler in some pooled thread
+- (void) registerPooledTexture:(MTLPooledTextureHandle *)handle;
+@end
+
+/**
+ * The MTLContext class contains cached state relevant to the native
+ * MTL context stored within the native ctxInfo field.  Each Java-level
+ * MTLContext object is associated with a native-level MTLContext class.
+ * */
+@interface MTLContext : NSObject
+@property (readonly) MTLComposite * composite;
+@property (readwrite, retain) MTLPaint * paint;
+@property (readonly) MTLTransform * transform;
+@property (readonly) MTLClip * clip;
+
+@property jint          textureFunction;
+@property jboolean      vertexCacheEnabled;
+@property jboolean      aaEnabled;
+
+@property (readonly, strong)   id<MTLDevice>   device;
+@property (strong) id<MTLCommandQueue>         commandQueue;
+@property (strong) id<MTLCommandQueue>         blitCommandQueue;
+@property (strong) id<MTLBuffer>               vertexBuffer;
+
+@property (readonly) EncoderManager * encoderManager;
+@property (readonly) MTLSamplerManager * samplerManager;
+@property (readonly) MTLStencilManager * stencilManager;
+
+@property (strong)MTLPipelineStatesStorage*   pipelineStateStorage;
+@property (strong)MTLTexturePool*             texturePool;
+
+- (MTLCommandBufferWrapper *) getCommandBufferWrapper; // creates command buffer wrapper (when doesn't exist)
+- (MTLCommandBufferWrapper *) pullCommandBufferWrapper; // returns current buffer wrapper with loosing object ownership
+
+/**
+ * Fetches the MTLContext associated with the given destination surface,
+ * makes the context current for those surfaces, updates the destination
+ * viewport, and then returns a pointer to the MTLContext.
+ */
++ (MTLContext*) setSurfacesEnv:(JNIEnv*)env src:(jlong)pSrc dst:(jlong)pDst;
+
+- (id)initWithDevice:(id<MTLDevice>)d shadersLib:(NSString*)shadersLib;
+- (void)dealloc;
+
+/**
+ * Resets the current clip state (disables both scissor and depth tests).
+ */
+- (void)resetClip;
+
+/**
+ * Sets the Metal scissor bounds to the provided rectangular clip bounds.
+ */
+- (void)setClipRectX1:(jint)x1 Y1:(jint)y1 X2:(jint)x2 Y2:(jint)y2;
+
+- (const MTLScissorRect *)clipRect;
+
+/**
+ * Sets up a complex (shape) clip using the Metal stencil buffer.  This
+ * method prepares the stencil buffer so that the clip Region spans can
+ * be "rendered" into it.  The stencil buffer is first cleared, then the
+ * stencil func is setup so that when we render the clip spans,
+ * nothing is rendered into the color buffer, but for each pixel that would
+ * be rendered, a 0xFF value is placed into that location in the stencil
+ * buffer.  With stencil test enabled, pixels will only be rendered into the
+ * color buffer if the corresponding value at that (x,y) location in the
+ * stencil buffer is equal to 0xFF.
+ */
+- (void)beginShapeClip:(BMTLSDOps *)dstOps;
+
+/**
+ * Finishes setting up the shape clip by resetting the stencil func
+ * so that future rendering operations will once again be encoded for the
+ * color buffer (while respecting the clip set up in the stencil buffer).
+ */
+- (void)endShapeClip:(BMTLSDOps *)dstOps;
+
+/**
+ * Resets all Metal compositing state (disables blending and logic
+ * operations).
+ */
+- (void)resetComposite;
+
+/**
+ * Initializes the Metal blending state.  XOR mode is disabled and the
+ * appropriate blend functions are setup based on the AlphaComposite rule
+ * constant.
+ */
+- (void)setAlphaCompositeRule:(jint)rule extraAlpha:(jfloat)extraAlpha
+                        flags:(jint)flags;
+
+/**
+ * Returns autorelease string with composite description (for debugging only)
+ */
+- (NSString*)getCompositeDescription;
+
+/**
+ * Returns autorelease string with paint description (for debugging only)
+ */
+- (NSString*)getPaintDescription;
+
+/**
+ * Initializes the Metal logic op state to XOR mode.  Blending is disabled
+ * before enabling logic op mode.  The XOR pixel value will be applied
+ * later in the MTLContext_SetColor() method.
+ */
+- (void)setXorComposite:(jint)xorPixel;
+- (jboolean)useXORComposite;
+
+/**
+ * Resets the Metal transform state back to the identity matrix.
+ */
+- (void)resetTransform;
+
+/**
+ * Initializes the Metal transform state by setting the modelview transform
+ * using the given matrix parameters.
+ *
+ * REMIND: it may be worthwhile to add serial id to AffineTransform, so we
+ *         could do a quick check to see if the xform has changed since
+ *         last time... a simple object compare won't suffice...
+ */
+- (void)setTransformM00:(jdouble) m00 M10:(jdouble) m10
+                    M01:(jdouble) m01 M11:(jdouble) m11
+                    M02:(jdouble) m02 M12:(jdouble) m12;
+
+- (void)reset;
+- (void)resetPaint;
+- (void)setColorPaint:(int)pixel;
+- (void)setGradientPaintUseMask:(jboolean)useMask
+                         cyclic:(jboolean)cyclic
+                             p0:(jdouble)p0
+                             p1:(jdouble)p1
+                             p3:(jdouble)p3
+                         pixel1:(jint)pixel1
+                         pixel2:(jint) pixel2;
+- (void)setLinearGradientPaint:(jboolean)useMask
+                   linear:(jboolean)linear
+              cycleMethod:(jint)cycleMethod
+                 numStops:(jint)numStops
+                       p0:(jfloat)p0
+                       p1:(jfloat)p1
+                       p3:(jfloat)p3
+                fractions:(jfloat *)fractions
+                   pixels:(jint *)pixels;
+- (void)setRadialGradientPaint:(jboolean)useMask
+                   linear:(jboolean)linear
+              cycleMethod:(jboolean)cycleMethod
+                 numStops:(jint)numStops
+                      m00:(jfloat)m00
+                      m01:(jfloat)m01
+                      m02:(jfloat)m02
+                      m10:(jfloat)m10
+                      m11:(jfloat)m11
+                      m12:(jfloat)m12
+                   focusX:(jfloat)focusX
+                fractions:(void *)fractions
+                   pixels:(void *)pixels;
+- (void)setTexturePaint:(jboolean)useMask
+           pSrcOps:(jlong)pSrcOps
+            filter:(jboolean)filter
+               xp0:(jdouble)xp0
+               xp1:(jdouble)xp1
+               xp3:(jdouble)xp3
+               yp0:(jdouble)yp0
+               yp1:(jdouble)yp1
+               yp3:(jdouble)yp3;
+
+// Sets current image conversion operation (instance of MTLConvolveOp, MTLRescaleOp, MTLLookupOp).
+// Used only in MTLIsoBlit (to blit image with some conversion). Pattern of usage: enableOp -> IsoBlit -> disableOp.
+// TODO: Need to remove it from MTLContext and pass it as an argument for IsoBlit (because it's more
+// simple and clear)
+-(void)setBufImgOp:(NSObject*)bufImgOp;
+
+-(NSObject*)getBufImgOp;
+
+- (id<MTLCommandBuffer>)createCommandBuffer;
+- (id<MTLCommandBuffer>)createBlitCommandBuffer;
+@end
+
+/**
+ * See BufferedContext.java for more on these flags...
+ */
+#define MTLC_NO_CONTEXT_FLAGS \
+    sun_java2d_pipe_BufferedContext_NO_CONTEXT_FLAGS
+#define MTLC_SRC_IS_OPAQUE    \
+    sun_java2d_pipe_BufferedContext_SRC_IS_OPAQUE
+#define MTLC_USE_MASK         \
+    sun_java2d_pipe_BufferedContext_USE_MASK
+
+#endif /* MTLContext_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.m
new file mode 100644
index 00000000000..b88d60957f3
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.m
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdlib.h>
+
+#include "sun_java2d_SunGraphics2D.h"
+
+#include "jlong.h"
+#import "MTLContext.h"
+#include "MTLRenderQueue.h"
+#import "MTLSamplerManager.h"
+#import "MTLStencilManager.h"
+
+
+extern jboolean MTLSD_InitMTLWindow(JNIEnv *env, MTLSDOps *mtlsdo);
+
+static struct TxtVertex verts[PGRAM_VERTEX_COUNT] = {
+        {{-1.0, 1.0}, {0.0, 0.0}},
+        {{1.0, 1.0}, {1.0, 0.0}},
+        {{1.0, -1.0}, {1.0, 1.0}},
+        {{1.0, -1.0}, {1.0, 1.0}},
+        {{-1.0, -1.0}, {0.0, 1.0}},
+        {{-1.0, 1.0}, {0.0, 0.0}}
+};
+
+MTLTransform* tempTransform = nil;
+
+@implementation MTLCommandBufferWrapper {
+    id<MTLCommandBuffer> _commandBuffer;
+    NSMutableArray * _pooledTextures;
+    NSLock* _lock;
+}
+
+- (id) initWithCommandBuffer:(id<MTLCommandBuffer>)cmdBuf {
+    self = [super init];
+    if (self) {
+        _commandBuffer = [cmdBuf retain];
+        _pooledTextures = [[NSMutableArray alloc] init];
+        _lock = [[NSLock alloc] init];
+    }
+    return self;
+}
+
+- (id<MTLCommandBuffer>) getCommandBuffer {
+    return _commandBuffer;
+}
+
+- (void) onComplete { // invoked from completion handler in some pooled thread
+    [_lock lock];
+    @try {
+        for (int c = 0; c < [_pooledTextures count]; ++c)
+            [[_pooledTextures objectAtIndex:c] releaseTexture];
+        [_pooledTextures removeAllObjects];
+    } @finally {
+        [_lock unlock];
+    }
+
+}
+
+- (void) registerPooledTexture:(MTLPooledTextureHandle *)handle {
+    [_lock lock];
+    @try {
+        [_pooledTextures addObject:handle];
+    } @finally {
+        [_lock unlock];
+    }
+}
+
+- (void) dealloc {
+    [self onComplete];
+
+    [_pooledTextures release];
+    _pooledTextures = nil;
+
+    [_commandBuffer release];
+    _commandBuffer = nil;
+
+    [_lock release];
+    _lock = nil;
+    [super dealloc];
+}
+
+@end
+
+@implementation MTLContext {
+    MTLCommandBufferWrapper * _commandBufferWrapper;
+
+    MTLComposite *     _composite;
+    MTLPaint *         _paint;
+    MTLTransform *     _transform;
+    MTLTransform *     _tempTransform;
+    MTLClip *          _clip;
+    NSObject*          _bufImgOp; // TODO: pass as parameter of IsoBlit
+
+    EncoderManager * _encoderManager;
+    MTLSamplerManager * _samplerManager;
+    MTLStencilManager * _stencilManager;
+}
+
+@synthesize textureFunction,
+            vertexCacheEnabled, aaEnabled, device, pipelineStateStorage,
+            commandQueue, blitCommandQueue, vertexBuffer,
+            texturePool, paint=_paint, encoderManager=_encoderManager,
+            samplerManager=_samplerManager, stencilManager=_stencilManager;
+
+extern void initSamplers(id<MTLDevice> device);
+
+- (id)initWithDevice:(id<MTLDevice>)d shadersLib:(NSString*)shadersLib {
+    self = [super init];
+    if (self) {
+        // Initialization code here.
+        device = d;
+
+        pipelineStateStorage = [[MTLPipelineStatesStorage alloc] initWithDevice:device shaderLibPath:shadersLib];
+        if (pipelineStateStorage == nil) {
+            J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLContext.initWithDevice(): Failed to initialize MTLPipelineStatesStorage.");
+            return nil;
+        }
+
+        texturePool = [[MTLTexturePool alloc] initWithDevice:device];
+
+        vertexBuffer = [device newBufferWithBytes:verts
+                                           length:sizeof(verts)
+                                          options:MTLResourceCPUCacheModeDefaultCache];
+
+        _encoderManager = [[EncoderManager alloc] init];
+        [_encoderManager setContext:self];
+        _samplerManager = [[MTLSamplerManager alloc] initWithDevice:device];
+        _stencilManager = [[MTLStencilManager alloc] initWithDevice:device];
+        _composite = [[MTLComposite alloc] init];
+        _paint = [[MTLPaint alloc] init];
+        _transform = [[MTLTransform alloc] init];
+        _clip = [[MTLClip alloc] init];
+        _bufImgOp = nil;
+
+        _commandBufferWrapper = nil;
+
+        // Create command queue
+        commandQueue = [device newCommandQueue];
+        blitCommandQueue = [device newCommandQueue];
+
+        _tempTransform = [[MTLTransform alloc] init];
+    }
+    return self;
+}
+
+- (void)dealloc {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.dealloc");
+
+    // TODO : Check that texturePool is completely released.
+    // texturePool content is released in MTLCommandBufferWrapper.onComplete()
+    //self.texturePool = nil;
+    self.vertexBuffer = nil;
+    self.commandQueue = nil;
+    self.blitCommandQueue = nil;
+    self.pipelineStateStorage = nil;
+
+    if (_encoderManager != nil) {
+        [_encoderManager release];
+        _encoderManager = nil;
+    }
+
+    if (_samplerManager != nil) {
+        [_samplerManager release];
+        _samplerManager = nil;
+    }
+
+    if (_stencilManager != nil) {
+        [_stencilManager release];
+        _stencilManager = nil;
+    }
+
+    if (_composite != nil) {
+        [_composite release];
+        _composite = nil;
+    }
+
+    if (_paint != nil) {
+        [_paint release];
+        _paint = nil;
+    }
+
+    if (_transform != nil) {
+        [_transform release];
+        _transform = nil;
+    }
+
+    if (_tempTransform != nil) {
+        [_tempTransform release];
+        _tempTransform = nil;
+    }
+
+    if (_clip != nil) {
+        [_clip release];
+        _clip = nil;
+    }
+
+    [super dealloc];
+}
+
+- (void) reset {
+    J2dTraceLn(J2D_TRACE_VERBOSE, "MTLContext : reset");
+
+    // Add code for context state reset here
+}
+
+ - (MTLCommandBufferWrapper *) getCommandBufferWrapper {
+    if (_commandBufferWrapper == nil) {
+        J2dTraceLn(J2D_TRACE_VERBOSE, "MTLContext : commandBuffer is NULL");
+        // NOTE: Command queues are thread-safe and allow multiple outstanding command buffers to be encoded simultaneously.
+        _commandBufferWrapper = [[MTLCommandBufferWrapper alloc] initWithCommandBuffer:[self.commandQueue commandBuffer]];// released in [layer blitTexture]
+    }
+    return _commandBufferWrapper;
+}
+
+- (MTLCommandBufferWrapper *) pullCommandBufferWrapper {
+    MTLCommandBufferWrapper * result = _commandBufferWrapper;
+    _commandBufferWrapper = nil;
+    return result;
+}
+
++ (MTLContext*) setSurfacesEnv:(JNIEnv*)env src:(jlong)pSrc dst:(jlong)pDst {
+    BMTLSDOps *srcOps = (BMTLSDOps *)jlong_to_ptr(pSrc);
+    BMTLSDOps *dstOps = (BMTLSDOps *)jlong_to_ptr(pDst);
+    MTLContext *mtlc = NULL;
+
+    if (srcOps == NULL || dstOps == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLContext_SetSurfaces: ops are null");
+        return NULL;
+    }
+
+    J2dTraceLn6(J2D_TRACE_VERBOSE, "MTLContext_SetSurfaces: bsrc=%p (tex=%p type=%d), bdst=%p (tex=%p type=%d)", srcOps, srcOps->pTexture, srcOps->drawableType, dstOps, dstOps->pTexture, dstOps->drawableType);
+
+    if (dstOps->drawableType == MTLSD_TEXTURE) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+                      "MTLContext_SetSurfaces: texture cannot be used as destination");
+        return NULL;
+    }
+
+    if (dstOps->drawableType == MTLSD_UNDEFINED) {
+        // initialize the surface as an MTLSD_WINDOW
+        if (!MTLSD_InitMTLWindow(env, dstOps)) {
+            J2dRlsTraceLn(J2D_TRACE_ERROR,
+                          "MTLContext_SetSurfaces: could not init MTL window");
+            return NULL;
+        }
+    }
+
+    // make the context current
+    MTLSDOps *dstMTLOps = (MTLSDOps *)dstOps->privOps;
+    mtlc = dstMTLOps->configInfo->context;
+
+    if (mtlc == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+                      "MTLContext_SetSurfaces: could not make context current");
+        return NULL;
+    }
+
+    return mtlc;
+}
+
+- (void)resetClip {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.resetClip");
+    [_clip reset];
+}
+
+- (void)setClipRectX1:(jint)x1 Y1:(jint)y1 X2:(jint)x2 Y2:(jint)y2 {
+    J2dTraceLn4(J2D_TRACE_INFO, "MTLContext.setClipRect: %d,%d - %d,%d", x1, y1, x2, y2);
+    [_clip setClipRectX1:x1 Y1:y1 X2:x2 Y2:y2];
+}
+
+- (void)beginShapeClip:(BMTLSDOps *)dstOps {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.beginShapeClip");
+    [_clip beginShapeClip:dstOps context:self];
+
+    // Store the current transform as we need to use identity transform
+    // for clip spans rendering
+    [_tempTransform copyFrom:_transform];
+    [self resetTransform];
+}
+
+- (void)endShapeClip:(BMTLSDOps *)dstOps {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.endShapeClip");
+    [_clip endShapeClip:dstOps context:self];
+
+    // Reset transform for further rendering
+    [_transform copyFrom:_tempTransform];
+}
+
+- (void)resetComposite {
+    J2dTraceLn(J2D_TRACE_VERBOSE, "MTLContext_ResetComposite");
+    [_composite reset];
+}
+
+- (void)setAlphaCompositeRule:(jint)rule extraAlpha:(jfloat)extraAlpha
+                        flags:(jint)flags {
+    J2dTraceLn3(J2D_TRACE_INFO, "MTLContext_SetAlphaComposite: rule=%d, extraAlpha=%1.2f, flags=%d", rule, extraAlpha, flags);
+
+    [_composite setRule:rule extraAlpha:extraAlpha];
+}
+
+- (NSString*)getCompositeDescription {
+    return [_composite getDescription];
+}
+
+- (NSString*)getPaintDescription {
+    return [_paint getDescription];
+}
+
+- (void)setXorComposite:(jint)xp {
+    J2dTraceLn1(J2D_TRACE_INFO, "MTLContext.setXorComposite: xorPixel=%08x", xp);
+
+    [_composite setXORComposite:xp];
+}
+
+- (jboolean) useXORComposite {
+    return ([_composite getCompositeState] == sun_java2d_SunGraphics2D_COMP_XOR);
+}
+
+- (void)resetTransform {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext_ResetTransform");
+    [_transform resetTransform];
+}
+
+- (void)setTransformM00:(jdouble) m00 M10:(jdouble) m10
+                    M01:(jdouble) m01 M11:(jdouble) m11
+                    M02:(jdouble) m02 M12:(jdouble) m12 {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext_SetTransform");
+    [_transform setTransformM00:m00 M10:m10 M01:m01 M11:m11 M02:m02 M12:m12];
+}
+
+- (void)resetPaint {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.resetPaint");
+    self.paint = [[[MTLPaint alloc] init] autorelease];
+}
+
+- (void)setColorPaint:(int)pixel {
+    J2dTraceLn5(J2D_TRACE_INFO, "MTLContext.setColorPaint: pixel=%08x [r=%d g=%d b=%d a=%d]", pixel, (pixel >> 16) & (0xFF), (pixel >> 8) & 0xFF, (pixel) & 0xFF, (pixel >> 24) & 0xFF);
+    self.paint = [[[MTLColorPaint alloc] initWithColor:pixel] autorelease];
+}
+
+- (void)setGradientPaintUseMask:(jboolean)useMask
+                         cyclic:(jboolean)cyclic
+                             p0:(jdouble)p0
+                             p1:(jdouble)p1
+                             p3:(jdouble)p3
+                         pixel1:(jint)pixel1
+                         pixel2:(jint) pixel2
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.setGradientPaintUseMask");
+    self.paint = [[[MTLGradPaint alloc] initWithUseMask:useMask
+                                                cyclic:cyclic
+                                                    p0:p0
+                                                    p1:p1
+                                                    p3:p3
+                                                pixel1:pixel1
+                                                pixel2:pixel2] autorelease];
+}
+
+- (void)setLinearGradientPaint:(jboolean)useMask
+                        linear:(jboolean)linear
+                   cycleMethod:(jint)cycleMethod
+                                // 0 - NO_CYCLE
+                                // 1 - REFLECT
+                                // 2 - REPEAT
+
+                      numStops:(jint)numStops
+                            p0:(jfloat)p0
+                            p1:(jfloat)p1
+                            p3:(jfloat)p3
+                     fractions:(jfloat*)fractions
+                        pixels:(jint*)pixels
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.setLinearGradientPaint");
+    self.paint = [[[MTLLinearGradPaint alloc] initWithUseMask:useMask
+                       linear:linear
+                  cycleMethod:cycleMethod
+                     numStops:numStops
+                           p0:p0
+                           p1:p1
+                           p3:p3
+                    fractions:fractions
+                       pixels:pixels] autorelease];
+}
+
+- (void)setRadialGradientPaint:(jboolean)useMask
+                        linear:(jboolean)linear
+                   cycleMethod:(jboolean)cycleMethod
+                      numStops:(jint)numStops
+                           m00:(jfloat)m00
+                           m01:(jfloat)m01
+                           m02:(jfloat)m02
+                           m10:(jfloat)m10
+                           m11:(jfloat)m11
+                           m12:(jfloat)m12
+                        focusX:(jfloat)focusX
+                     fractions:(void *)fractions
+                        pixels:(void *)pixels
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLContext.setRadialGradientPaint");
+    self.paint = [[[MTLRadialGradPaint alloc] initWithUseMask:useMask
+                                                      linear:linear
+                                                 cycleMethod:cycleMethod
+                                                    numStops:numStops
+                                                         m00:m00
+                                                         m01:m01
+                                                         m02:m02
+                                                         m10:m10
+                                                         m11:m11
+                                                         m12:m12
+                                                      focusX:focusX
+                                                   fractions:fractions
+                                                      pixels:pixels] autorelease];
+}
+
+- (void)setTexturePaint:(jboolean)useMask
+                pSrcOps:(jlong)pSrcOps
+                 filter:(jboolean)filter
+                    xp0:(jdouble)xp0
+                    xp1:(jdouble)xp1
+                    xp3:(jdouble)xp3
+                    yp0:(jdouble)yp0
+                    yp1:(jdouble)yp1
+                    yp3:(jdouble)yp3
+{
+    BMTLSDOps *srcOps = (BMTLSDOps *)jlong_to_ptr(pSrcOps);
+
+    if (srcOps == NULL || srcOps->pTexture == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLContext_setTexturePaint: texture paint - texture is null");
+        return;
+    }
+
+    J2dTraceLn1(J2D_TRACE_INFO, "MTLContext.setTexturePaint [tex=%p]", srcOps->pTexture);
+
+    self.paint = [[[MTLTexturePaint alloc] initWithUseMask:useMask
+                                                textureID:srcOps->pTexture
+                                                  isOpaque:srcOps->isOpaque
+                                                   filter:filter
+                                                      xp0:xp0
+                                                      xp1:xp1
+                                                      xp3:xp3
+                                                      yp0:yp0
+                                                      yp1:yp1
+                                                      yp3:yp3] autorelease];
+}
+
+- (id<MTLCommandBuffer>)createCommandBuffer {
+    return [self.commandQueue commandBuffer];
+}
+
+/*
+ * This should be exclusively used only for final blit
+ * and present of CAMetalDrawable in MTLLayer
+ */
+- (id<MTLCommandBuffer>)createBlitCommandBuffer {
+    return [self.blitCommandQueue commandBuffer];
+}
+
+-(void)setBufImgOp:(NSObject*)bufImgOp {
+    if (_bufImgOp != nil) {
+        [_bufImgOp release]; // context owns bufImgOp object
+    }
+    _bufImgOp = bufImgOp;
+}
+
+-(NSObject*)getBufImgOp {
+    return _bufImgOp;
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGlyphCache.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGlyphCache.h
new file mode 100644
index 00000000000..51fd9173b71
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGlyphCache.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLGlyphCache_h_Included
+#define MTLGlyphCache_h_Included
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "jni.h"
+#include "fontscalerdefs.h"
+#import <Metal/Metal.h>
+
+typedef void (MTLFlushFunc)();
+
+typedef struct _MTLCacheCellInfo MTLCacheCellInfo;
+
+typedef struct {
+    CacheCellInfo *head;
+    CacheCellInfo *tail;
+    id<MTLTexture> texture;
+    jint          width;
+    jint          height;
+    jint          cellWidth;
+    jint          cellHeight;
+    MTLFlushFunc     *Flush;
+} MTLGlyphCacheInfo;
+
+struct _MTLCacheCellInfo {
+    MTLGlyphCacheInfo   *cacheInfo;
+    struct GlyphInfo *glyphInfo;
+    // next cell info in the cache's list
+    MTLCacheCellInfo    *next;
+    // REMIND: find better name?
+    // next cell info in the glyph's cell list (next Glyph Cache Info)
+    MTLCacheCellInfo    *nextGCI;
+    jint             timesRendered;
+    jint             x;
+    jint             y;
+    // number of pixels from the left or right edge not considered touched
+    // by the glyph
+    jint             leftOff;
+    jint             rightOff;
+    jfloat           tx1;
+    jfloat           ty1;
+    jfloat           tx2;
+    jfloat           ty2;
+};
+
+MTLGlyphCacheInfo *
+MTLGlyphCache_Init(jint width, jint height,
+                     jint cellWidth, jint cellHeight,
+                     MTLFlushFunc *func);
+MTLCacheCellInfo *
+MTLGlyphCache_AddGlyph(MTLGlyphCacheInfo *cache, struct GlyphInfo *glyph);
+bool
+MTLGlyphCache_IsCacheFull(MTLGlyphCacheInfo *cache, GlyphInfo *glyph);
+void
+MTLGlyphCache_Invalidate(MTLGlyphCacheInfo *cache);
+void
+MTLGlyphCache_AddCellInfo(struct GlyphInfo *glyph, MTLCacheCellInfo *cellInfo);
+void
+MTLGlyphCache_RemoveCellInfo(struct GlyphInfo *glyph, MTLCacheCellInfo *cellInfo);
+MTLCacheCellInfo *
+MTLGlyphCache_GetCellInfoForCache(struct GlyphInfo *glyph,
+                                    MTLGlyphCacheInfo *cache);
+JNIEXPORT void
+MTLGlyphCache_RemoveAllCellInfos(struct GlyphInfo *glyph);
+void
+MTLGlyphCache_Free(MTLGlyphCacheInfo *cache);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* MTLGlyphCache_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGlyphCache.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGlyphCache.m
new file mode 100644
index 00000000000..474bdd9af65
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGlyphCache.m
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdlib.h>
+#include "jni.h"
+#include "MTLGlyphCache.h"
+#include "Trace.h"
+
+/**
+ * When the cache is full, we will try to reuse the cache cells that have
+ * been used relatively less than the others (and we will save the cells that
+ * have been rendered more than the threshold defined here).
+ */
+#define TIMES_RENDERED_THRESHOLD 5
+
+/**
+ * Creates a new GlyphCacheInfo structure, fills in the initial values, and
+ * then returns a pointer to the GlyphCacheInfo record.
+ *
+ * Note that this method only sets up a data structure describing a
+ * rectangular region of accelerated memory, containing "virtual" cells of
+ * the requested size.  The cell information is added lazily to the linked
+ * list describing the cache as new glyphs are added.  Platform specific
+ * glyph caching code is responsible for actually creating the accelerated
+ * memory surface that will contain the individual glyph images.
+ *
+ * Each glyph contains a reference to a list of cell infos - one per glyph
+ * cache. There may be multiple glyph caches (for example, one per graphics
+ * adapter), so if the glyph is cached on two devices its cell list will
+ * consists of two elements corresponding to different glyph caches.
+ *
+ * The platform-specific glyph caching code is supposed to use
+ * GetCellInfoForCache method for retrieving cache infos from the glyph's list.
+ *
+ * Note that if it is guaranteed that there will be only one global glyph
+ * cache then it one does not have to use AccelGlyphCache_GetCellInfoForCache
+ * for retrieving cell info for the glyph, but instead just use the struct's
+ * field directly.
+ */
+MTLGlyphCacheInfo *
+MTLGlyphCache_Init(jint width, jint height,
+                     jint cellWidth, jint cellHeight,
+                     MTLFlushFunc *func)
+{
+    MTLGlyphCacheInfo *gcinfo;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_Init");
+
+    gcinfo = (MTLGlyphCacheInfo *)malloc(sizeof(MTLGlyphCacheInfo));
+    if (gcinfo == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+            "MTLGlyphCache_Init: could not allocate MTLGlyphCacheInfo");
+        return NULL;
+    }
+
+    gcinfo->head = NULL;
+    gcinfo->tail = NULL;
+    gcinfo->width = width;
+    gcinfo->height = height;
+    gcinfo->cellWidth = cellWidth;
+    gcinfo->cellHeight = cellHeight;
+    gcinfo->Flush = func;
+
+    return gcinfo;
+}
+
+/**
+ * Attempts to add the provided glyph to the specified cache.  If the
+ * operation is successful, a pointer to the newly occupied cache cell is
+ * stored in the glyph's cellInfo field; otherwise, its cellInfo field is
+ * set to NULL, indicating that the glyph's original bits should be rendered
+ * instead.  If the cache is full, the least-recently-used glyph is
+ * invalidated and its cache cell is reassigned to the new glyph being added.
+ *
+ * Note that this method only ensures that a rectangular region in the
+ * "virtual" glyph cache is available for the glyph image.  Platform specific
+ * glyph caching code is responsible for actually caching the glyph image
+ * in the associated accelerated memory surface.
+ *
+ * Returns created cell info if it was successfully created and added to the
+ * cache and glyph's cell lists, NULL otherwise.
+ */
+MTLCacheCellInfo *
+MTLGlyphCache_AddGlyph(MTLGlyphCacheInfo *cache, GlyphInfo *glyph)
+{
+    MTLCacheCellInfo *cellinfo = NULL;
+    jint w = glyph->width;
+    jint h = glyph->height;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_AddGlyph");
+
+    if ((glyph->width > cache->cellWidth) ||
+        (glyph->height > cache->cellHeight))
+    {
+        return NULL;
+    }
+
+    jint x, y;
+
+    if (cache->head == NULL) {
+        x = 0;
+        y = 0;
+    } else {
+        x = cache->tail->x + cache->cellWidth;
+        y = cache->tail->y;
+        if ((x + cache->cellWidth) > cache->width) {
+            x = 0;
+            y += cache->cellHeight;
+        }
+    }
+
+    // create new CacheCellInfo
+    cellinfo = (MTLCacheCellInfo *)malloc(sizeof(MTLCacheCellInfo));
+    if (cellinfo == NULL) {
+        J2dTraceLn(J2D_TRACE_ERROR, "could not allocate CellInfo");
+        return NULL;
+    }
+
+    cellinfo->cacheInfo = cache;
+    cellinfo->glyphInfo = glyph;
+    cellinfo->timesRendered = 0;
+    cellinfo->x = x;
+    cellinfo->y = y;
+    cellinfo->leftOff = 0;
+    cellinfo->rightOff = 0;
+    cellinfo->tx1 = (jfloat)cellinfo->x / cache->width;
+    cellinfo->ty1 = (jfloat)cellinfo->y / cache->height;
+    cellinfo->tx2 = cellinfo->tx1 + ((jfloat)w / cache->width);
+    cellinfo->ty2 = cellinfo->ty1 + ((jfloat)h / cache->height);
+
+    if (cache->head == NULL) {
+        // initialize the head cell
+        cache->head = cellinfo;
+    } else {
+        // update existing tail cell
+        cache->tail->next = cellinfo;
+    }
+
+    // add the new cell to the end of the list
+    cache->tail = cellinfo;
+    cellinfo->next = NULL;
+    cellinfo->nextGCI = NULL;
+
+    // add cache cell to the glyph's cells list
+    MTLGlyphCache_AddCellInfo(glyph, cellinfo);
+    return cellinfo;
+}
+
+
+bool
+MTLGlyphCache_IsCacheFull(MTLGlyphCacheInfo *cache, GlyphInfo *glyph)
+{
+    jint w = glyph->width;
+    jint h = glyph->height;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_IsCacheFull");
+
+    jint x, y;
+
+    if (cache->head == NULL) {
+        return JNI_FALSE;
+    } else {
+        x = cache->tail->x + cache->cellWidth;
+        y = cache->tail->y;
+        if ((x + cache->cellWidth) > cache->width) {
+            x = 0;
+            y += cache->cellHeight;
+            if ((y + cache->cellHeight) > cache->height) {
+                return JNI_TRUE;
+            }
+        }
+    }
+    return JNI_FALSE;
+}
+/**
+ * Invalidates all cells in the cache.  Note that this method does not
+ * attempt to compact the cache in any way; it just invalidates any cells
+ * that already exist.
+ */
+void
+MTLGlyphCache_Invalidate(MTLGlyphCacheInfo *cache)
+{
+    MTLCacheCellInfo *cellinfo;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_Invalidate");
+
+    if (cache == NULL) {
+        return;
+    }
+
+    // flush any pending vertices that may be depending on the current
+    // glyph cache layout
+    if (cache->Flush != NULL) {
+        cache->Flush();
+    }
+
+    cellinfo = cache->head;
+    while (cellinfo != NULL) {
+        if (cellinfo->glyphInfo != NULL) {
+            // if the cell is occupied, notify the base glyph that its
+            // cached version for this cache is about to be invalidated
+            MTLGlyphCache_RemoveCellInfo(cellinfo->glyphInfo, cellinfo);
+        }
+        cellinfo = cellinfo->next;
+    }
+}
+
+/**
+ * Invalidates and frees all cells and the cache itself. The "cache" pointer
+ * becomes invalid after this function returns.
+ */
+void
+MTLGlyphCache_Free(MTLGlyphCacheInfo *cache)
+{
+    MTLCacheCellInfo *cellinfo;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_Free");
+
+    if (cache == NULL) {
+        return;
+    }
+
+    // flush any pending vertices that may be depending on the current
+    // glyph cache
+    if (cache->Flush != NULL) {
+        cache->Flush();
+    }
+
+    while (cache->head != NULL) {
+        cellinfo = cache->head;
+        if (cellinfo->glyphInfo != NULL) {
+            // if the cell is occupied, notify the base glyph that its
+            // cached version for this cache is about to be invalidated
+            MTLGlyphCache_RemoveCellInfo(cellinfo->glyphInfo, cellinfo);
+        }
+        cache->head = cellinfo->next;
+        free(cellinfo);
+    }
+    free(cache);
+}
+
+/**
+ * Add cell info to the head of the glyph's list of cached cells.
+ */
+void
+MTLGlyphCache_AddCellInfo(GlyphInfo *glyph, MTLCacheCellInfo *cellInfo)
+{
+    // assert (glyph != NULL && cellInfo != NULL)
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_AddCellInfo");
+    J2dTraceLn2(J2D_TRACE_VERBOSE, "  glyph 0x%x: adding cell 0x%x to the list",
+                glyph, cellInfo);
+
+    cellInfo->glyphInfo = glyph;
+    cellInfo->nextGCI = glyph->cellInfo;
+    glyph->cellInfo = cellInfo;
+    glyph->managed = MANAGED_GLYPH;
+}
+
+/**
+ * Removes cell info from the glyph's list of cached cells.
+ */
+void
+MTLGlyphCache_RemoveCellInfo(GlyphInfo *glyph, MTLCacheCellInfo *cellInfo)
+{
+    MTLCacheCellInfo *currCellInfo = glyph->cellInfo;
+    MTLCacheCellInfo *prevInfo = NULL;
+    // assert (glyph!= NULL && glyph->cellInfo != NULL && cellInfo != NULL)
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_RemoveCellInfo");
+    do {
+        if (currCellInfo == cellInfo) {
+            J2dTraceLn2(J2D_TRACE_VERBOSE,
+                        "  glyph 0x%x: removing cell 0x%x from glyph's list",
+                        glyph, currCellInfo);
+            if (prevInfo == NULL) { // it's the head, chop-chop
+                glyph->cellInfo = currCellInfo->nextGCI;
+            } else {
+                prevInfo->nextGCI = currCellInfo->nextGCI;
+            }
+            currCellInfo->glyphInfo = NULL;
+            currCellInfo->nextGCI = NULL;
+            return;
+        }
+        prevInfo = currCellInfo;
+        currCellInfo = currCellInfo->nextGCI;
+    } while (currCellInfo != NULL);
+    J2dTraceLn2(J2D_TRACE_WARNING, "MTLGlyphCache_RemoveCellInfo: "\
+                "no cell 0x%x in glyph 0x%x's cell list",
+                cellInfo, glyph);
+}
+
+/**
+ * Removes cell info from the glyph's list of cached cells.
+ */
+JNIEXPORT void
+MTLGlyphCache_RemoveAllCellInfos(GlyphInfo *glyph)
+{
+    MTLCacheCellInfo *currCell, *prevCell;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGlyphCache_RemoveAllCellInfos");
+
+    if (glyph == NULL || glyph->cellInfo == NULL) {
+        return;
+    }
+
+    // invalidate all of this glyph's accelerated cache cells
+    currCell = glyph->cellInfo;
+    do {
+        currCell->glyphInfo = NULL;
+        prevCell = currCell;
+        currCell = currCell->nextGCI;
+        prevCell->nextGCI = NULL;
+    } while (currCell != NULL);
+
+    glyph->cellInfo = NULL;
+}
+
+/**
+ * Returns cell info associated with particular cache from the glyph's list of
+ * cached cells.
+ */
+MTLCacheCellInfo *
+MTLGlyphCache_GetCellInfoForCache(GlyphInfo *glyph, MTLGlyphCacheInfo *cache)
+{
+    // assert (glyph != NULL && cache != NULL)
+    J2dTraceLn(J2D_TRACE_VERBOSE2, "MTLGlyphCache_GetCellInfoForCache");
+
+    if (glyph->cellInfo != NULL) {
+        MTLCacheCellInfo *cellInfo = glyph->cellInfo;
+        do {
+            if (cellInfo->cacheInfo == cache) {
+                J2dTraceLn3(J2D_TRACE_VERBOSE2,
+                            "  glyph 0x%x: found cell 0x%x for cache 0x%x",
+                            glyph, cellInfo, cache);
+                return cellInfo;
+            }
+            cellInfo = cellInfo->nextGCI;
+        } while (cellInfo != NULL);
+    }
+    J2dTraceLn2(J2D_TRACE_VERBOSE2, "  glyph 0x%x: no cell for cache 0x%x",
+                glyph, cache);
+    return NULL;
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGraphicsConfig.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGraphicsConfig.h
new file mode 100644
index 00000000000..1e63005753d
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGraphicsConfig.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLGraphicsConfig_h_Included
+#define MTLGraphicsConfig_h_Included
+
+#import "JNIUtilities.h"
+#import "MTLSurfaceDataBase.h"
+#import "MTLContext.h"
+#import <Cocoa/Cocoa.h>
+#import <Metal/Metal.h>
+
+
+@interface MTLGraphicsConfigUtil : NSObject {}
++ (void) _getMTLConfigInfo: (NSMutableArray *)argValue;
+@end
+
+/**
+ * The MTLGraphicsConfigInfo structure contains information specific to a
+ * given MTLGraphicsConfig (pixel format).
+ *     MTLContext *context;
+ * The context associated with this MTLGraphicsConfig.
+ */
+typedef struct _MTLGraphicsConfigInfo {
+    MTLContext          *context;
+} MTLGraphicsConfigInfo;
+
+// From "Metal Feature Set Tables"
+// There are 2 GPU families for mac - MTLGPUFamilyMac1 and MTLGPUFamilyMac2
+// Both of them support "Maximum 2D texture width and height" of 16384 pixels
+// Note : there is no API to get this value, hence hardcoding by reading from the table
+#define MaxTextureSize 16384
+
+#endif /* MTLGraphicsConfig_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGraphicsConfig.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGraphicsConfig.m
new file mode 100644
index 00000000000..8ac597f7c22
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLGraphicsConfig.m
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import "sun_java2d_metal_MTLGraphicsConfig.h"
+
+#import "MTLGraphicsConfig.h"
+#import "MTLSurfaceData.h"
+#import "ThreadUtilities.h"
+#import "awt.h"
+
+#pragma mark -
+#pragma mark "--- Mac OS X specific methods for Metal pipeline ---"
+
+/**
+ * Disposes all memory and resources associated with the given
+ * MTLGraphicsConfigInfo (including its native MTLContext data).
+ */
+void
+MTLGC_DestroyMTLGraphicsConfig(jlong pConfigInfo)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGC_DestroyMTLGraphicsConfig");
+
+    MTLGraphicsConfigInfo *mtlinfo =
+        (MTLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo);
+    if (mtlinfo == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+                      "MTLGC_DestroyMTLGraphicsConfig: info is null");
+        return;
+    }
+
+    MTLContext *mtlc = (MTLContext*)mtlinfo->context;
+    if (mtlc != NULL) {
+        [mtlinfo->context release];
+        mtlinfo->context = nil;
+    }
+    free(mtlinfo);
+}
+
+#pragma mark -
+#pragma mark "--- MTLGraphicsConfig methods ---"
+
+
+JNIEXPORT jboolean JNICALL
+Java_sun_java2d_metal_MTLGraphicsConfig_isMetalFrameworkAvailable
+    (JNIEnv *env, jclass mtlgc)
+{
+    jboolean metalSupported = JNI_FALSE;
+
+    // It is guranteed that metal supported GPU is available macOS 10.14 onwards
+    if (@available(macOS 10.14, *)) {
+        metalSupported = JNI_TRUE;
+    }
+
+    J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLGraphicsConfig_isMetalFrameworkAvailable : %d", metalSupported);
+
+    return metalSupported;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_sun_java2d_metal_MTLGraphicsConfig_tryLoadMetalLibrary
+    (JNIEnv *env, jclass mtlgc, jint displayID, jstring shadersLibName)
+{
+    jboolean ret = JNI_FALSE;
+    JNI_COCOA_ENTER(env);
+    NSMutableArray * retArray = [NSMutableArray arrayWithCapacity:3];
+    [retArray addObject: [NSNumber numberWithInt: (int)displayID]];
+    char *str = JNU_GetStringPlatformChars(env, shadersLibName, 0);
+    [retArray addObject: [NSString stringWithUTF8String: str]];
+
+    [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
+        [MTLGraphicsConfigUtil _tryLoadMetalLibrary: retArray];
+    }];
+
+    NSNumber * num = (NSNumber *)[retArray objectAtIndex: 0];
+    ret = (jboolean)[num boolValue];
+    JNU_ReleaseStringPlatformChars(env, shadersLibName, str);
+    JNI_COCOA_EXIT(env);
+    return ret;
+}
+
+
+/**
+ * Determines whether the MTL pipeline can be used for a given GraphicsConfig
+ * provided its screen number and visual ID.  If the minimum requirements are
+ * met, the native MTLGraphicsConfigInfo structure is initialized for this
+ * GraphicsConfig with the necessary information (pixel format, etc.)
+ * and a pointer to this structure is returned as a jlong.  If
+ * initialization fails at any point, zero is returned, indicating that MTL
+ * cannot be used for this GraphicsConfig (we should fallback on an existing
+ * 2D pipeline).
+ */
+JNIEXPORT jlong JNICALL
+Java_sun_java2d_metal_MTLGraphicsConfig_getMTLConfigInfo
+    (JNIEnv *env, jclass mtlgc, jint displayID, jstring mtlShadersLib)
+{
+    jlong ret = 0L;
+    JNI_COCOA_ENTER(env);
+    NSMutableArray * retArray = [NSMutableArray arrayWithCapacity:3];
+    [retArray addObject: [NSNumber numberWithInt: (int)displayID]];
+    char *str = JNU_GetStringPlatformChars(env, mtlShadersLib, 0);
+    [retArray addObject: [NSString stringWithUTF8String: str]];
+
+    [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
+        [MTLGraphicsConfigUtil _getMTLConfigInfo: retArray];
+    }];
+
+    NSNumber * num = (NSNumber *)[retArray objectAtIndex: 0];
+    ret = (jlong)[num longValue];
+    JNU_ReleaseStringPlatformChars(env, mtlShadersLib, str);
+    JNI_COCOA_EXIT(env);
+    return ret;
+}
+
+
+
+
+@implementation MTLGraphicsConfigUtil
++ (void) _getMTLConfigInfo: (NSMutableArray *)argValue {
+    AWT_ASSERT_APPKIT_THREAD;
+
+    jint displayID = (jint)[(NSNumber *)[argValue objectAtIndex: 0] intValue];
+    NSString *mtlShadersLib = (NSString *)[argValue objectAtIndex: 1];
+    JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
+    [argValue removeAllObjects];
+
+    J2dRlsTraceLn(J2D_TRACE_INFO, "MTLGraphicsConfig_getMTLConfigInfo");
+
+    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+
+    NSRect contentRect = NSMakeRect(0, 0, 64, 64);
+    NSWindow *window =
+        [[NSWindow alloc]
+            initWithContentRect: contentRect
+            styleMask: NSBorderlessWindowMask
+            backing: NSBackingStoreBuffered
+            defer: false];
+    if (window == nil) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLGraphicsConfig_getMTLConfigInfo: NSWindow is NULL");
+        [argValue addObject: [NSNumber numberWithLong: 0L]];
+        return;
+    }
+
+    MTLContext *mtlc = [[MTLContext alloc] initWithDevice:CGDirectDisplayCopyCurrentMetalDevice(displayID)
+                        shadersLib:mtlShadersLib];
+    if (mtlc == 0L) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLGC_InitMTLContext: could not initialze MTLContext.");
+        [argValue addObject: [NSNumber numberWithLong: 0L]];
+        return;
+    }
+
+
+    // create the MTLGraphicsConfigInfo record for this config
+    MTLGraphicsConfigInfo *mtlinfo = (MTLGraphicsConfigInfo *)malloc(sizeof(MTLGraphicsConfigInfo));
+    if (mtlinfo == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLGraphicsConfig_getMTLConfigInfo: could not allocate memory for mtlinfo");
+        free(mtlc);
+        [argValue addObject: [NSNumber numberWithLong: 0L]];
+        return;
+    }
+    memset(mtlinfo, 0, sizeof(MTLGraphicsConfigInfo));
+    mtlinfo->context = mtlc;
+
+    [argValue addObject: [NSNumber numberWithLong:ptr_to_jlong(mtlinfo)]];
+    [pool drain];
+}
+
++ (void) _tryLoadMetalLibrary: (NSMutableArray *)argValue {
+    AWT_ASSERT_APPKIT_THREAD;
+
+    jint displayID = (jint)[(NSNumber *)[argValue objectAtIndex: 0] intValue];
+    NSString *mtlShadersLib = (NSString *)[argValue objectAtIndex: 1];
+    JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
+    [argValue removeAllObjects];
+
+    J2dRlsTraceLn(J2D_TRACE_INFO, "MTLGraphicsConfigUtil_tryLoadMTLLibrary");
+
+
+    BOOL ret = FALSE;;
+    id<MTLDevice> device = CGDirectDisplayCopyCurrentMetalDevice(displayID);
+    if (device != nil) {
+        NSError *error = nil;
+        id<MTLLibrary> lib = [device newLibraryWithFile:mtlShadersLib error:&error];
+        if (lib != nil) {
+            ret = TRUE;
+        } else {
+            J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLGraphicsConfig_tryLoadMetalLibrary - Failed to load Metal shader library.");
+        }
+    } else {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLGraphicsConfig_tryLoadMetalLibrary - Failed to create MTLDevice.");
+    }
+
+    [argValue addObject: [NSNumber numberWithBool: ret]];
+
+}
+
+@end //GraphicsConfigUtil
+
+
+JNIEXPORT jint JNICALL
+Java_sun_java2d_metal_MTLGraphicsConfig_nativeGetMaxTextureSize
+    (JNIEnv *env, jclass mtlgc)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLGraphicsConfig_nativeGetMaxTextureSize");
+
+    return (jint)MaxTextureSize;
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLLayer.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLLayer.h
new file mode 100644
index 00000000000..dceac2ad433
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLLayer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLLayer_h_Included
+#define MTLLayer_h_Included
+#import <Metal/Metal.h>
+#import <QuartzCore/CAMetalLayer.h>
+#include <CoreVideo/CVDisplayLink.h>
+#import "common.h"
+
+@interface MTLLayer : CAMetalLayer
+{
+@private
+    jobject javaLayer;
+
+    // intermediate buffer, used the RQ lock to synchronize
+    MTLContext* ctx;
+    float bufferWidth;
+    float bufferHeight;
+    id<MTLTexture> buffer;
+    int nextDrawableCount;
+    int topInset;
+    int leftInset;
+    CVDisplayLinkRef displayLink;
+}
+
+@property (nonatomic) jobject javaLayer;
+@property (readwrite, assign) MTLContext* ctx;
+@property (readwrite, assign) float bufferWidth;
+@property (readwrite, assign) float bufferHeight;
+@property (readwrite, assign) id<MTLTexture> buffer;
+@property (readwrite, assign) int nextDrawableCount;
+@property (readwrite, assign) int topInset;
+@property (readwrite, assign) int leftInset;
+@property (readwrite, assign) CVDisplayLinkRef displayLink;
+
+- (id) initWithJavaLayer:(jobject)layer;
+
+- (void) blitTexture;
+- (void) fillParallelogramCtxX:(jfloat)x
+                             Y:(jfloat)y
+                           DX1:(jfloat)dx1
+                           DY1:(jfloat)dy1
+                           DX2:(jfloat)dx2
+                           DY2:(jfloat)dy2;
+- (void) blitCallback;
+- (void) display;
+- (void) redraw;
+- (void) startDisplayLink;
+- (void) stopDisplayLink;
+@end
+
+#endif /* MTLLayer_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLLayer.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLLayer.m
new file mode 100644
index 00000000000..b99e697fe54
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLLayer.m
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import "MTLGraphicsConfig.h"
+#import "MTLLayer.h"
+#import "ThreadUtilities.h"
+#import "LWCToolkit.h"
+#import "MTLSurfaceData.h"
+#import "JNIUtilities.h"
+
+@implementation MTLLayer
+
+
+@synthesize javaLayer;
+@synthesize ctx;
+@synthesize bufferWidth;
+@synthesize bufferHeight;
+@synthesize buffer;
+@synthesize topInset;
+@synthesize leftInset;
+@synthesize nextDrawableCount;
+@synthesize displayLink;
+
+- (id) initWithJavaLayer:(jobject)layer
+{
+    AWT_ASSERT_APPKIT_THREAD;
+    // Initialize ourselves
+    self = [super init];
+    if (self == nil) return self;
+
+    self.javaLayer = layer;
+
+    self.contentsGravity = kCAGravityTopLeft;
+
+    //Disable CALayer's default animation
+    NSMutableDictionary * actions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
+                                    [NSNull null], @"anchorPoint",
+                                    [NSNull null], @"bounds",
+                                    [NSNull null], @"contents",
+                                    [NSNull null], @"contentsScale",
+                                    [NSNull null], @"onOrderIn",
+                                    [NSNull null], @"onOrderOut",
+                                    [NSNull null], @"position",
+                                    [NSNull null], @"sublayers",
+                                    nil];
+    self.actions = actions;
+    [actions release];
+    self.topInset = 0;
+    self.leftInset = 0;
+    self.framebufferOnly = NO;
+    self.nextDrawableCount = 0;
+    self.opaque = FALSE;
+    CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
+    CVDisplayLinkSetOutputCallback(displayLink, &displayLinkCallback, (__bridge void*)self);
+    return self;
+}
+
+- (void) blitTexture {
+    if (self.ctx == NULL || self.javaLayer == NULL || self.buffer == nil || self.ctx.device == nil) {
+        J2dTraceLn4(J2D_TRACE_VERBOSE, "MTLLayer.blitTexture: uninitialized (mtlc=%p, javaLayer=%p, buffer=%p, devide=%p)", self.ctx, self.javaLayer, self.buffer, ctx.device);
+        return;
+    }
+
+    if (self.nextDrawableCount != 0) {
+        return;
+    }
+    @autoreleasepool {
+        if ((self.buffer.width == 0) || (self.buffer.height == 0)) {
+            J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer.blitTexture: cannot create drawable of size 0");
+            return;
+        }
+
+        NSUInteger src_x = self.leftInset * self.contentsScale;
+        NSUInteger src_y = self.topInset * self.contentsScale;
+        NSUInteger src_w = self.buffer.width - src_x;
+        NSUInteger src_h = self.buffer.height - src_y;
+
+        if (src_h <= 0 || src_w <= 0) {
+           J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer.blitTexture: Invalid src width or height.");
+           return;
+        }
+
+        id<MTLCommandBuffer> commandBuf = [self.ctx createBlitCommandBuffer];
+        if (commandBuf == nil) {
+            J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer.blitTexture: commandBuf is null");
+            return;
+        }
+        id<CAMetalDrawable> mtlDrawable = [self nextDrawable];
+        if (mtlDrawable == nil) {
+            J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer.blitTexture: nextDrawable is null)");
+            return;
+        }
+        self.nextDrawableCount++;
+
+        id <MTLBlitCommandEncoder> blitEncoder = [commandBuf blitCommandEncoder];
+
+        [blitEncoder
+                copyFromTexture:self.buffer sourceSlice:0 sourceLevel:0
+                sourceOrigin:MTLOriginMake(src_x, src_y, 0)
+                sourceSize:MTLSizeMake(src_w, src_h, 1)
+                toTexture:mtlDrawable.texture destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(0, 0, 0)];
+        [blitEncoder endEncoding];
+
+        [commandBuf presentDrawable:mtlDrawable];
+        [commandBuf addCompletedHandler:^(id <MTLCommandBuffer> commandBuf) {
+            self.nextDrawableCount--;
+        }];
+
+        [commandBuf commit];
+        [self stopDisplayLink];
+    }
+}
+
+- (void) dealloc {
+    JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
+    (*env)->DeleteWeakGlobalRef(env, self.javaLayer);
+    self.javaLayer = nil;
+    [self stopDisplayLink];
+    CVDisplayLinkRelease(self.displayLink);
+    self.displayLink = nil;
+    [super dealloc];
+}
+
+- (void) blitCallback {
+
+    JNIEnv *env = [ThreadUtilities getJNIEnv];
+    DECLARE_CLASS(jc_JavaLayer, "sun/java2d/metal/MTLLayer");
+    DECLARE_METHOD(jm_drawInMTLContext, jc_JavaLayer, "drawInMTLContext", "()V");
+
+    jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
+    if ((*env)->IsSameObject(env, javaLayerLocalRef, NULL)) {
+        return;
+    }
+
+    (*env)->CallVoidMethod(env, javaLayerLocalRef, jm_drawInMTLContext);
+    CHECK_EXCEPTION();
+    (*env)->DeleteLocalRef(env, javaLayerLocalRef);
+}
+
+- (void) display {
+    AWT_ASSERT_APPKIT_THREAD;
+    J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer_display() called");
+    [self blitCallback];
+    [super display];
+}
+
+- (void) redraw {
+    AWT_ASSERT_APPKIT_THREAD;
+    [self setNeedsDisplay];
+}
+
+- (void) startDisplayLink {
+    if (!CVDisplayLinkIsRunning(self.displayLink))
+        CVDisplayLinkStart(self.displayLink);
+}
+
+- (void) stopDisplayLink {
+    if (CVDisplayLinkIsRunning(self.displayLink))
+        CVDisplayLinkStop(self.displayLink);
+}
+
+CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
+{
+    J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer_displayLinkCallback() called");
+    @autoreleasepool {
+        MTLLayer *layer = (__bridge MTLLayer *)displayLinkContext;
+        [layer performSelectorOnMainThread:@selector(redraw) withObject:nil waitUntilDone:NO];
+    }
+    return kCVReturnSuccess;
+}
+@end
+
+/*
+ * Class:     sun_java2d_metal_MTLLayer
+ * Method:    nativeCreateLayer
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL
+Java_sun_java2d_metal_MTLLayer_nativeCreateLayer
+(JNIEnv *env, jobject obj)
+{
+    __block MTLLayer *layer = nil;
+
+JNI_COCOA_ENTER(env);
+
+    jobject javaLayer = (*env)->NewWeakGlobalRef(env, obj);
+
+    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
+            AWT_ASSERT_APPKIT_THREAD;
+
+            layer = [[MTLLayer alloc] initWithJavaLayer: javaLayer];
+    }];
+
+JNI_COCOA_EXIT(env);
+
+    return ptr_to_jlong(layer);
+}
+
+// Must be called under the RQ lock.
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLLayer_validate
+(JNIEnv *env, jclass cls, jlong layerPtr, jobject surfaceData)
+{
+    MTLLayer *layer = OBJC(layerPtr);
+
+    if (surfaceData != NULL) {
+        BMTLSDOps *bmtlsdo = (BMTLSDOps*) SurfaceData_GetOps(env, surfaceData);
+        layer.bufferWidth = bmtlsdo->width;
+        layer.bufferHeight = bmtlsdo->width;
+        layer.buffer = bmtlsdo->pTexture;
+        layer.ctx = ((MTLSDOps *)bmtlsdo->privOps)->configInfo->context;
+        layer.device = layer.ctx.device;
+        layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
+        layer.drawableSize =
+            CGSizeMake(layer.buffer.width,
+                       layer.buffer.height);
+        [layer startDisplayLink];
+    } else {
+        layer.ctx = NULL;
+        [layer stopDisplayLink];
+    }
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLLayer_nativeSetScale
+(JNIEnv *env, jclass cls, jlong layerPtr, jdouble scale)
+{
+    JNI_COCOA_ENTER(env);
+    MTLLayer *layer = jlong_to_ptr(layerPtr);
+    // We always call all setXX methods asynchronously, exception is only in
+    // this method where we need to change native texture size and layer's scale
+    // in one call on appkit, otherwise we'll get window's contents blinking,
+    // during screen-2-screen moving.
+    [ThreadUtilities performOnMainThreadWaiting:[NSThread isMainThread] block:^(){
+        layer.contentsScale = scale;
+    }];
+    JNI_COCOA_EXIT(env);
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLLayer_nativeSetInsets
+(JNIEnv *env, jclass cls, jlong layerPtr, jint top, jint left)
+{
+    MTLLayer *layer = jlong_to_ptr(layerPtr);
+    layer.topInset = top;
+    layer.leftInset = left;
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLLayer_blitTexture
+(JNIEnv *env, jclass cls, jlong layerPtr)
+{
+    J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer_blitTexture");
+    MTLLayer *layer = jlong_to_ptr(layerPtr);
+    MTLContext * ctx = layer.ctx;
+    if (layer == NULL || ctx == NULL) {
+        J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer_blit : Layer or Context is null");
+        return;
+    }
+
+    [layer blitTexture];
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskBlit.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskBlit.h
new file mode 100644
index 00000000000..201bbb8115a
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskBlit.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLMaskBlit_h_Included
+#define MTLMaskBlit_h_Included
+
+#include "MTLContext.h"
+
+void MTLMaskBlit_MaskBlit(JNIEnv *env, MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint dstx, jint dsty,
+                          jint width, jint height,
+                          void *pPixels);
+
+#endif /* MTLMaskBlit_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskBlit.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskBlit.m
new file mode 100644
index 00000000000..d23975ff4b5
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskBlit.m
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdlib.h>
+#include <jlong.h>
+
+#include "MTLMaskBlit.h"
+#include "MTLRenderQueue.h"
+#include "MTLBlitLoops.h"
+
+/**
+ * REMIND: This method assumes that the dimensions of the incoming pixel
+ *         array are less than or equal to the cached blit texture tile;
+ *         these are rather fragile assumptions, and should be cleaned up...
+ */
+void
+MTLMaskBlit_MaskBlit(JNIEnv *env, MTLContext *mtlc, BMTLSDOps * dstOps,
+                     jint dstx, jint dsty,
+                     jint width, jint height,
+                     void *pPixels)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLMaskBlit_MaskBlit");
+
+    if (width <= 0 || height <= 0) {
+        J2dTraceLn(J2D_TRACE_WARNING, "MTLMaskBlit_MaskBlit: invalid dimensions");
+        return;
+    }
+
+    RETURN_IF_NULL(pPixels);
+    RETURN_IF_NULL(mtlc);
+
+    MTLPooledTextureHandle * texHandle = [mtlc.texturePool
+                                                  getTexture:width
+                                                      height:height
+                                                      format:MTLPixelFormatBGRA8Unorm];
+    if (texHandle == nil) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLMaskBlit_MaskBlit: can't obtain temporary texture object from pool");
+        return;
+    }
+    [[mtlc getCommandBufferWrapper] registerPooledTexture:texHandle];
+
+    id<MTLTexture> texBuff = texHandle.texture;
+    MTLRegion region = MTLRegionMake2D(0, 0, width, height);
+    [texBuff replaceRegion:region mipmapLevel:0 withBytes:pPixels bytesPerRow:4*width];
+
+    drawTex2Tex(mtlc, texBuff, dstOps->pTexture, JNI_FALSE, dstOps->isOpaque, 0,
+                0, 0, width, height, dstx, dsty, dstx + width, dsty + height);
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskFill.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskFill.h
new file mode 100644
index 00000000000..3e74e048941
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskFill.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLMaskFill_h_Included
+#define MTLMaskFill_h_Included
+
+#include "MTLContext.h"
+
+void MTLMaskFill_MaskFill(MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint x, jint y, jint w, jint h,
+                          jint maskoff, jint maskscan, jint masklen,
+                          unsigned char *pMask);
+
+#endif /* MTLMaskFill_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskFill.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskFill.m
new file mode 100644
index 00000000000..89fad174c1e
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLMaskFill.m
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "sun_java2d_metal_MTLMaskFill.h"
+
+#include "MTLMaskFill.h"
+#include "MTLRenderQueue.h"
+#include "MTLVertexCache.h"
+
+/**
+ * In case of Metal we use shader for texture mapping.
+ *
+ * Descriptions of the many variables used in this method:
+ *   x,y     - upper left corner of the tile destination
+ *   w,h     - width/height of the mask tile
+ *   x0      - placekeeper for the original destination x location
+ *   tw,th   - width/height of the actual texture tile in pixels
+ *   sx1,sy1 - upper left corner of the mask tile source region
+ *   sx2,sy2 - lower left corner of the mask tile source region
+ *   sx,sy   - "current" upper left corner of the mask tile region of interest
+ */
+void
+MTLMaskFill_MaskFill(MTLContext *mtlc, BMTLSDOps * dstOps,
+                     jint x, jint y, jint w, jint h,
+                     jint maskoff, jint maskscan, jint masklen,
+                     unsigned char *pMask)
+{
+    J2dTraceLn5(J2D_TRACE_INFO, "MTLMaskFill_MaskFill (x=%d y=%d w=%d h=%d pMask=%p)", x, y, w, h, dstOps->pTexture);
+    jint tw, th, x0;
+    jint sx1, sy1, sx2, sy2;
+    jint sx, sy, sw, sh;
+
+    x0 = x;
+    tw = MTLVC_MASK_CACHE_TILE_WIDTH;
+    th = MTLVC_MASK_CACHE_TILE_HEIGHT;
+    sx1 = maskoff % maskscan;
+    sy1 = maskoff / maskscan;
+    sx2 = sx1 + w;
+    sy2 = sy1 + h;
+
+
+    for (sy = sy1; sy < sy2; sy += th, y += th) {
+        x = x0;
+        sh = ((sy + th) > sy2) ? (sy2 - sy) : th;
+
+        for (sx = sx1; sx < sx2; sx += tw, x += tw) {
+            sw = ((sx + tw) > sx2) ? (sx2 - sx) : tw;
+            MTLVertexCache_AddMaskQuad(mtlc,
+                    sx, sy, x, y, sw, sh,
+                    maskscan, pMask, dstOps);
+        }
+    }
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLMaskFill_maskFill
+    (JNIEnv *env, jobject self,
+     jint x, jint y, jint w, jint h,
+     jint maskoff, jint maskscan, jint masklen,
+     jbyteArray maskArray)
+{
+    MTLContext *mtlc = MTLRenderQueue_GetCurrentContext();
+    BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+    unsigned char *mask;
+
+    J2dTraceLn(J2D_TRACE_ERROR, "MTLMaskFill_maskFill");
+
+    if (maskArray != NULL) {
+        mask = (unsigned char *)
+            (*env)->GetPrimitiveArrayCritical(env, maskArray, NULL);
+    } else {
+        mask = NULL;
+    }
+
+    MTLMaskFill_MaskFill(mtlc, dstOps,
+                         x, y, w, h,
+                         maskoff, maskscan, masklen, mask);
+    if (mtlc != NULL) {
+        RESET_PREVIOUS_OP();
+        [mtlc.encoderManager endEncoder];
+        MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+        id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+        [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+            [cbwrapper release];
+        }];
+        [commandbuf commit];
+    }
+
+    if (mask != NULL) {
+        (*env)->ReleasePrimitiveArrayCritical(env, maskArray, mask, JNI_ABORT);
+    }
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.h
new file mode 100644
index 00000000000..20ba80a9949
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLPaints_h_Included
+#define MTLPaints_h_Included
+
+#import <Metal/Metal.h>
+#include "RenderOptions.h"
+
+#define sun_java2d_SunGraphics2D_PAINT_UNDEFINED -1
+
+@class MTLContext;
+@class MTLComposite;
+@class MTLClip;
+@class MTLPipelineStatesStorage;
+
+/**
+ * The MTLPaint class represents paint mode (color, gradient etc.)
+ */
+
+@interface MTLPaint : NSObject
+
+- (id)initWithState:(jint)state;
+- (BOOL)isEqual:(MTLPaint *)other; // used to compare requested with cached
+- (NSString *)getDescription;
+
+// For the current paint mode and passed composite (and flags):
+// 1. Selects vertex+fragment shader (and corresponding pipelineDesc) and set pipelineState
+// 2. Prepares corresponding buffers of vertex and fragment shaders
+
+- (void)setPipelineState:(id <MTLRenderCommandEncoder>)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage;
+
+
+- (void)setXorModePipelineState:(id <MTLRenderCommandEncoder>)encoder
+                        context:(MTLContext *)mtlc
+                  renderOptions:(const RenderOptions *)renderOptions
+           pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage;
+@end
+
+@interface MTLColorPaint : MTLPaint
+- (id)initWithColor:(jint)color;
+@property (nonatomic, readonly) jint color;
+@end
+
+@interface MTLBaseGradPaint : MTLPaint
+- (id)initWithState:(jint)state
+               mask:(jboolean)useMask
+             cyclic:(jboolean)cyclic;
+@end
+
+@interface MTLGradPaint : MTLBaseGradPaint
+
+- (id)initWithUseMask:(jboolean)useMask
+               cyclic:(jboolean)cyclic
+                   p0:(jdouble)p0
+                   p1:(jdouble)p1
+                   p3:(jdouble)p3
+               pixel1:(jint)pixel1
+               pixel2:(jint)pixel2;
+@end
+
+@interface MTLBaseMultiGradPaint : MTLBaseGradPaint
+
+- (id)initWithState:(jint)state
+               mask:(jboolean)useMask
+             linear:(jboolean)linear
+        cycleMethod:(jboolean)cycleMethod
+           numStops:(jint)numStops
+          fractions:(jfloat *)fractions
+             pixels:(jint *)pixels;
+@end
+
+@interface MTLLinearGradPaint : MTLBaseMultiGradPaint
+
+- (id)initWithUseMask:(jboolean)useMask
+               linear:(jboolean)linear
+          cycleMethod:(jboolean)cycleMethod
+             numStops:(jint)numStops
+                   p0:(jfloat)p0
+                   p1:(jfloat)p1
+                   p3:(jfloat)p3
+            fractions:(jfloat *)fractions
+               pixels:(jint *)pixels;
+@end
+
+@interface MTLRadialGradPaint : MTLBaseMultiGradPaint
+
+- (id)initWithUseMask:(jboolean)useMask
+               linear:(jboolean)linear
+          cycleMethod:(jint)cycleMethod
+             numStops:(jint)numStops
+                  m00:(jfloat)m00
+                  m01:(jfloat)m01
+                  m02:(jfloat)m02
+                  m10:(jfloat)m10
+                  m11:(jfloat)m11
+                  m12:(jfloat)m12
+               focusX:(jfloat)focusX
+            fractions:(void *)fractions
+               pixels:(void *)pixels;
+@end
+
+@interface MTLTexturePaint : MTLPaint
+
+- (id)initWithUseMask:(jboolean)useMask
+              textureID:(id <MTLTexture>)textureID
+               isOpaque:(jboolean)isOpaque
+                 filter:(jboolean)filter
+                    xp0:(jdouble)xp0
+                    xp1:(jdouble)xp1
+                    xp3:(jdouble)xp3
+                    yp0:(jdouble)yp0
+                    yp1:(jdouble)yp1
+                    yp3:(jdouble)yp3;
+@end
+
+#endif /* MTLPaints_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.m
new file mode 100644
index 00000000000..8bff3c4c2fb
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.m
@@ -0,0 +1,993 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "MTLPaints.h"
+#include "MTLClip.h"
+#include "common.h"
+
+#include "sun_java2d_SunGraphics2D.h"
+#include "sun_java2d_pipe_BufferedPaints.h"
+#import "MTLComposite.h"
+#import "MTLBufImgOps.h"
+#include "MTLRenderQueue.h"
+
+#define RGBA_TO_V4(c)              \
+{                                  \
+    (((c) >> 16) & (0xFF))/255.0f, \
+    (((c) >> 8) & 0xFF)/255.0f,    \
+    ((c) & 0xFF)/255.0f,           \
+    (((c) >> 24) & 0xFF)/255.0f    \
+}
+
+#define FLOAT_ARR_TO_V4(p) \
+{                      \
+    p[0], \
+    p[1], \
+    p[2], \
+    p[3]  \
+}
+
+static MTLRenderPipelineDescriptor * templateRenderPipelineDesc = nil;
+static MTLRenderPipelineDescriptor * templateTexturePipelineDesc = nil;
+static MTLRenderPipelineDescriptor * templateAATexturePipelineDesc = nil;
+static MTLRenderPipelineDescriptor * templateLCDPipelineDesc = nil;
+static MTLRenderPipelineDescriptor * templateAAPipelineDesc = nil;
+static void
+setTxtUniforms(MTLContext *mtlc, int color, id <MTLRenderCommandEncoder> encoder, int interpolation, bool repeat,
+               jfloat extraAlpha, const SurfaceRasterFlags *srcFlags, const SurfaceRasterFlags *dstFlags, int mode);
+
+static void initTemplatePipelineDescriptors() {
+    if (templateRenderPipelineDesc != nil && templateTexturePipelineDesc != nil &&
+        templateAATexturePipelineDesc != nil && templateLCDPipelineDesc != nil &&
+        templateAAPipelineDesc != nil)
+        return;
+
+    MTLVertexDescriptor *vertDesc = [[MTLVertexDescriptor new] autorelease];
+    vertDesc.attributes[VertexAttributePosition].format = MTLVertexFormatFloat2;
+    vertDesc.attributes[VertexAttributePosition].offset = 0;
+    vertDesc.attributes[VertexAttributePosition].bufferIndex = MeshVertexBuffer;
+    vertDesc.layouts[MeshVertexBuffer].stride = sizeof(struct Vertex);
+    vertDesc.layouts[MeshVertexBuffer].stepRate = 1;
+    vertDesc.layouts[MeshVertexBuffer].stepFunction = MTLVertexStepFunctionPerVertex;
+
+    templateRenderPipelineDesc = [MTLRenderPipelineDescriptor new];
+    templateRenderPipelineDesc.sampleCount = 1;
+    templateRenderPipelineDesc.vertexDescriptor = vertDesc;
+    templateRenderPipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
+    templateRenderPipelineDesc.colorAttachments[0].rgbBlendOperation =   MTLBlendOperationAdd;
+    templateRenderPipelineDesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
+    templateRenderPipelineDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
+    templateRenderPipelineDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
+    templateRenderPipelineDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+    templateRenderPipelineDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+    templateRenderPipelineDesc.label = @"template_render";
+
+    templateTexturePipelineDesc = [templateRenderPipelineDesc copy];
+    templateTexturePipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].format = MTLVertexFormatFloat2;
+    templateTexturePipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].offset = 2*sizeof(float);
+    templateTexturePipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].bufferIndex = MeshVertexBuffer;
+    templateTexturePipelineDesc.vertexDescriptor.layouts[MeshVertexBuffer].stride = sizeof(struct TxtVertex);
+    templateTexturePipelineDesc.vertexDescriptor.layouts[MeshVertexBuffer].stepRate = 1;
+    templateTexturePipelineDesc.vertexDescriptor.layouts[MeshVertexBuffer].stepFunction = MTLVertexStepFunctionPerVertex;
+    templateTexturePipelineDesc.label = @"template_texture";
+
+    templateAATexturePipelineDesc = [templateTexturePipelineDesc copy];
+    templateAATexturePipelineDesc.label = @"template_aa_texture";
+
+    templateLCDPipelineDesc = [MTLRenderPipelineDescriptor new];
+    templateLCDPipelineDesc.sampleCount = 1;
+    templateLCDPipelineDesc.vertexDescriptor = vertDesc;
+    templateLCDPipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
+    templateLCDPipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].format = MTLVertexFormatFloat2;
+    templateLCDPipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].offset = 2*sizeof(float);
+    templateLCDPipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].bufferIndex = MeshVertexBuffer;
+    templateLCDPipelineDesc.vertexDescriptor.layouts[MeshVertexBuffer].stride = sizeof(struct TxtVertex);
+    templateLCDPipelineDesc.vertexDescriptor.layouts[MeshVertexBuffer].stepRate = 1;
+    templateLCDPipelineDesc.vertexDescriptor.layouts[MeshVertexBuffer].stepFunction = MTLVertexStepFunctionPerVertex;
+    templateLCDPipelineDesc.label = @"template_lcd";
+
+    vertDesc = [[MTLVertexDescriptor new] autorelease];
+    vertDesc.attributes[VertexAttributePosition].format = MTLVertexFormatFloat2;
+    vertDesc.attributes[VertexAttributePosition].offset = 0;
+    vertDesc.attributes[VertexAttributePosition].bufferIndex = MeshVertexBuffer;
+    vertDesc.layouts[MeshVertexBuffer].stride = sizeof(struct AAVertex);
+    vertDesc.layouts[MeshVertexBuffer].stepRate = 1;
+    vertDesc.layouts[MeshVertexBuffer].stepFunction = MTLVertexStepFunctionPerVertex;
+
+    templateAAPipelineDesc = [MTLRenderPipelineDescriptor new];
+    templateAAPipelineDesc.sampleCount = 1;
+    templateAAPipelineDesc.vertexDescriptor = vertDesc;
+    templateAAPipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
+    templateAAPipelineDesc.colorAttachments[0].rgbBlendOperation =   MTLBlendOperationAdd;
+    templateAAPipelineDesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
+    templateAAPipelineDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
+    templateAAPipelineDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
+    templateAAPipelineDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+    templateAAPipelineDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+    templateAAPipelineDesc.colorAttachments[0].blendingEnabled = YES;
+    templateAAPipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].format = MTLVertexFormatFloat2;
+    templateAAPipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].offset = 2*sizeof(float);
+    templateAAPipelineDesc.vertexDescriptor.attributes[VertexAttributeTexPos].bufferIndex = MeshVertexBuffer;
+    templateAAPipelineDesc.vertexDescriptor.attributes[VertexAttributeITexPos].format = MTLVertexFormatFloat2;
+    templateAAPipelineDesc.vertexDescriptor.attributes[VertexAttributeITexPos].offset = 4*sizeof(float);
+    templateAAPipelineDesc.vertexDescriptor.attributes[VertexAttributeITexPos].bufferIndex = MeshVertexBuffer;
+    templateAAPipelineDesc.label = @"template_aa";
+}
+
+
+@implementation MTLColorPaint {
+// color-mode
+jint _color;
+}
+- (id)initWithColor:(jint)color {
+    self = [super initWithState:sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR];
+
+    if (self) {
+        _color = color;
+    }
+    return self;
+}
+
+- (jint)color {
+    return _color;
+}
+
+- (BOOL)isEqual:(MTLColorPaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![[other class] isEqual:[self class]])
+        return NO;
+
+    return _color == other->_color;
+}
+
+- (NSUInteger)hash {
+    NSUInteger h = [super hash];
+    h = h*31 + _color;
+    return h;
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:
+            @"[r=%d g=%d b=%d a=%d]",
+            (_color >> 16) & (0xFF),
+            (_color >> 8) & 0xFF,
+            (_color) & 0xFF,
+            (_color >> 24) & 0xFF];
+}
+
+- (void)setPipelineState:(id<MTLRenderCommandEncoder>)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+
+    MTLRenderPipelineDescriptor *rpDesc = nil;
+
+    NSString *vertShader = @"vert_col";
+    NSString *fragShader = @"frag_col";
+
+    if (renderOptions->isTexture) {
+        vertShader = @"vert_txt";
+        fragShader = @"frag_txt";
+        rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+        if (renderOptions->isAA) {
+            fragShader = @"aa_frag_txt";
+            rpDesc = [[templateAATexturePipelineDesc copy] autorelease];
+        }
+        if (renderOptions->isText) {
+            fragShader = @"frag_text";
+        }
+        if (renderOptions->isLCD) {
+            vertShader = @"vert_txt_lcd";
+            fragShader = @"lcd_color";
+            rpDesc = [[templateLCDPipelineDesc copy] autorelease];
+        }
+        setTxtUniforms(mtlc, _color, encoder,
+                       renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha], &renderOptions->srcFlags,
+                       &renderOptions->dstFlags, 1);
+    } else if (renderOptions->isAAShader) {
+        vertShader = @"vert_col_aa";
+        fragShader = @"frag_col_aa";
+        rpDesc = [[templateAAPipelineDesc copy] autorelease];
+    } else {
+        rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+    }
+
+    struct FrameUniforms uf = {RGBA_TO_V4(_color)};
+    [encoder setVertexBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+
+- (void)setXorModePipelineState:(id<MTLRenderCommandEncoder>)encoder
+                        context:(MTLContext *)mtlc
+                  renderOptions:(const RenderOptions *)renderOptions
+           pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+    NSString * vertShader = @"vert_col_xorMode";
+    NSString * fragShader = @"frag_col_xorMode";
+    MTLRenderPipelineDescriptor * rpDesc = nil;
+    jint xorColor = (jint) [mtlc.composite getXorColor];
+    // Calculate _color ^ xorColor for RGB components
+    // This color gets XORed with destination framebuffer pixel color
+    const int col = _color ^ xorColor;
+    BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+
+    if (renderOptions->isTexture) {
+        vertShader = @"vert_txt_xorMode";
+        fragShader = @"frag_txt_xorMode";
+        rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+
+        setTxtUniforms(mtlc, col, encoder,
+                       renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
+                       &renderOptions->srcFlags, &renderOptions->dstFlags, 1);
+        [encoder setFragmentBytes:&xorColor length:sizeof(xorColor) atIndex:0];
+
+        [encoder setFragmentTexture:dstOps->pTexture atIndex:1];
+    } else {
+        struct FrameUniforms uf = {RGBA_TO_V4(col)};
+        rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+
+        [encoder setVertexBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+        [encoder setFragmentTexture:dstOps->pTexture atIndex:0];
+    }
+
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+@end
+
+@implementation MTLBaseGradPaint {
+    jboolean      _useMask;
+@protected
+    jint          _cyclic;
+}
+
+- (id)initWithState:(jint)state mask:(jboolean)useMask cyclic:(jboolean)cyclic {
+    self = [super initWithState:state];
+
+    if (self) {
+        _useMask = useMask;
+        _cyclic = cyclic;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLBaseGradPaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![[other class] isEqual:[self class]])
+        return NO;
+
+    return [super isEqual:self] && _cyclic == other->_cyclic && _useMask == other->_useMask;
+}
+
+- (NSUInteger)hash {
+    NSUInteger h = [super hash];
+    h = h*31 + _cyclic;
+    h = h*31 + _useMask;
+    return h;
+}
+
+@end
+
+@implementation MTLGradPaint {
+    jdouble _p0;
+    jdouble _p1;
+    jdouble _p3;
+    jint _pixel1;
+    jint _pixel2;
+}
+- (id)initWithUseMask:(jboolean)useMask
+               cyclic:(jboolean)cyclic
+                   p0:(jdouble)p0
+                   p1:(jdouble)p1
+                   p3:(jdouble)p3
+               pixel1:(jint)pixel1
+               pixel2:(jint)pixel2
+{
+    self = [super initWithState:sun_java2d_SunGraphics2D_PAINT_GRADIENT
+                           mask:useMask
+                         cyclic:cyclic];
+
+    if (self) {
+        _p0 = p0;
+        _p1 = p1;
+        _p3 = p3;
+        _pixel1 = pixel1;
+        _pixel2 = pixel2;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLGradPaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![[other class] isEqual:[self class]])
+        return NO;
+
+    return [super isEqual:self] && _p0 == other->_p0 &&
+           _p1 == other->_p1 && _p3 == other->_p3 &&
+           _pixel1 == other->_pixel1 && _pixel2 == other->_pixel2;
+}
+
+- (NSUInteger)hash {
+    NSUInteger h = [super hash];
+    h = h*31 + [@(_p0) hash];
+    h = h*31 + [@(_p1) hash];;
+    h = h*31 + [@(_p3) hash];;
+    h = h*31 + _pixel1;
+    h = h*31 + _pixel2;
+    return h;
+}
+- (NSString *)description {
+    return [NSString stringWithFormat:@"gradient"];
+}
+
+- (void)setPipelineState:(id)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+    MTLRenderPipelineDescriptor *rpDesc = nil;
+
+    NSString *vertShader = @"vert_grad";
+    NSString *fragShader = @"frag_grad";
+
+    struct GradFrameUniforms uf = {
+            {_p0, _p1, _p3},
+            RGBA_TO_V4(_pixel1),
+            RGBA_TO_V4(_pixel2),
+            _cyclic,
+            [mtlc.composite getExtraAlpha]
+    };
+    [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:0];
+
+    if (renderOptions->isTexture) {
+        vertShader = @"vert_txt_grad";
+        fragShader = @"frag_txt_grad";
+        rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+    } else {
+        rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+    }
+
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+
+- (void)setXorModePipelineState:(id)encoder
+                        context:(MTLContext *)mtlc
+                  renderOptions:(const RenderOptions *)renderOptions
+           pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    // This block is not reached in current implementation.
+    // Gradient paint XOR mode rendering uses a tile based rendering using a SW pipe (similar to OGL)
+    initTemplatePipelineDescriptors();
+    NSString* vertShader = @"vert_grad_xorMode";
+    NSString* fragShader = @"frag_grad_xorMode";
+    MTLRenderPipelineDescriptor *rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+    jint xorColor = (jint) [mtlc.composite getXorColor];
+
+    struct GradFrameUniforms uf = {
+            {_p0, _p1, _p3},
+            RGBA_TO_V4(_pixel1 ^ xorColor),
+            RGBA_TO_V4(_pixel2 ^ xorColor),
+            _cyclic,
+            [mtlc.composite getExtraAlpha]
+    };
+
+    [encoder setFragmentBytes: &uf length:sizeof(uf) atIndex:0];
+    BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+    [encoder setFragmentTexture:dstOps->pTexture atIndex:0];
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLPaints - setXorModePipelineState -- PAINT_GRADIENT");
+
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+
+@end
+
+@implementation MTLBaseMultiGradPaint {
+    @protected
+    jboolean _linear;
+    jint _numFracts;
+    jfloat _fract[GRAD_MAX_FRACTIONS];
+    jint _pixel[GRAD_MAX_FRACTIONS];
+}
+
+- (id)initWithState:(jint)state
+               mask:(jboolean)useMask
+             linear:(jboolean)linear
+        cycleMethod:(jboolean)cycleMethod
+           numStops:(jint)numStops
+          fractions:(jfloat *)fractions
+             pixels:(jint *)pixels
+{
+    self = [super initWithState:state
+                           mask:useMask
+                         cyclic:cycleMethod];
+
+    if (self) {
+        _linear = linear;
+        memcpy(_fract, fractions,numStops*sizeof(jfloat));
+        memcpy(_pixel, pixels, numStops*sizeof(jint));
+        _numFracts = numStops;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLBaseMultiGradPaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![[other class] isEqual:[self class]])
+        return NO;
+
+    if (_numFracts != other->_numFracts || ![super isEqual:self])
+        return NO;
+    for (int i = 0; i < _numFracts; i++) {
+        if (_fract[i] != other->_fract[i]) return NO;
+        if (_pixel[i] != other->_pixel[i]) return NO;
+    }
+    return YES;
+}
+
+- (NSUInteger)hash {
+    NSUInteger h = [super hash];
+    h = h*31 + _numFracts;
+    for (int i = 0; i < _numFracts; i++) {
+        h = h*31 + [@(_fract[i]) hash];
+        h = h*31 + _pixel[i];
+    }
+    return h;
+}
+
+@end
+
+@implementation MTLLinearGradPaint {
+    jdouble _p0;
+    jdouble _p1;
+    jdouble _p3;
+}
+- (void)setPipelineState:(id)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+
+{
+    initTemplatePipelineDescriptors();
+    MTLRenderPipelineDescriptor *rpDesc = nil;
+
+    NSString *vertShader = @"vert_grad";
+    NSString *fragShader = @"frag_lin_grad";
+
+    if (renderOptions->isTexture) {
+        vertShader = @"vert_txt_grad";
+        fragShader = @"frag_txt_lin_grad";
+        rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+    } else {
+        rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+    }
+
+    struct LinGradFrameUniforms uf = {
+            {_p0, _p1, _p3},
+            {},
+            {},
+            _numFracts,
+            _linear,
+            _cyclic,
+            [mtlc.composite getExtraAlpha]
+    };
+
+    memcpy(uf.fract, _fract, _numFracts*sizeof(jfloat));
+    for (int i = 0; i < _numFracts; i++) {
+        vector_float4 v = RGBA_TO_V4(_pixel[i]);
+        uf.color[i] = v;
+    }
+    [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:0];
+
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+
+- (id)initWithUseMask:(jboolean)useMask
+               linear:(jboolean)linear
+          cycleMethod:(jboolean)cycleMethod
+             numStops:(jint)numStops
+                   p0:(jfloat)p0
+                   p1:(jfloat)p1
+                   p3:(jfloat)p3
+            fractions:(jfloat *)fractions
+               pixels:(jint *)pixels
+{
+    self = [super initWithState:sun_java2d_SunGraphics2D_PAINT_LIN_GRADIENT
+                           mask:useMask
+                         linear:linear
+                    cycleMethod:cycleMethod
+                       numStops:numStops
+                      fractions:fractions
+                         pixels:pixels];
+
+    if (self) {
+        _p0 = p0;
+        _p1 = p1;
+        _p3 = p3;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLLinearGradPaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![[other class] isEqual:[self class]] || ![super isEqual:other])
+        return NO;
+
+    return _p0 == other->_p0 && _p1 == other->_p1 && _p3 == other->_p3;
+}
+
+- (NSUInteger)hash {
+    NSUInteger h = [super hash];
+    h = h*31 + [@(_p0) hash];
+    h = h*31 + [@(_p1) hash];
+    h = h*31 + [@(_p3) hash];
+    return h;
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:@"linear_gradient"];
+}
+@end
+
+@implementation MTLRadialGradPaint {
+    jfloat _m00;
+    jfloat _m01;
+    jfloat _m02;
+    jfloat _m10;
+    jfloat _m11;
+    jfloat _m12;
+    jfloat _focusX;
+}
+
+- (id)initWithUseMask:(jboolean)useMask
+               linear:(jboolean)linear
+          cycleMethod:(jint)cycleMethod
+             numStops:(jint)numStops
+                  m00:(jfloat)m00
+                  m01:(jfloat)m01
+                  m02:(jfloat)m02
+                  m10:(jfloat)m10
+                  m11:(jfloat)m11
+                  m12:(jfloat)m12
+               focusX:(jfloat)focusX
+            fractions:(void *)fractions
+               pixels:(void *)pixels
+{
+    self = [super initWithState:sun_java2d_SunGraphics2D_PAINT_RAD_GRADIENT
+                           mask:useMask
+                         linear:linear
+                    cycleMethod:cycleMethod
+                       numStops:numStops
+                      fractions:fractions
+                         pixels:pixels];
+
+    if (self) {
+        _m00 = m00;
+        _m01 = m01;
+        _m02 = m02;
+        _m10 = m10;
+        _m11 = m11;
+        _m12 = m12;
+        _focusX = focusX;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLRadialGradPaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![[other class] isEqual:[self class]]
+            || ![super isEqual:self])
+        return NO;
+
+    return _m00 == other->_m00 && _m01 == other->_m01 && _m02 == other->_m02 &&
+           _m10 == other->_m10 && _m11 == other->_m11 && _m12 == other->_m12 &&
+           _focusX == other->_focusX;
+}
+
+- (NSUInteger)hash {
+    NSUInteger h = [super hash];
+    h = h*31 + [@(_m00) hash];
+    h = h*31 + [@(_m01) hash];
+    h = h*31 + [@(_m02) hash];
+    h = h*31 + [@(_m10) hash];
+    h = h*31 + [@(_m11) hash];
+    h = h*31 + [@(_m12) hash];
+    return h;
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:@"radial_gradient"];
+}
+
+- (void)setPipelineState:(id)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+    MTLRenderPipelineDescriptor *rpDesc = nil;
+
+    NSString *vertShader = @"vert_grad";
+    NSString *fragShader = @"frag_rad_grad";
+
+    if (renderOptions->isTexture) {
+        vertShader = @"vert_txt_grad";
+        fragShader = @"frag_txt_rad_grad";
+        rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+    } else {
+        rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+    }
+
+    struct RadGradFrameUniforms uf = {
+            {},
+            {},
+            _numFracts,
+            _linear,
+            _cyclic,
+            {_m00, _m01, _m02},
+            {_m10, _m11, _m12},
+            {},
+            [mtlc.composite getExtraAlpha]
+    };
+
+    uf.precalc[0] = _focusX;
+    uf.precalc[1] = 1.0 - (_focusX * _focusX);
+    uf.precalc[2] = 1.0 / uf.precalc[1];
+
+    memcpy(uf.fract, _fract, _numFracts*sizeof(jfloat));
+    for (int i = 0; i < _numFracts; i++) {
+        vector_float4 v = RGBA_TO_V4(_pixel[i]);
+        uf.color[i] = v;
+    }
+    [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:0];
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+
+
+@end
+
+@implementation MTLTexturePaint {
+    struct AnchorData _anchor;
+    id <MTLTexture> _paintTexture;
+    jboolean _isOpaque;
+}
+
+- (id)initWithUseMask:(jboolean)useMask
+              textureID:(id)textureId
+               isOpaque:(jboolean)isOpaque
+                 filter:(jboolean)filter
+                    xp0:(jdouble)xp0
+                    xp1:(jdouble)xp1
+                    xp3:(jdouble)xp3
+                    yp0:(jdouble)yp0
+                    yp1:(jdouble)yp1
+                    yp3:(jdouble)yp3
+{
+    self = [super initWithState:sun_java2d_SunGraphics2D_PAINT_TEXTURE];
+
+    if (self) {
+        _paintTexture = textureId;
+        _anchor.xParams[0] = xp0;
+        _anchor.xParams[1] = xp1;
+        _anchor.xParams[2] = xp3;
+
+        _anchor.yParams[0] = yp0;
+        _anchor.yParams[1] = yp1;
+        _anchor.yParams[2] = yp3;
+        _isOpaque = isOpaque;
+    }
+    return self;
+
+}
+
+- (BOOL)isEqual:(MTLTexturePaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![[other class] isEqual:[self class]])
+        return NO;
+
+    return [_paintTexture isEqual:other->_paintTexture]
+            && _anchor.xParams[0] == other->_anchor.xParams[0]
+            && _anchor.xParams[1] == other->_anchor.xParams[1]
+            && _anchor.xParams[2] == other->_anchor.xParams[2]
+            && _anchor.yParams[0] == other->_anchor.yParams[0]
+            && _anchor.yParams[1] == other->_anchor.yParams[1]
+            && _anchor.yParams[2] == other->_anchor.yParams[2];
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:@"texture_paint"];
+}
+
+- (void)setPipelineState:(id)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+    MTLRenderPipelineDescriptor *rpDesc = nil;
+
+    NSString* vertShader = @"vert_tp";
+    NSString* fragShader = @"frag_tp";
+
+    [encoder setVertexBytes:&_anchor length:sizeof(_anchor) atIndex:FrameUniformBuffer];
+
+    if (renderOptions->isTexture) {
+        vertShader = @"vert_txt_tp";
+        fragShader = @"frag_txt_tp";
+        rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+        [encoder setFragmentTexture:_paintTexture atIndex:1];
+    } else {
+        rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+        [encoder setFragmentTexture:_paintTexture atIndex:0];
+    }
+    const SurfaceRasterFlags srcFlags = {_isOpaque, renderOptions->srcFlags.isPremultiplied};
+    setTxtUniforms(mtlc, 0, encoder,
+                   renderOptions->interpolation, YES, [mtlc.composite getExtraAlpha],
+                   &srcFlags, &renderOptions->dstFlags, 0);
+
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+
+- (void)setXorModePipelineState:(id)encoder
+                        context:(MTLContext *)mtlc
+                  renderOptions:(const RenderOptions *)renderOptions
+           pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+    // This block is not reached in current implementation.
+    // Texture paint XOR mode rendering uses a tile based rendering using a SW pipe (similar to OGL)
+    NSString* vertShader = @"vert_tp_xorMode";
+    NSString* fragShader = @"frag_tp_xorMode";
+    MTLRenderPipelineDescriptor *rpDesc = [[templateRenderPipelineDesc copy] autorelease];
+    jint xorColor = (jint) [mtlc.composite getXorColor];
+
+    [encoder setVertexBytes:&_anchor length:sizeof(_anchor) atIndex:FrameUniformBuffer];
+    [encoder setFragmentTexture:_paintTexture atIndex: 0];
+    BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+    [encoder setFragmentTexture:dstOps->pTexture atIndex:1];
+    [encoder setFragmentBytes:&xorColor length:sizeof(xorColor) atIndex: 0];
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLPaints - setXorModePipelineState -- PAINT_TEXTURE");
+
+    id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                        vertexShaderId:vertShader
+                                                                      fragmentShaderId:fragShader
+                                                                             composite:mtlc.composite
+                                                                         renderOptions:renderOptions
+                                                                         stencilNeeded:[mtlc.clip isShape]];
+    [encoder setRenderPipelineState:pipelineState];
+}
+
+@end
+
+@implementation MTLPaint {
+    jint _paintState;
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        _paintState = sun_java2d_SunGraphics2D_PAINT_UNDEFINED;
+    }
+
+    return self;
+}
+
+- (instancetype)initWithState:(jint)state {
+    self = [super init];
+    if (self) {
+        _paintState = state;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLPaint *)other {
+    if (other == self)
+        return YES;
+    if (!other || ![other isKindOfClass:[self class]])
+        return NO;
+    return _paintState == other->_paintState;
+}
+
+- (NSUInteger)hash {
+    return _paintState;
+}
+
+- (NSString *)description {
+    return @"unknown-paint";
+}
+
+static void
+setTxtUniforms(MTLContext *mtlc, int color, id <MTLRenderCommandEncoder> encoder, int interpolation, bool repeat,
+               jfloat extraAlpha, const SurfaceRasterFlags *srcFlags, const SurfaceRasterFlags *dstFlags, int mode) {
+    struct TxtFrameUniforms uf = {RGBA_TO_V4(color), mode, srcFlags->isOpaque, dstFlags->isOpaque, extraAlpha};
+    [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+    [mtlc.samplerManager setSamplerWithEncoder:encoder interpolation:interpolation repeat:repeat];
+}
+
+// For the current paint mode:
+// 1. Selects vertex+fragment shaders (and corresponding pipelineDesc) and set pipelineState
+// 2. Set vertex and fragment buffers
+// Base implementation is used in drawTex2Tex
+- (void)setPipelineState:(id <MTLRenderCommandEncoder>)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+    // Called from drawTex2Tex used in flushBuffer and for buffered image ops
+    if (renderOptions->isTexture) {
+        NSString * vertShader = @"vert_txt";
+        NSString * fragShader = @"frag_txt";
+        MTLRenderPipelineDescriptor* rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+
+        NSObject *bufImgOp = [mtlc getBufImgOp];
+        if (bufImgOp != nil) {
+            if ([bufImgOp isKindOfClass:[MTLRescaleOp class]]) {
+                MTLRescaleOp *rescaleOp = bufImgOp;
+                fragShader = @"frag_txt_op_rescale";
+
+                struct TxtFrameOpRescaleUniforms uf = {
+                        RGBA_TO_V4(0), [mtlc.composite getExtraAlpha], renderOptions->srcFlags.isOpaque,
+                        rescaleOp.isNonPremult,
+                        FLOAT_ARR_TO_V4([rescaleOp getScaleFactors]), FLOAT_ARR_TO_V4([rescaleOp getOffsets])
+                };
+                [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+                [mtlc.samplerManager setSamplerWithEncoder:encoder interpolation:renderOptions->interpolation repeat:NO];
+            } else if ([bufImgOp isKindOfClass:[MTLConvolveOp class]]) {
+                MTLConvolveOp *convolveOp = bufImgOp;
+                fragShader = @"frag_txt_op_convolve";
+
+                struct TxtFrameOpConvolveUniforms uf = {
+                        [mtlc.composite getExtraAlpha], renderOptions->srcFlags.isOpaque,
+                        FLOAT_ARR_TO_V4([convolveOp getImgEdge]),
+                        convolveOp.kernelSize, convolveOp.isEdgeZeroFill,
+                };
+                [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+                [mtlc.samplerManager setSamplerWithEncoder:encoder interpolation:renderOptions->interpolation repeat:NO];
+
+                [encoder setFragmentBuffer:[convolveOp getBuffer] offset:0 atIndex:2];
+            } else if ([bufImgOp isKindOfClass:[MTLLookupOp class]]) {
+                MTLLookupOp *lookupOp = bufImgOp;
+                fragShader = @"frag_txt_op_lookup";
+
+                struct TxtFrameOpLookupUniforms uf = {
+                        [mtlc.composite getExtraAlpha], renderOptions->srcFlags.isOpaque,
+                        FLOAT_ARR_TO_V4([lookupOp getOffset]), lookupOp.isUseSrcAlpha, lookupOp.isNonPremult,
+                };
+                [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+                [mtlc.samplerManager setSamplerWithEncoder:encoder interpolation:renderOptions->interpolation repeat:NO];
+                [encoder setFragmentTexture:[lookupOp getLookupTexture] atIndex:1];
+            }
+        } else {
+            setTxtUniforms(mtlc, 0, encoder,
+                           renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
+                           &renderOptions->srcFlags,
+                           &renderOptions->dstFlags, 0);
+
+        }
+        id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                            vertexShaderId:vertShader
+                                                                          fragmentShaderId:fragShader
+                                                                                 composite:mtlc.composite
+                                                                             renderOptions:renderOptions
+                                                                             stencilNeeded:[mtlc.clip isShape]];
+        [encoder setRenderPipelineState:pipelineState];
+    }
+}
+
+// For the current paint mode:
+// 1. Selects vertex+fragment shaders (and corresponding pipelineDesc) and set pipelineState
+// 2. Set vertex and fragment buffers
+- (void)setXorModePipelineState:(id <MTLRenderCommandEncoder>)encoder
+                 context:(MTLContext *)mtlc
+           renderOptions:(const RenderOptions *)renderOptions
+    pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
+{
+    initTemplatePipelineDescriptors();
+    if (renderOptions->isTexture) {
+        jint xorColor = (jint) [mtlc.composite getXorColor];
+        NSString * vertShader = @"vert_txt_xorMode";
+        NSString * fragShader = @"frag_txt_xorMode";
+        MTLRenderPipelineDescriptor * rpDesc = [[templateTexturePipelineDesc copy] autorelease];
+
+        const int col = 0 ^ xorColor;
+        setTxtUniforms(mtlc, col, encoder,
+                       renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
+                       &renderOptions->srcFlags, &renderOptions->dstFlags, 0);
+        [encoder setFragmentBytes:&xorColor length:sizeof(xorColor) atIndex: 0];
+
+        BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+        [encoder setFragmentTexture:dstOps->pTexture atIndex:1];
+
+        setTxtUniforms(mtlc, 0, encoder,
+                       renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
+                       &renderOptions->srcFlags,
+                       &renderOptions->dstFlags, 0);
+
+        id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
+                                                                            vertexShaderId:vertShader
+                                                                          fragmentShaderId:fragShader
+                                                                                 composite:mtlc.composite
+                                                                             renderOptions:renderOptions
+                                                                             stencilNeeded:[mtlc.clip isShape]];
+        [encoder setRenderPipelineState:pipelineState];
+    }
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPipelineStatesStorage.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPipelineStatesStorage.h
new file mode 100644
index 00000000000..527250be971
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPipelineStatesStorage.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLPipelineStatesStorage_h_Included
+#define MTLPipelineStatesStorage_h_Included
+
+#import "MTLUtils.h"
+#include "RenderOptions.h"
+
+@class MTLComposite;
+
+/**
+ * The MTLPipelineStatesStorage class used to obtain MTLRenderPipelineState
+ * */
+
+
+@interface MTLPipelineStatesStorage : NSObject {
+@private
+
+id<MTLDevice>       device;
+id<MTLLibrary>      library;
+NSMutableDictionary<NSString*, id<MTLFunction>> * shaders;
+NSMutableDictionary<NSString*, id<MTLComputePipelineState>> * computeStates;
+}
+
+@property (readwrite, assign) id<MTLDevice> device;
+@property (readwrite, retain) id<MTLLibrary> library;
+@property (readwrite, retain) NSMutableDictionary<NSString*, id<MTLFunction>> * shaders;
+@property (readwrite, retain) NSMutableDictionary<NSString*, NSMutableDictionary *> * states;
+
+- (id) initWithDevice:(id<MTLDevice>)device shaderLibPath:(NSString *)shadersLib;
+
+- (id<MTLRenderPipelineState>) getPipelineState:(MTLRenderPipelineDescriptor *) pipelineDescriptor
+                                 vertexShaderId:(NSString *)vertexShaderId
+                               fragmentShaderId:(NSString *)fragmentShaderId;
+
+- (id<MTLRenderPipelineState>) getPipelineState:(MTLRenderPipelineDescriptor *) pipelineDescriptor
+                                 vertexShaderId:(NSString *)vertexShaderId
+                               fragmentShaderId:(NSString *)fragmentShaderId
+                                  stencilNeeded:(bool)stencilNeeded;
+
+- (id<MTLRenderPipelineState>) getPipelineState:(MTLRenderPipelineDescriptor *) pipelineDescriptor
+                                 vertexShaderId:(NSString *)vertexShaderId
+                               fragmentShaderId:(NSString *)fragmentShaderId
+                                      composite:(MTLComposite*)composite
+                                  renderOptions:(const RenderOptions *)renderOptions
+                                  stencilNeeded:(bool)stencilNeeded;
+
+- (id<MTLComputePipelineState>) getComputePipelineState:(NSString *)computeShaderId;
+
+- (id<MTLFunction>) getShader:(NSString *)name;
+@end
+
+
+#endif // MTLPipelineStatesStorage_h_Included
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPipelineStatesStorage.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPipelineStatesStorage.m
new file mode 100644
index 00000000000..f9b9ec5d0c2
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPipelineStatesStorage.m
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import "MTLPipelineStatesStorage.h"
+
+#include "GraphicsPrimitiveMgr.h"
+#import "MTLComposite.h"
+
+#include "sun_java2d_SunGraphics2D.h"
+
+extern const SurfaceRasterFlags defaultRasterFlags;
+
+static void setBlendingFactors(
+        MTLRenderPipelineColorAttachmentDescriptor * cad,
+        MTLComposite* composite,
+        const RenderOptions * renderOptions);
+
+@implementation MTLPipelineStatesStorage
+
+@synthesize device;
+@synthesize library;
+@synthesize shaders;
+@synthesize states;
+
+- (id) initWithDevice:(id<MTLDevice>)dev shaderLibPath:(NSString *)shadersLib {
+    self = [super init];
+    if (self == nil) return self;
+
+    self.device = dev;
+
+    NSError *error = nil;
+    self.library = [dev newLibraryWithFile:shadersLib error:&error];
+    if (!self.library) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLPipelineStatesStorage.initWithDevice() - Failed to load Metal shader library.");
+        return nil;
+    }
+    self.shaders = [NSMutableDictionary dictionaryWithCapacity:10];
+    self.states = [NSMutableDictionary dictionaryWithCapacity:10];
+    computeStates = [[NSMutableDictionary dictionaryWithCapacity:10] retain] ;
+    return self;
+}
+
+- (NSPointerArray * ) getSubStates:(NSString *)vertexShaderId fragmentShader:(NSString *)fragmentShaderId {
+    NSMutableDictionary * vSubStates = states[vertexShaderId];
+    if (vSubStates == nil) {
+        @autoreleasepool {
+            vSubStates = [NSMutableDictionary dictionary];
+            [states setObject:vSubStates forKey:vertexShaderId];
+        }
+    }
+    NSPointerArray * sSubStates = vSubStates[fragmentShaderId];
+    if (sSubStates == nil) {
+        @autoreleasepool {
+            sSubStates = [NSPointerArray strongObjectsPointerArray];
+            [vSubStates setObject:sSubStates forKey:fragmentShaderId];
+        }
+    }
+    return sSubStates;
+}
+
+- (id<MTLRenderPipelineState>) getPipelineState:(MTLRenderPipelineDescriptor *) pipelineDescriptor
+                                 vertexShaderId:(NSString *)vertexShaderId
+                               fragmentShaderId:(NSString *)fragmentShaderId
+{
+    RenderOptions defaultOptions = {JNI_FALSE, JNI_FALSE, 0/*unused*/, {JNI_FALSE, JNI_TRUE}, {JNI_FALSE, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
+    return [self getPipelineState:pipelineDescriptor
+                   vertexShaderId:vertexShaderId
+                 fragmentShaderId:fragmentShaderId
+                        composite:nil
+                    renderOptions:&defaultOptions
+                    stencilNeeded:NO];
+}
+
+- (id<MTLRenderPipelineState>) getPipelineState:(MTLRenderPipelineDescriptor *) pipelineDescriptor
+                                 vertexShaderId:(NSString *)vertexShaderId
+                               fragmentShaderId:(NSString *)fragmentShaderId
+                               stencilNeeded:(bool)stencilNeeded
+{
+    RenderOptions defaultOptions = {JNI_FALSE, JNI_FALSE, 0/*unused*/, {JNI_FALSE, JNI_TRUE}, {JNI_FALSE, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
+    return [self getPipelineState:pipelineDescriptor
+                   vertexShaderId:vertexShaderId
+                 fragmentShaderId:fragmentShaderId
+                        composite:nil
+                    renderOptions:&defaultOptions
+                    stencilNeeded:stencilNeeded];
+}
+
+// Base method to obtain MTLRenderPipelineState.
+// NOTE: parameters compositeRule, srcFlags, dstFlags are used to set MTLRenderPipelineColorAttachmentDescriptor multipliers
+- (id<MTLRenderPipelineState>) getPipelineState:(MTLRenderPipelineDescriptor *) pipelineDescriptor
+                                 vertexShaderId:(NSString *)vertexShaderId
+                               fragmentShaderId:(NSString *)fragmentShaderId
+                                      composite:(MTLComposite*) composite
+                                  renderOptions:(const RenderOptions *)renderOptions
+                                  stencilNeeded:(bool)stencilNeeded;
+{
+    jint compositeRule = composite != nil ? [composite getRule] : RULE_Src;
+    const jboolean useXorComposite = composite != nil && [composite getCompositeState] == sun_java2d_SunGraphics2D_COMP_XOR;
+    const jboolean useComposite = composite != nil && compositeRule >= 0
+        && compositeRule < java_awt_AlphaComposite_MAX_RULE;
+
+    // Calculate index by flags and compositeRule
+    // TODO: reimplement, use map with convenient key (calculated by all arguments)
+    int subIndex = 0;
+    if (useXorComposite) {
+        // compositeRule value is already XOR_COMPOSITE_RULE
+    }
+    else {
+        if (useComposite) {
+            if (!renderOptions->srcFlags.isPremultiplied)
+                subIndex |= 1;
+            if (renderOptions->srcFlags.isOpaque)
+                subIndex |= 1 << 1;
+            if (!renderOptions->dstFlags.isPremultiplied)
+                subIndex |= 1 << 2;
+            if (renderOptions->dstFlags.isOpaque)
+                subIndex |= 1 << 3;
+        } else
+            compositeRule = RULE_Src;
+    }
+
+    if (stencilNeeded) {
+        subIndex |= 1 << 4;
+    }
+
+    if (renderOptions->isAA) {
+        subIndex |= 1 << 5;
+    }
+
+    if ((composite != nil && FLT_LT([composite getExtraAlpha], 1.0f))) {
+        subIndex |= 1 << 6;
+    }
+    int index = compositeRule*64 + subIndex;
+
+    NSPointerArray * subStates = [self getSubStates:vertexShaderId fragmentShader:fragmentShaderId];
+
+    if (index >= subStates.count) {
+        subStates.count = (NSUInteger) (index + 1);
+    }
+
+    id<MTLRenderPipelineState> result = [subStates pointerAtIndex:index];
+    if (result == nil) {
+        @autoreleasepool {
+            id <MTLFunction> vertexShader = [self getShader:vertexShaderId];
+            id <MTLFunction> fragmentShader = [self getShader:fragmentShaderId];
+            MTLRenderPipelineDescriptor *pipelineDesc = [[pipelineDescriptor copy] autorelease];
+            pipelineDesc.vertexFunction = vertexShader;
+            pipelineDesc.fragmentFunction = fragmentShader;
+
+            if (useXorComposite) {
+                /* The below configuration is the best performant implementation of XOR mode rendering.
+                   It was found that it works ONLY for basic Colors and not for all RGB combinations.
+                   Hence, a slow performant XOR mode rendering has been implemented by
+                   disabling blending & committing after each draw call.
+                   In XOR mode rendering, subsequent draw calls are rendered
+                   by shader using already rendered framebuffer pixel value XORed
+                   with current draw color and XOR color.
+                pipelineDesc.colorAttachments[0].blendingEnabled = YES;
+                pipelineDesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
+                pipelineDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOneMinusDestinationColor;
+                pipelineDesc.colorAttachments[0].destinationRGBBlendFactor =  MTLBlendFactorOneMinusSourceColor;
+                */
+
+                pipelineDesc.colorAttachments[0].blendingEnabled = NO;
+            } else if (useComposite ||
+                       (composite != nil  &&
+                        FLT_LT([composite getExtraAlpha], 1.0f)))
+            {
+                setBlendingFactors(
+                        pipelineDesc.colorAttachments[0],
+                        composite,
+                        renderOptions
+                );
+            }
+            if (stencilNeeded) {
+                pipelineDesc.stencilAttachmentPixelFormat = MTLPixelFormatStencil8;
+            } else {
+                // We continue to use same encoder when we move from shape clip
+                // to other opcodes. So we need to maintain apprppriate state
+                // for stencilAttachmentPixelFormat until we end the encoder
+                pipelineDesc.stencilAttachmentPixelFormat = MTLPixelFormatInvalid;
+            }
+
+            if (renderOptions->isAA) {
+                pipelineDesc.sampleCount = MTLAASampleCount;
+                pipelineDesc.colorAttachments[0].rgbBlendOperation =   MTLBlendOperationAdd;
+                pipelineDesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
+                pipelineDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
+                pipelineDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
+                pipelineDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+                pipelineDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+                pipelineDesc.colorAttachments[0].blendingEnabled = YES;
+            }
+
+            NSError *error = nil;
+            result = [[self.device newRenderPipelineStateWithDescriptor:pipelineDesc error:&error] autorelease];
+            if (result == nil) {
+                NSLog(@"Failed to create pipeline state, error %@", error);
+                exit(0);
+            }
+
+            [subStates insertPointer:result atIndex:index];
+        }
+    }
+
+    return result;
+}
+
+- (id<MTLComputePipelineState>) getComputePipelineState:(NSString *)computeShaderId {
+    id<MTLComputePipelineState> result = computeStates[computeShaderId];
+    if (result == nil) {
+        id <MTLFunction> computeShader = [self getShader:computeShaderId];
+        @autoreleasepool {
+            NSError *error = nil;
+            result = (id <MTLComputePipelineState>) [[self.device newComputePipelineStateWithFunction:computeShader error:&error] autorelease];
+            if (result == nil) {
+                NSLog(@"Failed to create pipeline state, error %@", error);
+                exit(0);
+            }
+            computeStates[computeShaderId] = result;
+        }
+    }
+    return result;
+}
+
+- (id<MTLFunction>) getShader:(NSString *)name {
+    id<MTLFunction> result = [self.shaders valueForKey:name];
+    if (result == nil) {
+        result = [[self.library newFunctionWithName:name] autorelease];
+        [self.shaders setValue:result forKey:name];
+    }
+    return result;
+}
+
+- (void) dealloc {
+    [super dealloc];
+    [computeStates release];
+}
+@end
+
+/**
+ * The MTLBlendRule structure encapsulates the two enumerated values that
+ * comprise a given Porter-Duff blending (compositing) rule.  For example,
+ * the "SrcOver" rule can be represented by:
+ *     rule.src = GL_ONE;
+ *     rule.dst = GL_ONE_MINUS_SRC_ALPHA;
+ *
+ *     GLenum src;
+ * The constant representing the source factor in this Porter-Duff rule.
+ *
+ *     GLenum dst;
+ * The constant representing the destination factor in this Porter-Duff rule.
+ */
+struct MTLBlendRule {
+    MTLBlendFactor src;
+    MTLBlendFactor dst;
+};
+
+/**
+ * This table contains the standard blending rules (or Porter-Duff compositing
+ * factors) used in setBlendingFactors(), indexed by the rule constants from the
+ * AlphaComposite class.
+ */
+static struct MTLBlendRule StdBlendRules[] = {
+        { MTLBlendFactorZero,                     MTLBlendFactorZero                }, /* 0 - Nothing      */
+        { MTLBlendFactorZero,                     MTLBlendFactorZero                }, /* 1 - RULE_Clear   */
+        { MTLBlendFactorOne,                      MTLBlendFactorZero                }, /* 2 - RULE_Src     */
+        { MTLBlendFactorOne,                      MTLBlendFactorOneMinusSourceAlpha }, /* 3 - RULE_SrcOver */
+        { MTLBlendFactorOneMinusDestinationAlpha, MTLBlendFactorOne                 }, /* 4 - RULE_DstOver */
+        { MTLBlendFactorDestinationAlpha,         MTLBlendFactorZero                }, /* 5 - RULE_SrcIn   */
+        { MTLBlendFactorZero,                     MTLBlendFactorSourceAlpha         }, /* 6 - RULE_DstIn   */
+        { MTLBlendFactorOneMinusDestinationAlpha, MTLBlendFactorZero                }, /* 7 - RULE_SrcOut  */
+        { MTLBlendFactorZero,                     MTLBlendFactorOneMinusSourceAlpha }, /* 8 - RULE_DstOut  */
+        { MTLBlendFactorZero,                     MTLBlendFactorOne                 }, /* 9 - RULE_Dst     */
+        { MTLBlendFactorDestinationAlpha,         MTLBlendFactorOneMinusSourceAlpha }, /*10 - RULE_SrcAtop */
+        { MTLBlendFactorOneMinusDestinationAlpha, MTLBlendFactorSourceAlpha         }, /*11 - RULE_DstAtop */
+        { MTLBlendFactorOneMinusDestinationAlpha, MTLBlendFactorOneMinusSourceAlpha }, /*12 - RULE_AlphaXor*/
+};
+
+static void setBlendingFactors(
+        MTLRenderPipelineColorAttachmentDescriptor * cad,
+        MTLComposite* composite,
+        const RenderOptions * renderOptions
+) {
+    const long compositeRule = composite != nil ? [composite getRule] : RULE_Src;
+
+    if ((compositeRule == RULE_Src || compositeRule == RULE_SrcOver) &&
+        (composite == nil || FLT_GE([composite getExtraAlpha], 1.0f)) &&
+        (renderOptions->srcFlags.isOpaque))
+    {
+        cad.blendingEnabled = NO;
+        return;
+    }
+
+    cad.blendingEnabled = YES;
+    cad.rgbBlendOperation = MTLBlendOperationAdd;
+    cad.alphaBlendOperation = MTLBlendOperationAdd;
+
+    cad.sourceAlphaBlendFactor = StdBlendRules[compositeRule].src;
+    cad.sourceRGBBlendFactor = StdBlendRules[compositeRule].src;
+    cad.destinationAlphaBlendFactor = StdBlendRules[compositeRule].dst;
+    cad.destinationRGBBlendFactor = StdBlendRules[compositeRule].dst;
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.h
new file mode 100644
index 00000000000..7231bfd026e
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLRenderQueue_h_Included
+#define MTLRenderQueue_h_Included
+
+#include "MTLContext.h"
+#include "MTLSurfaceData.h"
+#include "MTLVertexCache.h"
+
+/*
+ * The following macros are used to pick values (of the specified type) off
+ * the queue.
+ */
+#define NEXT_VAL(buf, type) (((type *)((buf) += sizeof(type)))[-1])
+#define NEXT_BYTE(buf)      NEXT_VAL(buf, unsigned char)
+#define NEXT_INT(buf)       NEXT_VAL(buf, jint)
+#define NEXT_FLOAT(buf)     NEXT_VAL(buf, jfloat)
+#define NEXT_BOOLEAN(buf)   (jboolean)NEXT_INT(buf)
+#define NEXT_LONG(buf)      NEXT_VAL(buf, jlong)
+#define NEXT_DOUBLE(buf)    NEXT_VAL(buf, jdouble)
+
+// Operations for CheckPreviousOp
+enum {
+  MTL_OP_INIT,
+  MTL_OP_AA,
+  MTL_OP_SET_COLOR,
+  MTL_OP_RESET_PAINT,
+  MTL_OP_SYNC,
+  MTL_OP_SHAPE_CLIP_SPANS,
+  MTL_OP_MASK_OP,
+  MTL_OP_OTHER
+};
+/*
+ * These macros now simply delegate to the CheckPreviousOp() method.
+ */
+#define CHECK_PREVIOUS_OP(op) MTLRenderQueue_CheckPreviousOp(op)
+#define RESET_PREVIOUS_OP() {mtlPreviousOp = MTL_OP_INIT;}
+
+/*
+ * Increments a pointer (buf) by the given number of bytes.
+ */
+#define SKIP_BYTES(buf, numbytes) buf += (numbytes)
+
+/*
+ * Extracts a value at the given offset from the provided packed value.
+ */
+#define EXTRACT_VAL(packedval, offset, mask) \
+    (((packedval) >> (offset)) & (mask))
+#define EXTRACT_BYTE(packedval, offset) \
+    (unsigned char)EXTRACT_VAL(packedval, offset, 0xff)
+#define EXTRACT_BOOLEAN(packedval, offset) \
+    (jboolean)EXTRACT_VAL(packedval, offset, 0x1)
+
+/*
+ * The following macros allow the caller to return (or continue) if the
+ * provided value is NULL.  (The strange else clause is included below to
+ * allow for a trailing ';' after RETURN/CONTINUE_IF_NULL() invocations.)
+ */
+#define ACT_IF_NULL(ACTION, value)         \
+    if ((value) == NULL) {                 \
+        J2dTraceLn1(J2D_TRACE_ERROR,       \
+                    "%s is null", #value); \
+        ACTION;                            \
+    } else do { } while (0)
+#define RETURN_IF_NULL(value)   ACT_IF_NULL(return, value)
+#define CONTINUE_IF_NULL(value) ACT_IF_NULL(continue, value)
+
+#define ACT_IF_TRUE(ACTION, value)         \
+    if ((value)) {                         \
+        J2dTraceLn1(J2D_TRACE_ERROR,       \
+                    "%s is false", #value);\
+        ACTION;                            \
+    } else do { } while (0)
+
+#define RETURN_IF_TRUE(value)   ACT_IF_TRUE(return, value)
+
+MTLContext *MTLRenderQueue_GetCurrentContext();
+BMTLSDOps *MTLRenderQueue_GetCurrentDestination();
+void commitEncodedCommands();
+
+extern jint mtlPreviousOp;
+
+#endif /* MTLRenderQueue_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m
new file mode 100644
index 00000000000..f708ce22cbf
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m
@@ -0,0 +1,956 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdlib.h>
+
+#include "sun_java2d_pipe_BufferedOpCodes.h"
+
+#include "jlong.h"
+#include "MTLBlitLoops.h"
+#include "MTLBufImgOps.h"
+#include "MTLMaskBlit.h"
+#include "MTLMaskFill.h"
+#include "MTLPaints.h"
+#include "MTLRenderQueue.h"
+#include "MTLRenderer.h"
+#include "MTLTextRenderer.h"
+#import "ThreadUtilities.h"
+
+/**
+ * References to the "current" context and destination surface.
+ */
+static MTLContext *mtlc = NULL;
+static BMTLSDOps *dstOps = NULL;
+jint mtlPreviousOp = MTL_OP_INIT;
+
+
+/**
+ * The following methods are implemented in the windowing system (i.e. GLX
+ * and WGL) source files.
+ */
+extern void MTLGC_DestroyMTLGraphicsConfig(jlong pConfigInfo);
+
+void MTLRenderQueue_CheckPreviousOp(jint op) {
+
+    if (mtlPreviousOp == op) {
+        // The op is the same as last time, so we can return immediately.
+        return;
+    }
+
+    if (op == MTL_OP_SET_COLOR) {
+        if (mtlPreviousOp != MTL_OP_MASK_OP) {
+            return; // SET_COLOR should not cause endEncoder
+        }
+    } else if (op == MTL_OP_MASK_OP) {
+        MTLVertexCache_EnableMaskCache(mtlc, dstOps);
+        mtlPreviousOp = op;
+        return;
+    }
+
+    J2dTraceLn1(J2D_TRACE_VERBOSE,
+                "MTLRenderQueue_CheckPreviousOp: new op=%d", op);
+
+    switch (mtlPreviousOp) {
+        case MTL_OP_INIT :
+            mtlPreviousOp = op;
+            return;
+        case MTL_OP_MASK_OP :
+            MTLVertexCache_DisableMaskCache(mtlc);
+            break;
+    }
+
+    if (mtlc != NULL) {
+        [mtlc.encoderManager endEncoder];
+
+        if (op == MTL_OP_RESET_PAINT || op == MTL_OP_SYNC || op == MTL_OP_SHAPE_CLIP_SPANS) {
+            MTLCommandBufferWrapper *cbwrapper = [mtlc pullCommandBufferWrapper];
+            id <MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+            [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+                [cbwrapper release];
+            }];
+            [commandbuf commit];
+            if (op == MTL_OP_SYNC || op == MTL_OP_SHAPE_CLIP_SPANS) {
+                [commandbuf waitUntilCompleted];
+            }
+        }
+    }
+    mtlPreviousOp = op;
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
+    (JNIEnv *env, jobject mtlrq,
+     jlong buf, jint limit)
+{
+    unsigned char *b, *end;
+
+    J2dTraceLn1(J2D_TRACE_INFO,
+                "MTLRenderQueue_flushBuffer: limit=%d", limit);
+
+    b = (unsigned char *)jlong_to_ptr(buf);
+    if (b == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+            "MTLRenderQueue_flushBuffer: cannot get direct buffer address");
+        return;
+    }
+
+    end = b + limit;
+    @autoreleasepool {
+        while (b < end) {
+            jint opcode = NEXT_INT(b);
+
+            J2dTraceLn2(J2D_TRACE_VERBOSE,
+                    "MTLRenderQueue_flushBuffer: opcode=%d, rem=%d",
+                    opcode, (end-b));
+
+            switch (opcode) {
+
+                // draw ops
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_LINE:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "DRAW_LINE in XOR mode - Force commit earlier draw calls before DRAW_LINE.");
+                    }
+                    jint x1 = NEXT_INT(b);
+                    jint y1 = NEXT_INT(b);
+                    jint x2 = NEXT_INT(b);
+                    jint y2 = NEXT_INT(b);
+                    MTLRenderer_DrawLine(mtlc, dstOps, x1, y1, x2, y2);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_RECT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "DRAW_RECT in XOR mode - Force commit earlier draw calls before DRAW_RECT.");
+                    }
+                    jint x = NEXT_INT(b);
+                    jint y = NEXT_INT(b);
+                    jint w = NEXT_INT(b);
+                    jint h = NEXT_INT(b);
+                    MTLRenderer_DrawRect(mtlc, dstOps, x, y, w, h);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_POLY:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint nPoints      = NEXT_INT(b);
+                    jboolean isClosed = NEXT_BOOLEAN(b);
+                    jint transX       = NEXT_INT(b);
+                    jint transY       = NEXT_INT(b);
+                    jint *xPoints = (jint *)b;
+                    jint *yPoints = ((jint *)b) + nPoints;
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "DRAW_POLY in XOR mode - Force commit earlier draw calls before DRAW_POLY.");
+
+                        // draw separate (N-1) lines using N points
+                        for(int point = 0; point < nPoints-1; point++) {
+                            jint x1 = xPoints[point] + transX;
+                            jint y1 = yPoints[point] + transY;
+                            jint x2 = xPoints[point + 1] + transX;
+                            jint y2 = yPoints[point + 1] + transY;
+                            MTLRenderer_DrawLine(mtlc, dstOps, x1, y1, x2, y2);
+                        }
+
+                        if (isClosed) {
+                            MTLRenderer_DrawLine(mtlc, dstOps, xPoints[0] + transX, yPoints[0] + transY,
+                                                 xPoints[nPoints-1] + transX, yPoints[nPoints-1] + transY);
+                        }
+                    } else {
+                        MTLRenderer_DrawPoly(mtlc, dstOps, nPoints, isClosed, transX, transY, xPoints, yPoints);
+                    }
+
+                    SKIP_BYTES(b, nPoints * BYTES_PER_POLY_POINT);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_PIXEL:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "DRAW_PIXEL in XOR mode - Force commit earlier draw calls before DRAW_PIXEL.");
+                    }
+
+                    jint x = NEXT_INT(b);
+                    jint y = NEXT_INT(b);
+                    CONTINUE_IF_NULL(mtlc);
+                    MTLRenderer_DrawPixel(mtlc, dstOps, x, y);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_SCANLINES:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "DRAW_SCANLINES in XOR mode - Force commit earlier draw calls before "
+                                   "DRAW_SCANLINES.");
+                    }
+
+                    jint count = NEXT_INT(b);
+                    MTLRenderer_DrawScanlines(mtlc, dstOps, count, (jint *)b);
+
+                    SKIP_BYTES(b, count * BYTES_PER_SCANLINE);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_PARALLELOGRAM:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "DRAW_PARALLELOGRAM in XOR mode - Force commit earlier draw calls before "
+                                   "DRAW_PARALLELOGRAM.");
+                    }
+
+                    jfloat x11 = NEXT_FLOAT(b);
+                    jfloat y11 = NEXT_FLOAT(b);
+                    jfloat dx21 = NEXT_FLOAT(b);
+                    jfloat dy21 = NEXT_FLOAT(b);
+                    jfloat dx12 = NEXT_FLOAT(b);
+                    jfloat dy12 = NEXT_FLOAT(b);
+                    jfloat lwr21 = NEXT_FLOAT(b);
+                    jfloat lwr12 = NEXT_FLOAT(b);
+
+                    MTLRenderer_DrawParallelogram(mtlc, dstOps,
+                                                  x11, y11,
+                                                  dx21, dy21,
+                                                  dx12, dy12,
+                                                  lwr21, lwr12);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_AAPARALLELOGRAM:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jfloat x11 = NEXT_FLOAT(b);
+                    jfloat y11 = NEXT_FLOAT(b);
+                    jfloat dx21 = NEXT_FLOAT(b);
+                    jfloat dy21 = NEXT_FLOAT(b);
+                    jfloat dx12 = NEXT_FLOAT(b);
+                    jfloat dy12 = NEXT_FLOAT(b);
+                    jfloat lwr21 = NEXT_FLOAT(b);
+                    jfloat lwr12 = NEXT_FLOAT(b);
+
+                    MTLRenderer_DrawAAParallelogram(mtlc, dstOps,
+                                                    x11, y11,
+                                                    dx21, dy21,
+                                                    dx12, dy12,
+                                                    lwr21, lwr12);
+                    break;
+                }
+
+                // fill ops
+                case sun_java2d_pipe_BufferedOpCodes_FILL_RECT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "FILL_RECT in XOR mode - Force commit earlier draw calls before FILL_RECT.");
+                    }
+
+                    jint x = NEXT_INT(b);
+                    jint y = NEXT_INT(b);
+                    jint w = NEXT_INT(b);
+                    jint h = NEXT_INT(b);
+                    MTLRenderer_FillRect(mtlc, dstOps, x, y, w, h);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_FILL_SPANS:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "FILL_SPANS in XOR mode - Force commit earlier draw calls before FILL_SPANS.");
+                    }
+
+                    jint count = NEXT_INT(b);
+                    MTLRenderer_FillSpans(mtlc, dstOps, count, (jint *)b);
+                    SKIP_BYTES(b, count * BYTES_PER_SPAN);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_FILL_PARALLELOGRAM:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "FILL_PARALLELOGRAM in XOR mode - Force commit earlier draw calls before "
+                                   "FILL_PARALLELOGRAM.");
+                    }
+
+                    jfloat x11 = NEXT_FLOAT(b);
+                    jfloat y11 = NEXT_FLOAT(b);
+                    jfloat dx21 = NEXT_FLOAT(b);
+                    jfloat dy21 = NEXT_FLOAT(b);
+                    jfloat dx12 = NEXT_FLOAT(b);
+                    jfloat dy12 = NEXT_FLOAT(b);
+                    MTLRenderer_FillParallelogram(mtlc, dstOps,
+                                                  x11, y11,
+                                                  dx21, dy21,
+                                                  dx12, dy12);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_FILL_AAPARALLELOGRAM:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jfloat x11 = NEXT_FLOAT(b);
+                    jfloat y11 = NEXT_FLOAT(b);
+                    jfloat dx21 = NEXT_FLOAT(b);
+                    jfloat dy21 = NEXT_FLOAT(b);
+                    jfloat dx12 = NEXT_FLOAT(b);
+                    jfloat dy12 = NEXT_FLOAT(b);
+                    MTLRenderer_FillAAParallelogram(mtlc, dstOps,
+                                                    x11, y11,
+                                                    dx21, dy21,
+                                                    dx12, dy12);
+                    break;
+                }
+
+                // text-related ops
+                case sun_java2d_pipe_BufferedOpCodes_DRAW_GLYPH_LIST:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+
+                    if ([mtlc useXORComposite]) {
+                        commitEncodedCommands();
+                        J2dTraceLn(J2D_TRACE_VERBOSE,
+                                   "DRAW_GLYPH_LIST in XOR mode - Force commit earlier draw calls before "
+                                   "DRAW_GLYPH_LIST.");
+                    }
+
+                    jint numGlyphs        = NEXT_INT(b);
+                    jint packedParams     = NEXT_INT(b);
+                    jfloat glyphListOrigX = NEXT_FLOAT(b);
+                    jfloat glyphListOrigY = NEXT_FLOAT(b);
+                    jboolean usePositions = EXTRACT_BOOLEAN(packedParams,
+                                                            OFFSET_POSITIONS);
+                    jboolean subPixPos    = EXTRACT_BOOLEAN(packedParams,
+                                                            OFFSET_SUBPIXPOS);
+                    jboolean rgbOrder     = EXTRACT_BOOLEAN(packedParams,
+                                                            OFFSET_RGBORDER);
+                    jint lcdContrast      = EXTRACT_BYTE(packedParams,
+                                                         OFFSET_CONTRAST);
+                    unsigned char *images = b;
+                    unsigned char *positions;
+                    jint bytesPerGlyph;
+                    if (usePositions) {
+                        positions = (b + numGlyphs * BYTES_PER_GLYPH_IMAGE);
+                        bytesPerGlyph = BYTES_PER_POSITIONED_GLYPH;
+                    } else {
+                        positions = NULL;
+                        bytesPerGlyph = BYTES_PER_GLYPH_IMAGE;
+                    }
+                    MTLTR_DrawGlyphList(env, mtlc, dstOps,
+                                        numGlyphs, usePositions,
+                                        subPixPos, rgbOrder, lcdContrast,
+                                        glyphListOrigX, glyphListOrigY,
+                                        images, positions);
+                    SKIP_BYTES(b, numGlyphs * bytesPerGlyph);
+                    break;
+                }
+
+                // copy-related ops
+                case sun_java2d_pipe_BufferedOpCodes_COPY_AREA:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint x  = NEXT_INT(b);
+                    jint y  = NEXT_INT(b);
+                    jint w  = NEXT_INT(b);
+                    jint h  = NEXT_INT(b);
+                    jint dx = NEXT_INT(b);
+                    jint dy = NEXT_INT(b);
+                    MTLBlitLoops_CopyArea(env, mtlc, dstOps,
+                                          x, y, w, h, dx, dy);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_BLIT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint packedParams = NEXT_INT(b);
+                    jint sx1          = NEXT_INT(b);
+                    jint sy1          = NEXT_INT(b);
+                    jint sx2          = NEXT_INT(b);
+                    jint sy2          = NEXT_INT(b);
+                    jdouble dx1       = NEXT_DOUBLE(b);
+                    jdouble dy1       = NEXT_DOUBLE(b);
+                    jdouble dx2       = NEXT_DOUBLE(b);
+                    jdouble dy2       = NEXT_DOUBLE(b);
+                    jlong pSrc        = NEXT_LONG(b);
+                    jlong pDst        = NEXT_LONG(b);
+                    jint hint         = EXTRACT_BYTE(packedParams, OFFSET_HINT);
+                    jboolean texture  = EXTRACT_BOOLEAN(packedParams,
+                                                        OFFSET_TEXTURE);
+                    jboolean xform    = EXTRACT_BOOLEAN(packedParams,
+                                                        OFFSET_XFORM);
+                    jboolean isoblit  = EXTRACT_BOOLEAN(packedParams,
+                                                        OFFSET_ISOBLIT);
+                    if (isoblit) {
+                        MTLBlitLoops_IsoBlit(env, mtlc, pSrc, pDst,
+                                             xform, hint, texture,
+                                             sx1, sy1, sx2, sy2,
+                                             dx1, dy1, dx2, dy2);
+                    } else {
+                        jint srctype = EXTRACT_BYTE(packedParams, OFFSET_SRCTYPE);
+                        MTLBlitLoops_Blit(env, mtlc, pSrc, pDst,
+                                          xform, hint, srctype, texture,
+                                          sx1, sy1, sx2, sy2,
+                                          dx1, dy1, dx2, dy2);
+                    }
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SURFACE_TO_SW_BLIT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint sx      = NEXT_INT(b);
+                    jint sy      = NEXT_INT(b);
+                    jint dx      = NEXT_INT(b);
+                    jint dy      = NEXT_INT(b);
+                    jint w       = NEXT_INT(b);
+                    jint h       = NEXT_INT(b);
+                    jint dsttype = NEXT_INT(b);
+                    jlong pSrc   = NEXT_LONG(b);
+                    jlong pDst   = NEXT_LONG(b);
+                    MTLBlitLoops_SurfaceToSwBlit(env, mtlc,
+                                                 pSrc, pDst, dsttype,
+                                                 sx, sy, dx, dy, w, h);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_MASK_FILL:
+                {
+                    jint x        = NEXT_INT(b);
+                    jint y        = NEXT_INT(b);
+                    jint w        = NEXT_INT(b);
+                    jint h        = NEXT_INT(b);
+                    jint maskoff  = NEXT_INT(b);
+                    jint maskscan = NEXT_INT(b);
+                    jint masklen  = NEXT_INT(b);
+                    unsigned char *pMask = (masklen > 0) ? b : NULL;
+                    if (mtlc == nil)
+                        return;
+                    CHECK_PREVIOUS_OP(MTL_OP_MASK_OP);
+                    MTLMaskFill_MaskFill(mtlc, dstOps, x, y, w, h,
+                                         maskoff, maskscan, masklen, pMask);
+                    SKIP_BYTES(b, masklen);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_MASK_BLIT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint dstx     = NEXT_INT(b);
+                    jint dsty     = NEXT_INT(b);
+                    jint width    = NEXT_INT(b);
+                    jint height   = NEXT_INT(b);
+                    jint masklen  = width * height * sizeof(jint);
+                    MTLMaskBlit_MaskBlit(env, mtlc, dstOps,
+                                         dstx, dsty, width, height, b);
+                    SKIP_BYTES(b, masklen);
+                    break;
+                }
+
+                // state-related ops
+                case sun_java2d_pipe_BufferedOpCodes_SET_RECT_CLIP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint x1 = NEXT_INT(b);
+                    jint y1 = NEXT_INT(b);
+                    jint x2 = NEXT_INT(b);
+                    jint y2 = NEXT_INT(b);
+                    [mtlc setClipRectX1:x1 Y1:y1 X2:x2 Y2:y2];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_BEGIN_SHAPE_CLIP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc beginShapeClip:dstOps];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_SHAPE_CLIP_SPANS:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_SHAPE_CLIP_SPANS);
+                    // This results in creation of new render encoder with
+                    // stencil buffer set as render target
+                    jint count = NEXT_INT(b);
+                    MTLRenderer_FillSpans(mtlc, dstOps, count, (jint *)b);
+                    SKIP_BYTES(b, count * BYTES_PER_SPAN);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_END_SHAPE_CLIP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc endShapeClip:dstOps];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_RESET_CLIP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc resetClip];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_ALPHA_COMPOSITE:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint rule         = NEXT_INT(b);
+                    jfloat extraAlpha = NEXT_FLOAT(b);
+                    jint flags        = NEXT_INT(b);
+                    [mtlc setAlphaCompositeRule:rule extraAlpha:extraAlpha flags:flags];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_XOR_COMPOSITE:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jint xorPixel = NEXT_INT(b);
+                    [mtlc setXorComposite:xorPixel];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_RESET_COMPOSITE:
+                {
+                    /* TODO: check whether something needs to be done here if we are moving out of XOR composite
+                    commitEncodedCommands();
+                    MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+                    [cbwrapper onComplete];
+
+                    J2dTraceLn(J2D_TRACE_VERBOSE,
+                     "RESET_COMPOSITE - Force commit earlier draw calls before RESET_COMPOSITE.");*/
+
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc resetComposite];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_TRANSFORM:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jdouble m00 = NEXT_DOUBLE(b);
+                    jdouble m10 = NEXT_DOUBLE(b);
+                    jdouble m01 = NEXT_DOUBLE(b);
+                    jdouble m11 = NEXT_DOUBLE(b);
+                    jdouble m02 = NEXT_DOUBLE(b);
+                    jdouble m12 = NEXT_DOUBLE(b);
+                    [mtlc setTransformM00:m00 M10:m10 M01:m01 M11:m11 M02:m02 M12:m12];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_RESET_TRANSFORM:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc resetTransform];
+                    break;
+                }
+
+                // context-related ops
+                case sun_java2d_pipe_BufferedOpCodes_SET_SURFACES:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pSrc = NEXT_LONG(b);
+                    jlong pDst = NEXT_LONG(b);
+
+                    if (mtlc != NULL) {
+                        [mtlc.encoderManager endEncoder];
+                        MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+                        id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+                        [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+                            [cbwrapper release];
+                        }];
+                        [commandbuf commit];
+                    }
+                    mtlc = [MTLContext setSurfacesEnv:env src:pSrc dst:pDst];
+                    dstOps = (BMTLSDOps *)jlong_to_ptr(pDst);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_SCRATCH_SURFACE:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pConfigInfo = NEXT_LONG(b);
+                    MTLGraphicsConfigInfo *mtlInfo =
+                            (MTLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo);
+
+                    if (mtlInfo == NULL) {
+
+                    } else {
+                        MTLContext *newMtlc = mtlInfo->context;
+                        if (newMtlc == NULL) {
+
+                        } else {
+                            if (mtlc != NULL) {
+                                [mtlc.encoderManager endEncoder];
+                                MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+                                id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+                                [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+                                    [cbwrapper release];
+                                }];
+                                [commandbuf commit];
+                            }
+                            mtlc = newMtlc;
+                            dstOps = NULL;
+                        }
+                    }
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_FLUSH_SURFACE:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pData = NEXT_LONG(b);
+                    BMTLSDOps *mtlsdo = (BMTLSDOps *)jlong_to_ptr(pData);
+                    if (mtlsdo != NULL) {
+                        CONTINUE_IF_NULL(mtlc);
+                        MTLTR_FreeGlyphCaches();
+                        MTLSD_Delete(env, mtlsdo);
+                    }
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DISPOSE_SURFACE:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pData = NEXT_LONG(b);
+                    BMTLSDOps *mtlsdo = (BMTLSDOps *)jlong_to_ptr(pData);
+                    if (mtlsdo != NULL) {
+                        CONTINUE_IF_NULL(mtlc);
+                        MTLSD_Delete(env, mtlsdo);
+                        if (mtlsdo->privOps != NULL) {
+                            free(mtlsdo->privOps);
+                        }
+                    }
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DISPOSE_CONFIG:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pConfigInfo = NEXT_LONG(b);
+                    CONTINUE_IF_NULL(mtlc);
+
+                    if (mtlc != NULL) {
+                        [mtlc.encoderManager endEncoder];
+                    }
+
+                    MTLGC_DestroyMTLGraphicsConfig(pConfigInfo);
+
+                    mtlc = NULL;
+                 //   dstOps = NULL;
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_INVALIDATE_CONTEXT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    // invalidate the references to the current context and
+                    // destination surface that are maintained at the native level
+                    if (mtlc != NULL) {
+                        commitEncodedCommands();
+                        RESET_PREVIOUS_OP();
+                        [mtlc reset];
+                    }
+
+                    MTLTR_FreeGlyphCaches();
+                    if (dstOps != NULL) {
+                        MTLSD_Delete(env, dstOps);
+                    }
+
+                    mtlc = NULL;
+                    dstOps = NULL;
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SYNC:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_SYNC);
+                    break;
+                }
+
+                // special no-op (mainly used for achieving 8-byte alignment)
+                case sun_java2d_pipe_BufferedOpCodes_NOOP:
+                    break;
+
+                // paint-related ops
+                case sun_java2d_pipe_BufferedOpCodes_RESET_PAINT:
+                {
+                  CHECK_PREVIOUS_OP(MTL_OP_RESET_PAINT);
+                  [mtlc resetPaint];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_COLOR:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_SET_COLOR);
+                    jint pixel = NEXT_INT(b);
+                    [mtlc setColorPaint:pixel];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_GRADIENT_PAINT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jboolean useMask= NEXT_BOOLEAN(b);
+                    jboolean cyclic = NEXT_BOOLEAN(b);
+                    jdouble p0      = NEXT_DOUBLE(b);
+                    jdouble p1      = NEXT_DOUBLE(b);
+                    jdouble p3      = NEXT_DOUBLE(b);
+                    jint pixel1     = NEXT_INT(b);
+                    jint pixel2     = NEXT_INT(b);
+                    [mtlc setGradientPaintUseMask:useMask
+                                        cyclic:cyclic
+                                            p0:p0
+                                            p1:p1
+                                            p3:p3
+                                        pixel1:pixel1
+                                        pixel2:pixel2];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_LINEAR_GRADIENT_PAINT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jboolean useMask = NEXT_BOOLEAN(b);
+                    jboolean linear  = NEXT_BOOLEAN(b);
+                    jint cycleMethod = NEXT_INT(b);
+                    jint numStops    = NEXT_INT(b);
+                    jfloat p0        = NEXT_FLOAT(b);
+                    jfloat p1        = NEXT_FLOAT(b);
+                    jfloat p3        = NEXT_FLOAT(b);
+                    void *fractions, *pixels;
+                    fractions = b; SKIP_BYTES(b, numStops * sizeof(jfloat));
+                    pixels    = b; SKIP_BYTES(b, numStops * sizeof(jint));
+                    [mtlc setLinearGradientPaint:useMask
+                                          linear:linear
+                                     cycleMethod:cycleMethod
+                                        numStops:numStops
+                                              p0:p0
+                                              p1:p1
+                                              p3:p3
+                                       fractions:fractions
+                                          pixels:pixels];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_RADIAL_GRADIENT_PAINT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jboolean useMask = NEXT_BOOLEAN(b);
+                    jboolean linear  = NEXT_BOOLEAN(b);
+                    jint numStops    = NEXT_INT(b);
+                    jint cycleMethod = NEXT_INT(b);
+                    jfloat m00       = NEXT_FLOAT(b);
+                    jfloat m01       = NEXT_FLOAT(b);
+                    jfloat m02       = NEXT_FLOAT(b);
+                    jfloat m10       = NEXT_FLOAT(b);
+                    jfloat m11       = NEXT_FLOAT(b);
+                    jfloat m12       = NEXT_FLOAT(b);
+                    jfloat focusX    = NEXT_FLOAT(b);
+                    void *fractions, *pixels;
+                    fractions = b; SKIP_BYTES(b, numStops * sizeof(jfloat));
+                    pixels    = b; SKIP_BYTES(b, numStops * sizeof(jint));
+                    [mtlc setRadialGradientPaint:useMask
+                                          linear:linear
+                                     cycleMethod:cycleMethod
+                                        numStops:numStops
+                                             m00:m00
+                                             m01:m01
+                                             m02:m02
+                                             m10:m10
+                                             m11:m11
+                                             m12:m12
+                                          focusX:focusX
+                                       fractions:fractions
+                                          pixels:pixels];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_SET_TEXTURE_PAINT:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jboolean useMask= NEXT_BOOLEAN(b);
+                    jboolean filter = NEXT_BOOLEAN(b);
+                    jlong pSrc      = NEXT_LONG(b);
+                    jdouble xp0     = NEXT_DOUBLE(b);
+                    jdouble xp1     = NEXT_DOUBLE(b);
+                    jdouble xp3     = NEXT_DOUBLE(b);
+                    jdouble yp0     = NEXT_DOUBLE(b);
+                    jdouble yp1     = NEXT_DOUBLE(b);
+                    jdouble yp3     = NEXT_DOUBLE(b);
+                    [mtlc setTexturePaint:useMask
+                                  pSrcOps:pSrc
+                                   filter:filter
+                                      xp0:xp0
+                                      xp1:xp1
+                                      xp3:xp3
+                                      yp0:yp0
+                                      yp1:yp1
+                                      yp3:yp3];
+                    break;
+                }
+
+                // BufferedImageOp-related ops
+                case sun_java2d_pipe_BufferedOpCodes_ENABLE_CONVOLVE_OP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pSrc        = NEXT_LONG(b);
+                    jboolean edgeZero = NEXT_BOOLEAN(b);
+                    jint kernelWidth  = NEXT_INT(b);
+                    jint kernelHeight = NEXT_INT(b);
+
+                    BMTLSDOps * bmtlsdOps = (BMTLSDOps *)pSrc;
+                    MTLConvolveOp * convolveOp = [[MTLConvolveOp alloc] init:edgeZero
+                            kernelWidth:kernelWidth
+                           kernelHeight:kernelHeight
+                               srcWidth:bmtlsdOps->width
+                              srcHeight:bmtlsdOps->height
+                                 kernel:b
+                                 device:mtlc.device
+                                                  ];
+                    [mtlc setBufImgOp:convolveOp];
+                    SKIP_BYTES(b, kernelWidth * kernelHeight * sizeof(jfloat));
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DISABLE_CONVOLVE_OP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc setBufImgOp:NULL];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_ENABLE_RESCALE_OP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pSrc          = NEXT_LONG(b);
+                    jboolean nonPremult = NEXT_BOOLEAN(b);
+                    jint numFactors     = 4;
+                    unsigned char *scaleFactors = b;
+                    unsigned char *offsets = (b + numFactors * sizeof(jfloat));
+                    MTLRescaleOp * rescaleOp =
+                            [[MTLRescaleOp alloc] init:nonPremult factors:scaleFactors offsets:offsets];
+                    [mtlc setBufImgOp:rescaleOp];
+                    SKIP_BYTES(b, numFactors * sizeof(jfloat) * 2);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DISABLE_RESCALE_OP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc setBufImgOp:NULL];
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_ENABLE_LOOKUP_OP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    jlong pSrc          = NEXT_LONG(b);
+                    jboolean nonPremult = NEXT_BOOLEAN(b);
+                    jboolean shortData  = NEXT_BOOLEAN(b);
+                    jint numBands       = NEXT_INT(b);
+                    jint bandLength     = NEXT_INT(b);
+                    jint offset         = NEXT_INT(b);
+                    jint bytesPerElem = shortData ? sizeof(jshort):sizeof(jbyte);
+                    void *tableValues = b;
+
+                    MTLLookupOp * lookupOp = [[MTLLookupOp alloc] init:nonPremult
+                                                             shortData:shortData
+                                                              numBands:numBands
+                                                            bandLength:bandLength
+                                                                offset:offset
+                                                           tableValues:tableValues
+                                                                device:mtlc.device];
+                    [mtlc setBufImgOp:lookupOp];
+                    SKIP_BYTES(b, numBands * bandLength * bytesPerElem);
+                    break;
+                }
+                case sun_java2d_pipe_BufferedOpCodes_DISABLE_LOOKUP_OP:
+                {
+                    CHECK_PREVIOUS_OP(MTL_OP_OTHER);
+                    [mtlc setBufImgOp:NULL];
+                    break;
+                }
+
+                default:
+                    J2dRlsTraceLn1(J2D_TRACE_ERROR,
+                        "MTLRenderQueue_flushBuffer: invalid opcode=%d", opcode);
+                    return;
+            }
+        }
+
+        if (mtlc != NULL) {
+            if (mtlPreviousOp == MTL_OP_MASK_OP) {
+                MTLVertexCache_DisableMaskCache(mtlc);
+            }
+            [mtlc.encoderManager endEncoder];
+            MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+            id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+            [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+                [cbwrapper release];
+            }];
+            [commandbuf commit];
+            BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+            if (dstOps != NULL) {
+                MTLSDOps *dstMTLOps = (MTLSDOps *)dstOps->privOps;
+                MTLLayer *layer = (MTLLayer*)dstMTLOps->layer;
+                if (layer != NULL) {
+                    [layer startDisplayLink];
+                }
+            }
+        }
+        RESET_PREVIOUS_OP();
+    }
+}
+
+/**
+ * Returns a pointer to the "current" context, as set by the last SET_SURFACES
+ * or SET_SCRATCH_SURFACE operation.
+ */
+MTLContext *
+MTLRenderQueue_GetCurrentContext()
+{
+    return mtlc;
+}
+
+/**
+ * Returns a pointer to the "current" destination surface, as set by the last
+ * SET_SURFACES operation.
+ */
+BMTLSDOps *
+MTLRenderQueue_GetCurrentDestination()
+{
+    return dstOps;
+}
+
+/**
+ * commit earlier encoded commmands
+ * these would be rendered to the back-buffer - which is read in shader while rendering in XOR mode
+ */
+void commitEncodedCommands() {
+    [mtlc.encoderManager endEncoder];
+
+    MTLCommandBufferWrapper *cbwrapper = [mtlc pullCommandBufferWrapper];
+    id <MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+    [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+        [cbwrapper release];
+    }];
+    [commandbuf commit];
+    [commandbuf waitUntilCompleted];
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderer.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderer.h
new file mode 100644
index 00000000000..4ec9956305b
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderer.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLRenderer_h_Included
+#define MTLRenderer_h_Included
+
+#include "sun_java2d_pipe_BufferedRenderPipe.h"
+#include "MTLContext.h"
+#include "MTLGraphicsConfig.h"
+#import "MTLLayer.h"
+
+#define BYTES_PER_POLY_POINT \
+    sun_java2d_pipe_BufferedRenderPipe_BYTES_PER_POLY_POINT
+#define BYTES_PER_SCANLINE \
+    sun_java2d_pipe_BufferedRenderPipe_BYTES_PER_SCANLINE
+#define BYTES_PER_SPAN \
+    sun_java2d_pipe_BufferedRenderPipe_BYTES_PER_SPAN
+
+void MTLRenderer_DrawLine(MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint x1, jint y1, jint x2, jint y2);
+void MTLRenderer_DrawPixel(MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint x, jint y);
+void MTLRenderer_DrawRect(MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint x, jint y, jint w, jint h);
+void MTLRenderer_DrawPoly(MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint nPoints, jint isClosed,
+                          jint transX, jint transY,
+                          jint *xPoints, jint *yPoints);
+void MTLRenderer_DrawScanlines(MTLContext *mtlc, BMTLSDOps * dstOps,
+                               jint count, jint *scanlines);
+void MTLRenderer_DrawParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                                   jfloat fx11, jfloat fy11,
+                                   jfloat dx21, jfloat dy21,
+                                   jfloat dx12, jfloat dy12,
+                                   jfloat lw21, jfloat lw12);
+void MTLRenderer_DrawAAParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                                   jfloat fx11, jfloat fy11,
+                                   jfloat dx21, jfloat dy21,
+                                   jfloat dx12, jfloat dy12,
+                                   jfloat lw21, jfloat lw12);
+void MTLRenderer_FillRect(MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint x, jint y, jint w, jint h);
+void MTLRenderer_FillSpans(MTLContext *mtlc, BMTLSDOps * dstOps,
+                           jint count, jint *spans);
+void MTLRenderer_FillParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                                   jfloat fx11, jfloat fy11,
+                                   jfloat dx21, jfloat dy21,
+                                   jfloat dx12, jfloat dy12);
+void MTLRenderer_FillAAParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                                   jfloat fx11, jfloat fy11,
+                                   jfloat dx21, jfloat dy21,
+                                   jfloat dx12, jfloat dy12);
+
+#endif /* MTLRenderer_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderer.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderer.m
new file mode 100644
index 00000000000..8f2e928283a
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderer.m
@@ -0,0 +1,961 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <jlong.h>
+#include <jni_util.h>
+#include <math.h>
+
+#include "sun_java2d_metal_MTLRenderer.h"
+
+#include "MTLRenderer.h"
+#include "MTLRenderQueue.h"
+#include "MTLSurfaceData.h"
+#include "MTLUtils.h"
+#import "MTLLayer.h"
+
+/**
+ * Note: Some of the methods in this file apply a "magic number"
+ * translation to line segments. It is same as what we have in
+ * OGLrenderer.
+ *
+ * The "magic numbers" you see here have been empirically derived
+ * after testing on a variety of graphics hardware in order to find some
+ * reasonable middle ground between the two specifications.  The general
+ * approach is to apply a fractional translation to vertices so that they
+ * hit pixel centers and therefore touch the same pixels as in our other
+ * pipelines.  Emphasis was placed on finding values so that MTL lines with
+ * a slope of +/- 1 hit all the same pixels as our other (software) loops.
+ * The stepping in other diagonal lines rendered with MTL may deviate
+ * slightly from those rendered with our software loops, but the most
+ * important thing is that these magic numbers ensure that all MTL lines
+ * hit the same endpoints as our software loops.
+ *
+ * If you find it necessary to change any of these magic numbers in the
+ * future, just be sure that you test the changes across a variety of
+ * hardware to ensure consistent rendering everywhere.
+ */
+
+void MTLRenderer_DrawLine(MTLContext *mtlc, BMTLSDOps * dstOps, jint x1, jint y1, jint x2, jint y2) {
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawLine: dest is null");
+        return;
+    }
+
+    J2dTraceLn5(J2D_TRACE_INFO, "MTLRenderer_DrawLine (x1=%d y1=%d x2=%d y2=%d), dst tex=%p", x1, y1, x2, y2, dstOps->pTexture);
+
+    id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
+    if (mtlEncoder == nil)
+        return;
+
+    // DrawLine implementation same as in OGLRenderer.c
+    struct Vertex verts[2];
+    if (y1 == y2) {
+        // horizontal
+        float fx1 = (float)x1;
+        float fx2 = (float)x2;
+        float fy  = ((float)y1) + 0.2f;
+
+        if (x1 > x2) {
+            float t = fx1; fx1 = fx2; fx2 = t;
+        }
+
+        verts[0].position[0] = fx1 + 0.2f;
+        verts[0].position[1] = fy;
+        verts[1].position[0] = fx2 + 1.2f;
+        verts[1].position[1] = fy;
+    } else if (x1 == x2) {
+        // vertical
+        float fx  = ((float)x1) + 0.2f;
+        float fy1 = (float)y1;
+        float fy2 = (float)y2;
+
+        if (y1 > y2) {
+            float t = fy1; fy1 = fy2; fy2 = t;
+        }
+
+        verts[0].position[0] = fx;
+        verts[0].position[1] = fy1 + 0.2f;
+        verts[1].position[0] = fx;
+        verts[1].position[1] = fy2 + 1.2f;
+    } else {
+        // diagonal
+        float fx1 = (float)x1;
+        float fy1 = (float)y1;
+        float fx2 = (float)x2;
+        float fy2 = (float)y2;
+
+        if (x1 < x2) {
+            fx1 += 0.2f;
+            fx2 += 1.0f;
+        } else {
+            fx1 += 0.8f;
+            fx2 -= 0.2f;
+        }
+
+        if (y1 < y2) {
+            fy1 += 0.2f;
+            fy2 += 1.0f;
+        } else {
+            fy1 += 0.8f;
+            fy2 -= 0.2f;
+        }
+        verts[0].position[0] = fx1;
+        verts[0].position[1] = fy1;
+        verts[1].position[0] = fx2;
+        verts[1].position[1] = fy2;
+    }
+
+    [mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
+    [mtlEncoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0 vertexCount:2];
+}
+
+void MTLRenderer_DrawPixel(MTLContext *mtlc, BMTLSDOps * dstOps, jint x, jint y) {
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawPixel: dest is null");
+        return;
+    }
+
+    id<MTLTexture> dest = dstOps->pTexture;
+    J2dTraceLn3(J2D_TRACE_INFO, "MTLRenderer_DrawPixel (x=%d y=%d), dst tex=%p", x, y, dest);
+
+    id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
+    if (mtlEncoder == nil)
+        return;
+
+    // Translate each vertex by a fraction so
+    // that we hit pixel centers.
+    float fx = (float)x + 0.2f;
+    float fy = (float)y + 0.5f;
+    struct Vertex vert = {{fx, fy}};
+    [mtlEncoder setVertexBytes:&vert length:sizeof(vert) atIndex:MeshVertexBuffer];
+    [mtlEncoder drawPrimitives:MTLPrimitiveTypePoint vertexStart:0 vertexCount:1];
+}
+
+void MTLRenderer_DrawRect(MTLContext *mtlc, BMTLSDOps * dstOps, jint x, jint y, jint w, jint h) {
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+        J2dTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawRect: dest is null");
+        return;
+    }
+
+    id<MTLTexture> dest = dstOps->pTexture;
+    J2dTraceLn5(J2D_TRACE_INFO, "MTLRenderer_DrawRect (x=%d y=%d w=%d h=%d), dst tex=%p", x, y, w, h, dest);
+
+    // TODO: use DrawParallelogram(x, y, w, h, lw=1, lh=1)
+    id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
+    if (mtlEncoder == nil)
+        return;
+
+    // Translate each vertex by a fraction so
+    // that we hit pixel centers.
+    const int verticesCount = 5;
+    float fx = (float)x + 0.2f;
+    float fy = (float)y + 0.5f;
+    float fw = (float)w;
+    float fh = (float)h;
+    struct Vertex vertices[5] = {
+            {{fx, fy}},
+            {{fx + fw, fy}},
+            {{fx + fw, fy + fh}},
+            {{fx, fy + fh}},
+            {{fx, fy}},
+    };
+    [mtlEncoder setVertexBytes:vertices length:sizeof(vertices) atIndex:MeshVertexBuffer];
+    [mtlEncoder drawPrimitives:MTLPrimitiveTypeLineStrip vertexStart:0 vertexCount:verticesCount];
+}
+
+const int POLYLINE_BUF_SIZE = 64;
+
+NS_INLINE void fillVertex(struct Vertex * vertex, int x, int y) {
+    vertex->position[0] = x;
+    vertex->position[1] = y;
+}
+
+void MTLRenderer_DrawPoly(MTLContext *mtlc, BMTLSDOps * dstOps,
+                     jint nPoints, jint isClosed,
+                     jint transX, jint transY,
+                     jint *xPoints, jint *yPoints)
+{
+    // Note that BufferedRenderPipe.drawPoly() has already rejected polys
+    // with nPoints<2, so we can be certain here that we have nPoints>=2.
+    if (xPoints == NULL || yPoints == NULL || nPoints < 2) { // just for insurance
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawPoly: points array is empty");
+        return;
+    }
+
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawPoly: dest is null");
+        return;
+    }
+
+    J2dTraceLn4(J2D_TRACE_INFO, "MTLRenderer_DrawPoly: %d points, transX=%d, transY=%d, dst tex=%p", nPoints, transX, transY, dstOps->pTexture);
+
+    __block struct {
+        struct Vertex verts[POLYLINE_BUF_SIZE];
+    } pointsChunk;
+
+    // We intend to submit draw commands in batches of POLYLINE_BUF_SIZE vertices at a time
+    // Subsequent batches need to be connected - so end point in one batch is repeated as first point in subsequent batch
+    // This inflates the total number of points by a factor of number of batches of size POLYLINE_BUF_SIZE
+    nPoints += (nPoints/POLYLINE_BUF_SIZE);
+
+    jint prevX = *(xPoints++);
+    jint prevY = *(yPoints++);
+    const jint firstX = prevX;
+    const jint firstY = prevY;
+    while (nPoints > 0) {
+        const bool isLastChunk = nPoints <= POLYLINE_BUF_SIZE;
+        __block int chunkSize = isLastChunk ? nPoints : POLYLINE_BUF_SIZE;
+
+        fillVertex(pointsChunk.verts, prevX + transX + 0.5f, prevY + transY + 0.5f);
+        J2dTraceLn2(J2D_TRACE_INFO, "MTLRenderer_DrawPoly: Point - (%1.2f, %1.2f)", prevX + transX + 0.5f, prevY + transY + 0.5f);
+
+        for (int i = 1; i < chunkSize; i++) {
+            prevX = *(xPoints++);
+            prevY = *(yPoints++);
+            fillVertex(pointsChunk.verts + i, prevX + transX + 0.5f, prevY + transY + 0.5f);
+            J2dTraceLn2(J2D_TRACE_INFO, "MTLRenderer_DrawPoly: Point - (%1.2f, %1.2f)", prevX + transX + 0.5f,prevY + transY + 0.5f);
+        }
+
+        bool drawCloseSegment = false;
+        if (isClosed && isLastChunk) {
+            if (chunkSize + 2 <= POLYLINE_BUF_SIZE) {
+                fillVertex(pointsChunk.verts + chunkSize, firstX + transX + 0.5f, firstY + transY + 0.5f);
+                J2dTraceLn2(J2D_TRACE_INFO, "MTLRenderer_DrawPoly: Point - (%1.2f, %1.2f)",firstX + transX + 0.5f, firstY + transY + 0.5f);
+
+                ++chunkSize;
+            } else
+                drawCloseSegment = true;
+        }
+
+        nPoints -= chunkSize;
+        id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
+        if (mtlEncoder == nil)
+            return;
+
+        [mtlEncoder setVertexBytes:pointsChunk.verts length:sizeof(pointsChunk.verts) atIndex:MeshVertexBuffer];
+        [mtlEncoder drawPrimitives:MTLPrimitiveTypeLineStrip vertexStart:0 vertexCount:chunkSize];
+
+        if (drawCloseSegment) {
+            struct Vertex vertices[2] = {
+                    {{prevX + transX + 0.5f, prevY + transY + 0.5f}},
+                    {{firstX + transX + 0.5f, firstY + transY + 0.5f}}
+            };
+
+            J2dTraceLn2(J2D_TRACE_INFO, "MTLRenderer_DrawPoly: last segment Point1 - (%1.2f, %1.2f)",prevX + transX + 0.5f, prevY + transY + 0.5f);
+            J2dTraceLn2(J2D_TRACE_INFO, "MTLRenderer_DrawPoly: last segment Point2 - (%1.2f, %1.2f)",firstX + transX + 0.5f, firstY + transY + 0.5f);
+
+            [mtlEncoder setVertexBytes:vertices length:sizeof(vertices) atIndex:MeshVertexBuffer];
+            [mtlEncoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0 vertexCount:2];
+        }
+    }
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLRenderer_drawPoly
+    (JNIEnv *env, jobject mtlr,
+     jintArray xpointsArray, jintArray ypointsArray,
+     jint nPoints, jboolean isClosed,
+     jint transX, jint transY)
+{
+    jint *xPoints, *yPoints;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLRenderer_drawPoly");
+
+    xPoints = (jint *)
+        (*env)->GetPrimitiveArrayCritical(env, xpointsArray, NULL);
+    if (xPoints != NULL) {
+        yPoints = (jint *)
+            (*env)->GetPrimitiveArrayCritical(env, ypointsArray, NULL);
+        if (yPoints != NULL) {
+            MTLContext *mtlc = MTLRenderQueue_GetCurrentContext();
+            BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+
+            MTLRenderer_DrawPoly(mtlc, dstOps,
+                                 nPoints, isClosed,
+                                 transX, transY,
+                                 xPoints, yPoints);
+            if (mtlc != NULL) {
+                RESET_PREVIOUS_OP();
+                [mtlc.encoderManager endEncoder];
+                MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+                id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+                [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+                    [cbwrapper release];
+                }];
+                [commandbuf commit];
+            }
+
+            (*env)->ReleasePrimitiveArrayCritical(env, ypointsArray, yPoints,
+                                                  JNI_ABORT);
+        }
+        (*env)->ReleasePrimitiveArrayCritical(env, xpointsArray, xPoints,
+                                              JNI_ABORT);
+    }
+}
+
+const int SCANLINE_MAX_VERTEX_SIZE = 4096;
+const int VERTEX_STRUCT_SIZE = 8;
+const int NUM_OF_VERTICES_PER_SCANLINE = 2;
+
+void
+MTLRenderer_DrawScanlines(MTLContext *mtlc, BMTLSDOps * dstOps,
+                          jint scanlineCount, jint *scanlines)
+{
+
+    J2dTraceLn2(J2D_TRACE_INFO, "MTLRenderer_DrawScanlines (scanlineCount=%d), dst tex=%p", scanlineCount, dstOps->pTexture);
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+            J2dTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawScanlines: dest is null");
+            return;
+    }
+    RETURN_IF_NULL(scanlines);
+    int vertexSize = NUM_OF_VERTICES_PER_SCANLINE
+        * scanlineCount * VERTEX_STRUCT_SIZE;
+    J2dTraceLn1(J2D_TRACE_INFO, "MTLRenderer_DrawScanlines: Total vertex size : %d", vertexSize);
+    if (vertexSize == 0) return;
+
+    id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
+
+    if (mtlEncoder == nil) return;
+
+    if (vertexSize <= SCANLINE_MAX_VERTEX_SIZE) {
+        struct Vertex verts[NUM_OF_VERTICES_PER_SCANLINE * scanlineCount];
+
+        for (int j = 0, i = 0; j < scanlineCount; j++) {
+            // Translate each vertex by a fraction so
+            // that we hit pixel centers.
+            float x1 = ((float)*(scanlines++)) + 0.2f;
+            float x2 = ((float)*(scanlines++)) + 1.2f;
+            float y  = ((float)*(scanlines++)) + 0.5f;
+            struct Vertex v1 = {{x1, y}};
+            struct Vertex v2 = {{x2, y}};
+            verts[i++] = v1;
+            verts[i++] = v2;
+        }
+
+        [mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
+        [mtlEncoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0
+            vertexCount:NUM_OF_VERTICES_PER_SCANLINE * scanlineCount];
+    } else {
+        int remainingScanlineCount = vertexSize;
+        do {
+            if (remainingScanlineCount > SCANLINE_MAX_VERTEX_SIZE) {
+                struct Vertex verts[SCANLINE_MAX_VERTEX_SIZE/ VERTEX_STRUCT_SIZE];
+
+                for (int j = 0, i = 0; j < (SCANLINE_MAX_VERTEX_SIZE / (VERTEX_STRUCT_SIZE * 2)); j++) {
+                    // Translate each vertex by a fraction so
+                    // that we hit pixel centers.
+                    float x1 = ((float)*(scanlines++)) + 0.2f;
+                    float x2 = ((float)*(scanlines++)) + 1.2f;
+                    float y  = ((float)*(scanlines++)) + 0.5f;
+                    struct Vertex v1 = {{x1, y}};
+                    struct Vertex v2 = {{x2, y}};
+                    verts[i++] = v1;
+                    verts[i++] = v2;
+                }
+
+                [mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
+                [mtlEncoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0
+                    vertexCount:(SCANLINE_MAX_VERTEX_SIZE / VERTEX_STRUCT_SIZE)];
+                remainingScanlineCount -= SCANLINE_MAX_VERTEX_SIZE;
+            } else {
+                struct Vertex verts[remainingScanlineCount / VERTEX_STRUCT_SIZE];
+
+                for (int j = 0, i = 0; j < (remainingScanlineCount / (VERTEX_STRUCT_SIZE * 2)); j++) {
+                    // Translate each vertex by a fraction so
+                    // that we hit pixel centers.
+                    float x1 = ((float)*(scanlines++)) + 0.2f;
+                    float x2 = ((float)*(scanlines++)) + 1.2f;
+                    float y  = ((float)*(scanlines++)) + 0.5f;
+                    struct Vertex v1 = {{x1, y}};
+                    struct Vertex v2 = {{x2, y}};
+                    verts[i++] = v1;
+                    verts[i++] = v2;
+                }
+
+                [mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
+                [mtlEncoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0
+                    vertexCount:(remainingScanlineCount / VERTEX_STRUCT_SIZE)];
+                remainingScanlineCount -= remainingScanlineCount;
+            }
+            J2dTraceLn1(J2D_TRACE_INFO,
+                "MTLRenderer_DrawScanlines: Remaining vertex size %d", remainingScanlineCount);
+        } while (remainingScanlineCount != 0);
+    }
+}
+
+void
+MTLRenderer_FillRect(MTLContext *mtlc, BMTLSDOps * dstOps, jint x, jint y, jint w, jint h)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLRenderer_FillRect");
+
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_FillRect: current dest is null");
+        return;
+    }
+
+    struct Vertex verts[QUAD_VERTEX_COUNT] = {
+        { {x, y}},
+        { {x, y+h}},
+        { {x+w, y}},
+        { {x+w, y+h}
+    }};
+
+
+    id<MTLTexture> dest = dstOps->pTexture;
+    J2dTraceLn5(J2D_TRACE_INFO, "MTLRenderer_FillRect (x=%d y=%d w=%d h=%d), dst tex=%p", x, y, w, h, dest);
+
+    // Encode render command.
+    id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
+    if (mtlEncoder == nil)
+        return;
+
+    [mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
+    [mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount: QUAD_VERTEX_COUNT];
+}
+
+void MTLRenderer_FillSpans(MTLContext *mtlc, BMTLSDOps * dstOps, jint spanCount, jint *spans)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLRenderer_FillSpans");
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_FillSpans: dest is null");
+        return;
+    }
+
+    // MTLRenderCommandEncoder setVertexBytes usage is recommended if the data is of 4KB.
+
+    // We use a buffer that closely matches the 4KB limit size
+    // This buffer is resued multiple times to encode draw calls of a triangle list
+    // NOTE : Due to nature of *spans data - it is not possible to use triangle strip.
+    // We use triangle list to draw spans
+
+    // Destination texture to which render commands are encoded
+    id<MTLTexture> dest = dstOps->pTexture;
+    id<MTLTexture> destAA = nil;
+    BOOL isDestOpaque = dstOps->isOpaque;
+    if (mtlc.clip.stencilMaskGenerationInProgress == JNI_TRUE) {
+        dest = dstOps->pStencilData;
+        isDestOpaque = NO;
+    }
+    id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dest isDstOpaque:isDestOpaque];
+    if (mtlEncoder == nil) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_FillSpans: mtlEncoder is nil");
+        return;
+    }
+
+    // This is the max no of vertices (of struct Vertex - 8 bytes) we can accomodate in 4KB
+    const int TOTAL_VERTICES_IN_BLOCK = 510;
+    struct Vertex vertexList[TOTAL_VERTICES_IN_BLOCK]; // a total of 170 triangles ==> 85 spans
+
+    jfloat shapeX1 = mtlc.clip.shapeX;
+    jfloat shapeY1 = mtlc.clip.shapeY;
+    jfloat shapeX2 = shapeX1 + mtlc.clip.shapeWidth;
+    jfloat shapeY2 = shapeY1 + mtlc.clip.shapeHeight;
+
+    int counter = 0;
+    for (int i = 0; i < spanCount; i++) {
+        jfloat x1 = *(spans++);
+        jfloat y1 = *(spans++);
+        jfloat x2 = *(spans++);
+        jfloat y2 = *(spans++);
+
+        if (mtlc.clip.stencilMaskGenerationInProgress == JNI_TRUE) {
+            if (shapeX1 > x1) shapeX1 = x1;
+            if (shapeY1 > y1) shapeY1 = y1;
+            if (shapeX2 < x2) shapeX2 = x2;
+            if (shapeY2 < y2) shapeY2 = y2;
+        }
+
+        struct Vertex verts[6] = {
+            {{x1, y1}},
+            {{x1, y2}},
+            {{x2, y1}},
+
+            {{x1, y2}},
+            {{x2, y1}},
+            {{x2, y2}
+        }};
+
+        memcpy(&vertexList[counter], &verts, sizeof(verts));
+        counter += 6;
+
+        // If vertexList buffer full
+        if (counter % TOTAL_VERTICES_IN_BLOCK == 0) {
+            [mtlEncoder setVertexBytes:vertexList length:sizeof(vertexList) atIndex:MeshVertexBuffer];
+            [mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:TOTAL_VERTICES_IN_BLOCK];
+            counter = 0;
+        }
+    }
+
+    // Draw triangles using remaining vertices if any
+    if (counter != 0) {
+        [mtlEncoder setVertexBytes:vertexList length:sizeof(vertexList) atIndex:MeshVertexBuffer];
+        [mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:counter];
+    }
+
+    if (mtlc.clip.stencilMaskGenerationInProgress == JNI_TRUE) {
+        if (shapeX1 < 0) shapeX1 = 0;
+        if (shapeY1 < 0) shapeY1 = 0;
+        if (shapeX1 > dest.width) shapeX1 = dest.width;
+        if (shapeY1 > dest.height) shapeY1 = dest.height;
+        if (shapeX2 < 0) shapeX2 = 0;
+        if (shapeY2 < 0) shapeY2 = 0;
+        if (shapeX2 > dest.width) shapeX2 = dest.width;
+        if (shapeY2 > dest.height) shapeY2 = dest.height;
+
+        mtlc.clip.shapeX = (NSUInteger) shapeX1;
+        mtlc.clip.shapeY = (NSUInteger) shapeY1;
+        mtlc.clip.shapeWidth = (NSUInteger) (shapeX2 - shapeX1);
+        mtlc.clip.shapeHeight = (NSUInteger) (shapeY2 - shapeY1);
+    }
+}
+
+void
+MTLRenderer_FillParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                              jfloat fx11, jfloat fy11,
+                              jfloat dx21, jfloat dy21,
+                              jfloat dx12, jfloat dy12)
+{
+
+    if (mtlc == NULL || dstOps == NULL || dstOps->pTexture == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_FillParallelogram: current dest is null");
+        return;
+    }
+
+    id<MTLTexture> dest = dstOps->pTexture;
+    J2dTraceLn7(J2D_TRACE_INFO,
+                "MTLRenderer_FillParallelogram"
+                "(x=%6.2f y=%6.2f "
+                "dx1=%6.2f dy1=%6.2f "
+                "dx2=%6.2f dy2=%6.2f dst tex=%p)",
+                fx11, fy11,
+                dx21, dy21,
+                dx12, dy12, dest);
+
+    struct Vertex verts[QUAD_VERTEX_COUNT] = {
+            { {fx11, fy11}},
+            { {fx11+dx21, fy11+dy21}},
+            { {fx11+dx12, fy11+dy12}},
+            { {fx11 + dx21 + dx12, fy11+ dy21 + dy12}
+        }};
+
+    // Encode render command.
+    id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];;
+
+    if (mtlEncoder == nil) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_FillParallelogram: error creating MTLRenderCommandEncoder.");
+        return;
+    }
+
+    [mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
+    [mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount: QUAD_VERTEX_COUNT];
+}
+
+void
+MTLRenderer_DrawParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                              jfloat fx11, jfloat fy11,
+                              jfloat dx21, jfloat dy21,
+                              jfloat dx12, jfloat dy12,
+                              jfloat lwr21, jfloat lwr12)
+{
+    // dx,dy for line width in the "21" and "12" directions.
+    jfloat ldx21 = dx21 * lwr21;
+    jfloat ldy21 = dy21 * lwr21;
+    jfloat ldx12 = dx12 * lwr12;
+    jfloat ldy12 = dy12 * lwr12;
+
+    // calculate origin of the outer parallelogram
+    jfloat ox11 = fx11 - (ldx21 + ldx12) / 2.0f;
+    jfloat oy11 = fy11 - (ldy21 + ldy12) / 2.0f;
+
+    J2dTraceLn8(J2D_TRACE_INFO,
+                "MTLRenderer_DrawParallelogram"
+                "(x=%6.2f y=%6.2f "
+                "dx1=%6.2f dy1=%6.2f lwr1=%6.2f "
+                "dx2=%6.2f dy2=%6.2f lwr2=%6.2f)",
+                fx11, fy11,
+                dx21, dy21, lwr21,
+                dx12, dy12, lwr12);
+
+
+    // Only need to generate 4 quads if the interior still
+    // has a hole in it (i.e. if the line width ratio was
+    // less than 1.0)
+    if (lwr21 < 1.0f && lwr12 < 1.0f) {
+
+        // Note: "TOP", "BOTTOM", "LEFT" and "RIGHT" here are
+        // relative to whether the dxNN variables are positive
+        // and negative.  The math works fine regardless of
+        // their signs, but for conceptual simplicity the
+        // comments will refer to the sides as if the dxNN
+        // were all positive.  "TOP" and "BOTTOM" segments
+        // are defined by the dxy21 deltas.  "LEFT" and "RIGHT"
+        // segments are defined by the dxy12 deltas.
+
+        // Each segment includes its starting corner and comes
+        // to just short of the following corner.  Thus, each
+        // corner is included just once and the only lengths
+        // needed are the original parallelogram delta lengths
+        // and the "line width deltas".  The sides will cover
+        // the following relative territories:
+        //
+        //     T T T T T R
+        //      L         R
+        //       L         R
+        //        L         R
+        //         L         R
+        //          L B B B B B
+
+        // Every segment is drawn as a filled Parallelogram quad
+        // Each quad is encoded using two triangles
+        // For 4 segments - there are 8 triangles in total
+        // Each triangle has 3 vertices
+        const int TOTAL_VERTICES = 8 * 3;
+        struct Vertex vertexList[TOTAL_VERTICES];
+        int i = 0;
+
+        // TOP segment, to left side of RIGHT edge
+        // "width" of original pgram, "height" of hor. line size
+        fx11 = ox11;
+        fy11 = oy11;
+
+        fillVertex(vertexList + (i++), fx11, fy11);
+        fillVertex(vertexList + (i++), fx11 + dx21, fy11 + dy21);
+        fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
+
+        fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
+        fillVertex(vertexList + (i++), fx11 + ldx12, fy11 + ldy12);
+        fillVertex(vertexList + (i++), fx11, fy11);
+
+        // RIGHT segment, to top of BOTTOM edge
+        // "width" of vert. line size , "height" of original pgram
+        fx11 = ox11 + dx21;
+        fy11 = oy11 + dy21;
+        fillVertex(vertexList + (i++), fx11, fy11);
+        fillVertex(vertexList + (i++), fx11 + ldx21, fy11 + ldy21);
+        fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
+
+        fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
+        fillVertex(vertexList + (i++), fx11 + dx12, fy11 + dy12);
+        fillVertex(vertexList + (i++), fx11, fy11);
+
+        // BOTTOM segment, from right side of LEFT edge
+        // "width" of original pgram, "height" of hor. line size
+        fx11 = ox11 + dx12 + ldx21;
+        fy11 = oy11 + dy12 + ldy21;
+        fillVertex(vertexList + (i++), fx11, fy11);
+        fillVertex(vertexList + (i++), fx11 + dx21, fy11 + dy21);
+        fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
+
+        fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
+        fillVertex(vertexList + (i++), fx11 + ldx12, fy11 + ldy12);
+        fillVertex(vertexList + (i++), fx11, fy11);
+
+        // LEFT segment, from bottom of TOP edge
+        // "width" of vert. line size , "height" of inner pgram
+        fx11 = ox11 + ldx12;
+        fy11 = oy11 + ldy12;
+        fillVertex(vertexList + (i++), fx11, fy11);
+        fillVertex(vertexList + (i++), fx11 + ldx21, fy11 + ldy21);
+        fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
+
+        fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
+        fillVertex(vertexList + (i++), fx11 + dx12, fy11 + dy12);
+        fillVertex(vertexList + (i++), fx11, fy11);
+
+        // Encode render command.
+        id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
+
+        if (mtlEncoder == nil) {
+            J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawParallelogram: error creating MTLRenderCommandEncoder.");
+            return;
+        }
+
+        [mtlEncoder setVertexBytes:vertexList length:sizeof(vertexList) atIndex:MeshVertexBuffer];
+        [mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:TOTAL_VERTICES];
+    } else {
+        // The line width ratios were large enough to consume
+        // the entire hole in the middle of the parallelogram
+        // so we can just issue one large quad for the outer
+        // parallelogram.
+        dx21 += ldx21;
+        dy21 += ldy21;
+        dx12 += ldx12;
+        dy12 += ldy12;
+        MTLRenderer_FillParallelogram(mtlc, dstOps, ox11, oy11, dx21, dy21, dx12, dy12);
+    }
+}
+
+static struct AAVertex aaVertices[6];
+static jint vertexCacheIndex = 0;
+
+#define AA_ADD_VERTEX(OU, OV, IU, IV, DX, DY) \
+    do { \
+        struct AAVertex *v = &aaVertices[vertexCacheIndex++]; \
+        v->otxtpos[0] = OU; \
+        v->otxtpos[1] = OV; \
+        v->itxtpos[0] = IU; \
+        v->itxtpos[1] = IV; \
+        v->position[0]= DX; \
+        v->position[1] = DY; \
+    } while (0)
+
+#define AA_ADD_TRIANGLES(ou11, ov11, iu11, iv11, ou21, ov21, iu21, iv21, ou22, ov22, iu22, iv22, ou12, ov12, iu12, iv12, DX1, DY1, DX2, DY2) \
+    do { \
+        AA_ADD_VERTEX(ou11, ov11, iu11, iv11, DX1, DY1); \
+        AA_ADD_VERTEX(ou21, ov21, iu21, iv21, DX2, DY1); \
+        AA_ADD_VERTEX(ou22, ov22, iu22, iv22, DX2, DY2); \
+        AA_ADD_VERTEX(ou22, ov22, iu22, iv22, DX2, DY2); \
+        AA_ADD_VERTEX(ou12, ov12, iu12, iv12, DX1, DY2); \
+        AA_ADD_VERTEX(ou11, ov11, iu11, iv11, DX1, DY1); \
+    } while (0)
+
+#define ADJUST_PGRAM(V1, DV, V2) \
+    do { \
+        if ((DV) >= 0) { \
+            (V2) += (DV); \
+        } else { \
+            (V1) += (DV); \
+        } \
+    } while (0)
+
+// Invert the following transform:
+// DeltaT(0, 0) == (0,       0)
+// DeltaT(1, 0) == (DX1,     DY1)
+// DeltaT(0, 1) == (DX2,     DY2)
+// DeltaT(1, 1) == (DX1+DX2, DY1+DY2)
+// TM00 = DX1,   TM01 = DX2,   (TM02 = X11)
+// TM10 = DY1,   TM11 = DY2,   (TM12 = Y11)
+// Determinant = TM00*TM11 - TM01*TM10
+//             =  DX1*DY2  -  DX2*DY1
+// Inverse is:
+// IM00 =  TM11/det,   IM01 = -TM01/det
+// IM10 = -TM10/det,   IM11 =  TM00/det
+// IM02 = (TM01 * TM12 - TM11 * TM02) / det,
+// IM12 = (TM10 * TM02 - TM00 * TM12) / det,
+
+#define DECLARE_MATRIX(MAT) \
+    jfloat MAT ## 00, MAT ## 01, MAT ## 02, MAT ## 10, MAT ## 11, MAT ## 12
+
+#define GET_INVERTED_MATRIX(MAT, X11, Y11, DX1, DY1, DX2, DY2, RET_CODE) \
+    do { \
+        jfloat det = DX1*DY2 - DX2*DY1; \
+        if (det == 0) { \
+            RET_CODE; \
+        } \
+        MAT ## 00 = DY2/det; \
+        MAT ## 01 = -DX2/det; \
+        MAT ## 10 = -DY1/det; \
+        MAT ## 11 = DX1/det; \
+        MAT ## 02 = (DX2 * Y11 - DY2 * X11) / det; \
+        MAT ## 12 = (DY1 * X11 - DX1 * Y11) / det; \
+    } while (0)
+
+#define TRANSFORM(MAT, TX, TY, X, Y) \
+    do { \
+        TX = (X) * MAT ## 00 + (Y) * MAT ## 01 + MAT ## 02; \
+        TY = (X) * MAT ## 10 + (Y) * MAT ## 11 + MAT ## 12; \
+    } while (0)
+
+void
+MTLRenderer_FillAAParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                              jfloat fx11, jfloat fy11,
+                              jfloat dx21, jfloat dy21,
+                              jfloat dx12, jfloat dy12)
+{
+    DECLARE_MATRIX(om);
+    // parameters for parallelogram bounding box
+    jfloat bx11, by11, bx22, by22;
+    // parameters for uv texture coordinates of parallelogram corners
+    jfloat ou11, ov11, ou12, ov12, ou21, ov21, ou22, ov22;
+
+    J2dTraceLn6(J2D_TRACE_INFO,
+                "MTLRenderer_FillAAParallelogram "
+                "(x=%6.2f y=%6.2f "
+                "dx1=%6.2f dy1=%6.2f "
+                "dx2=%6.2f dy2=%6.2f)",
+                fx11, fy11,
+                dx21, dy21,
+                dx12, dy12);
+
+    RETURN_IF_NULL(mtlc);
+    RETURN_IF_NULL(dstOps);
+
+    GET_INVERTED_MATRIX(om, fx11, fy11, dx21, dy21, dx12, dy12,
+                        return);
+
+    bx11 = bx22 = fx11;
+    by11 = by22 = fy11;
+    ADJUST_PGRAM(bx11, dx21, bx22);
+    ADJUST_PGRAM(by11, dy21, by22);
+    ADJUST_PGRAM(bx11, dx12, bx22);
+    ADJUST_PGRAM(by11, dy12, by22);
+    bx11 = (jfloat) floor(bx11);
+    by11 = (jfloat) floor(by11);
+    bx22 = (jfloat) ceil(bx22);
+    by22 = (jfloat) ceil(by22);
+
+    TRANSFORM(om, ou11, ov11, bx11, by11);
+    TRANSFORM(om, ou21, ov21, bx22, by11);
+    TRANSFORM(om, ou12, ov12, bx11, by22);
+    TRANSFORM(om, ou22, ov22, bx22, by22);
+
+    id<MTLRenderCommandEncoder> encoder =
+        [mtlc.encoderManager getAAShaderRenderEncoder:dstOps];
+
+    AA_ADD_TRIANGLES(ou11, ov11, 5.f, 5.f, ou21, ov21, 6.f, 5.f, ou22, ov22, 6.f, 6.f, ou12, ov12, 5.f, 5.f, bx11, by11, bx22, by22);
+    [encoder setVertexBytes:aaVertices length:sizeof(aaVertices) atIndex:MeshVertexBuffer];
+    [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
+    vertexCacheIndex = 0;
+}
+
+void
+MTLRenderer_FillAAParallelogramInnerOuter(MTLContext *mtlc, MTLSDOps *dstOps,
+                                          jfloat ox11, jfloat oy11,
+                                          jfloat ox21, jfloat oy21,
+                                          jfloat ox12, jfloat oy12,
+                                          jfloat ix11, jfloat iy11,
+                                          jfloat ix21, jfloat iy21,
+                                          jfloat ix12, jfloat iy12)
+{
+    DECLARE_MATRIX(om);
+    DECLARE_MATRIX(im);
+    // parameters for parallelogram bounding box
+    jfloat bx11, by11, bx22, by22;
+    // parameters for uv texture coordinates of outer parallelogram corners
+    jfloat ou11, ov11, ou12, ov12, ou21, ov21, ou22, ov22;
+    // parameters for uv texture coordinates of inner parallelogram corners
+    jfloat iu11, iv11, iu12, iv12, iu21, iv21, iu22, iv22;
+
+    RETURN_IF_NULL(mtlc);
+    RETURN_IF_NULL(dstOps);
+
+    GET_INVERTED_MATRIX(im, ix11, iy11, ix21, iy21, ix12, iy12,
+                        // inner parallelogram is degenerate
+                        // therefore it encloses no area
+                        // fill outer
+                        MTLRenderer_FillAAParallelogram(mtlc, dstOps,
+                                                        ox11, oy11,
+                                                        ox21, oy21,
+                                                        ox12, oy12);
+                        return);
+    GET_INVERTED_MATRIX(om, ox11, oy11, ox21, oy21, ox12, oy12,
+                        return);
+
+    bx11 = bx22 = ox11;
+    by11 = by22 = oy11;
+    ADJUST_PGRAM(bx11, ox21, bx22);
+    ADJUST_PGRAM(by11, oy21, by22);
+    ADJUST_PGRAM(bx11, ox12, bx22);
+    ADJUST_PGRAM(by11, oy12, by22);
+    bx11 = (jfloat) floor(bx11);
+    by11 = (jfloat) floor(by11);
+    bx22 = (jfloat) ceil(bx22);
+    by22 = (jfloat) ceil(by22);
+
+    TRANSFORM(om, ou11, ov11, bx11, by11);
+    TRANSFORM(om, ou21, ov21, bx22, by11);
+    TRANSFORM(om, ou12, ov12, bx11, by22);
+    TRANSFORM(om, ou22, ov22, bx22, by22);
+
+    TRANSFORM(im, iu11, iv11, bx11, by11);
+    TRANSFORM(im, iu21, iv21, bx22, by11);
+    TRANSFORM(im, iu12, iv12, bx11, by22);
+    TRANSFORM(im, iu22, iv22, bx22, by22);
+
+    id<MTLRenderCommandEncoder> encoder =
+        [mtlc.encoderManager getAAShaderRenderEncoder:dstOps];
+
+    AA_ADD_TRIANGLES(ou11, ov11, iu11, iv11, ou21, ov21, iu21, iv21, ou22, ov22, iu22, iv22, ou12, ov12, iu12, iv12, bx11, by11, bx22, by22);
+    [encoder setVertexBytes:aaVertices length:sizeof(aaVertices) atIndex:MeshVertexBuffer];
+    [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
+    vertexCacheIndex = 0;
+}
+
+void
+MTLRenderer_DrawAAParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
+                              jfloat fx11, jfloat fy11,
+                              jfloat dx21, jfloat dy21,
+                              jfloat dx12, jfloat dy12,
+                              jfloat lwr21, jfloat lwr12)
+{
+    // dx,dy for line width in the "21" and "12" directions.
+    jfloat ldx21, ldy21, ldx12, ldy12;
+    // parameters for "outer" parallelogram
+    jfloat ofx11, ofy11, odx21, ody21, odx12, ody12;
+    // parameters for "inner" parallelogram
+    jfloat ifx11, ify11, idx21, idy21, idx12, idy12;
+
+    J2dTraceLn8(J2D_TRACE_INFO,
+                "MTLRenderer_DrawAAParallelogram "
+                "(x=%6.2f y=%6.2f "
+                "dx1=%6.2f dy1=%6.2f lwr1=%6.2f "
+                "dx2=%6.2f dy2=%6.2f lwr2=%6.2f)",
+                fx11, fy11,
+                dx21, dy21, lwr21,
+                dx12, dy12, lwr12);
+
+    RETURN_IF_NULL(mtlc);
+    RETURN_IF_NULL(dstOps);
+
+    // calculate true dx,dy for line widths from the "line width ratios"
+    ldx21 = dx21 * lwr21;
+    ldy21 = dy21 * lwr21;
+    ldx12 = dx12 * lwr12;
+    ldy12 = dy12 * lwr12;
+
+    // calculate coordinates of the outer parallelogram
+    ofx11 = fx11 - (ldx21 + ldx12) / 2.0f;
+    ofy11 = fy11 - (ldy21 + ldy12) / 2.0f;
+    odx21 = dx21 + ldx21;
+    ody21 = dy21 + ldy21;
+    odx12 = dx12 + ldx12;
+    ody12 = dy12 + ldy12;
+
+    // Only process the inner parallelogram if the line width ratio
+    // did not consume the entire interior of the parallelogram
+    // (i.e. if the width ratio was less than 1.0)
+    if (lwr21 < 1.0f && lwr12 < 1.0f) {
+        // calculate coordinates of the inner parallelogram
+        ifx11 = fx11 + (ldx21 + ldx12) / 2.0f;
+        ify11 = fy11 + (ldy21 + ldy12) / 2.0f;
+        idx21 = dx21 - ldx21;
+        idy21 = dy21 - ldy21;
+        idx12 = dx12 - ldx12;
+        idy12 = dy12 - ldy12;
+
+        MTLRenderer_FillAAParallelogramInnerOuter(mtlc, dstOps,
+                                                  ofx11, ofy11,
+                                                  odx21, ody21,
+                                                  odx12, ody12,
+                                                  ifx11, ify11,
+                                                  idx21, idy21,
+                                                  idx12, idy12);
+    } else {
+        MTLRenderer_FillAAParallelogram(mtlc, dstOps,
+                                        ofx11, ofy11,
+                                        odx21, ody21,
+                                        odx12, ody12);
+    }
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSamplerManager.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSamplerManager.h
new file mode 100644
index 00000000000..506d8f1cd58
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSamplerManager.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLSamplerManager_h_Included
+#define MTLSamplerManager_h_Included
+
+#import <Metal/Metal.h>
+
+#include "RenderOptions.h"
+
+@class MTLContex;
+
+
+@interface MTLSamplerManager : NSObject
+- (id _Nonnull)initWithDevice:(_Nonnull id<MTLDevice>) device;
+- (void)dealloc;
+
+- (void) setSamplerWithEncoder:(_Nonnull id<MTLRenderCommandEncoder>) encoder
+                 interpolation:(int) interpolation
+                        repeat:(bool) repeat;
+@end
+
+#endif // MTLSamplerManager_h_Included
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSamplerManager.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSamplerManager.m
new file mode 100644
index 00000000000..f3f6db66dba
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSamplerManager.m
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "MTLSamplerManager.h"
+#include "MTLContext.h"
+#include "sun_java2d_SunGraphics2D.h"
+#import "common.h"
+
+@implementation MTLSamplerManager {
+    id<MTLSamplerState> _samplerNearestClamp;
+    id<MTLSamplerState> _samplerLinearClamp;
+    id<MTLSamplerState> _samplerNearestRepeat;
+    id<MTLSamplerState> _samplerLinearRepeat;
+}
+
+- (id _Nonnull)initWithDevice:(id<MTLDevice>) device {
+    self = [super init];
+    if (self) {
+        MTLSamplerDescriptor *samplerDescriptor = [[MTLSamplerDescriptor new] autorelease];
+
+        samplerDescriptor.rAddressMode = MTLSamplerAddressModeClampToEdge;
+        samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
+        samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
+
+        samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
+        samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
+        _samplerNearestClamp = [device newSamplerStateWithDescriptor:samplerDescriptor];
+
+        samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear;
+        samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear;
+        _samplerLinearClamp = [device newSamplerStateWithDescriptor:samplerDescriptor];
+
+        samplerDescriptor.rAddressMode = MTLSamplerAddressModeRepeat;
+        samplerDescriptor.sAddressMode = MTLSamplerAddressModeRepeat;
+        samplerDescriptor.tAddressMode = MTLSamplerAddressModeRepeat;
+
+        samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
+        samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
+        _samplerNearestRepeat = [device newSamplerStateWithDescriptor:samplerDescriptor];
+
+        samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear;
+        samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear;
+        _samplerLinearRepeat = [device newSamplerStateWithDescriptor:samplerDescriptor];
+    }
+    return self;
+}
+
+- (void) setSamplerWithEncoder:(id<MTLRenderCommandEncoder>) encoder
+                 interpolation:(int) interpolation
+                        repeat:(bool) repeat {
+    id<MTLSamplerState> sampler;
+    if (repeat) {
+        sampler = interpolation == INTERPOLATION_BILINEAR ? _samplerLinearRepeat : _samplerNearestRepeat;
+    } else {
+        sampler = interpolation == INTERPOLATION_BILINEAR ? _samplerLinearClamp : _samplerNearestClamp;
+    }
+    [encoder setFragmentSamplerState:sampler atIndex:0];
+}
+
+- (void)dealloc {
+    [_samplerNearestClamp release];
+    [_samplerLinearClamp release];
+    [_samplerNearestRepeat release];
+    [_samplerLinearRepeat release];
+    [super dealloc];
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLStencilManager.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLStencilManager.h
new file mode 100644
index 00000000000..a82e593f5a7
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLStencilManager.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLStencilManager_h_Included
+#define MTLStencilManager_h_Included
+
+#import <Metal/Metal.h>
+
+#include "RenderOptions.h"
+
+@class MTLContex;
+
+
+@interface MTLStencilManager : NSObject
+- (id _Nonnull)initWithDevice:(_Nonnull id<MTLDevice>) device;
+- (void)dealloc;
+@property (readonly) _Nonnull id<MTLDepthStencilState> stencilState;
+@end
+
+#endif // MTLSamplerManager_h_Included
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLStencilManager.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLStencilManager.m
new file mode 100644
index 00000000000..3510596d0d5
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLStencilManager.m
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "MTLStencilManager.h"
+//#include "MTLContext.h"
+//#include "sun_java2d_SunGraphics2D.h"
+//#import "common.h"
+
+@implementation MTLStencilManager {
+    id<MTLDepthStencilState> _stencilState;
+}
+
+@synthesize stencilState = _stencilState;
+
+- (id _Nonnull)initWithDevice:(id<MTLDevice>) device {
+    self = [super init];
+    if (self) {
+        MTLDepthStencilDescriptor* stencilDescriptor;
+        stencilDescriptor = [[MTLDepthStencilDescriptor new] autorelease];
+        stencilDescriptor.frontFaceStencil.stencilCompareFunction = MTLCompareFunctionEqual;
+        stencilDescriptor.frontFaceStencil.stencilFailureOperation = MTLStencilOperationKeep;
+
+        // TODO : backFaceStencil can be set to nil if all primitives are drawn as front-facing primitives
+        // currently, fill parallelogram uses back-facing primitive drawing - that needs to be changed.
+        // Once that part is changed, set backFaceStencil to nil
+        //stencilDescriptor.backFaceStencil = nil;
+
+        stencilDescriptor.backFaceStencil.stencilCompareFunction = MTLCompareFunctionEqual;
+        stencilDescriptor.backFaceStencil.stencilFailureOperation = MTLStencilOperationKeep;
+        _stencilState = [device newDepthStencilStateWithDescriptor:stencilDescriptor];
+    }
+    return self;
+}
+
+- (void)dealloc {
+    [_stencilState release];
+    [super dealloc];
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceData.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceData.h
new file mode 100644
index 00000000000..d8ee2076a48
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceData.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLSurfaceData_h_Included
+#define MTLSurfaceData_h_Included
+
+#import "MTLSurfaceDataBase.h"
+#import "MTLGraphicsConfig.h"
+#import "AWTWindow.h"
+#import "MTLLayer.h"
+
+/**
+ * The MTLSDOps structure contains the MTL-specific information for a given
+ * MTLSurfaceData.  It is referenced by the native MTLSDOps structure.
+ */
+typedef struct _MTLSDOps {
+    AWTView               *peerData;
+    MTLLayer              *layer;
+    jint              argb[4]; // background clear color
+    MTLGraphicsConfigInfo *configInfo;
+} MTLSDOps;
+
+// debug-method
+NSString * getSurfaceDescription(const BMTLSDOps * bmtlsdOps);
+
+#endif /* MTLSurfaceData_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceData.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceData.m
new file mode 100644
index 00000000000..c7f8c7d4094
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceData.m
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import <stdlib.h>
+
+#import "sun_java2d_metal_MTLSurfaceData.h"
+
+#import "jni_util.h"
+#import "MTLRenderQueue.h"
+#import "MTLGraphicsConfig.h"
+#import "MTLSurfaceData.h"
+#import "ThreadUtilities.h"
+#include "jlong.h"
+
+/**
+ * The following methods are implemented in the windowing system (i.e. GLX
+ * and WGL) source files.
+ */
+extern jlong MTLSD_GetNativeConfigInfo(BMTLSDOps *bmtlsdo);
+extern jboolean MTLSD_InitMTLWindow(JNIEnv *env, BMTLSDOps *bmtlsdo);
+extern void MTLSD_DestroyMTLSurface(JNIEnv *env, BMTLSDOps *bmtlsdo);
+
+void MTLSD_SetNativeDimensions(JNIEnv *env, BMTLSDOps *bmtlsdo, jint w, jint h);
+
+static jboolean MTLSurfaceData_initTexture(BMTLSDOps *bmtlsdo, jboolean isOpaque, jboolean rtt, jint width, jint height) {
+    @autoreleasepool {
+        if (bmtlsdo == NULL) {
+            J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initTexture: ops are null");
+            return JNI_FALSE;
+        }
+        if (width <= 0 || height <= 0) {
+            J2dRlsTraceLn2(J2D_TRACE_ERROR, "MTLSurfaceData_initTexture: texture dimensions is incorrect, w=%d, h=%d", width, height);
+            return JNI_FALSE;
+        }
+
+        MTLSDOps *mtlsdo = (MTLSDOps *)bmtlsdo->privOps;
+
+        if (mtlsdo == NULL) {
+            J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initTexture: MTLSDOps are null");
+            return JNI_FALSE;
+        }
+        if (mtlsdo->configInfo == NULL || mtlsdo->configInfo->context == NULL) {
+            J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initTexture: MTLSDOps wasn't initialized (context is null)");
+            return JNI_FALSE;
+        }
+
+        MTLContext* ctx = mtlsdo->configInfo->context;
+
+        width = (width <= MaxTextureSize) ? width : 0;
+        height = (height <= MaxTextureSize) ? height : 0;
+
+        J2dTraceLn3(J2D_TRACE_VERBOSE, "  desired texture dimensions: w=%d h=%d max=%d",
+                width, height, MaxTextureSize);
+
+        // if either dimension is 0, we cannot allocate a texture with the
+        // requested dimensions
+        if ((width == 0 || height == 0)) {
+            J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initTexture: texture dimensions too large");
+            return JNI_FALSE;
+        }
+
+        MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: MTLPixelFormatBGRA8Unorm width: width height: height mipmapped: NO];
+        textureDescriptor.usage = MTLTextureUsageUnknown;
+        textureDescriptor.storageMode = MTLStorageModePrivate;
+        bmtlsdo->pTexture = [ctx.device newTextureWithDescriptor: textureDescriptor];
+
+        MTLTextureDescriptor *stencilDataDescriptor =
+            [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Uint width:width height:height mipmapped:NO];
+        stencilDataDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
+        stencilDataDescriptor.storageMode = MTLStorageModePrivate;
+        bmtlsdo->pStencilData = [ctx.device newTextureWithDescriptor:stencilDataDescriptor];
+        bmtlsdo->pAAStencilData = [ctx.device newTextureWithDescriptor:textureDescriptor];
+        bmtlsdo->pStencilDataBuf = [ctx.device newBufferWithLength:width*height options:MTLResourceStorageModePrivate];
+        bmtlsdo->pAAStencilDataBuf = [ctx.device newBufferWithLength:width*height*4 options:MTLResourceStorageModePrivate];
+
+
+        MTLTextureDescriptor *stencilTextureDescriptor =
+            [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatStencil8 width:width height:height mipmapped:NO];
+        stencilTextureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
+        stencilTextureDescriptor.storageMode = MTLStorageModePrivate;
+        bmtlsdo->pStencilTexture = [ctx.device newTextureWithDescriptor:stencilTextureDescriptor];
+
+        bmtlsdo->isOpaque = isOpaque;
+        bmtlsdo->xOffset = 0;
+        bmtlsdo->yOffset = 0;
+        bmtlsdo->width = width;
+        bmtlsdo->height = height;
+        bmtlsdo->textureWidth = width;
+        bmtlsdo->textureHeight = height;
+        bmtlsdo->drawableType = rtt ? MTLSD_RT_TEXTURE : MTLSD_TEXTURE;
+
+        J2dTraceLn6(J2D_TRACE_VERBOSE, "MTLSurfaceData_initTexture: w=%d h=%d bp=%p [tex=%p] opaque=%d rtt=%d", width, height, bmtlsdo, bmtlsdo->pTexture, isOpaque, rtt);
+        return JNI_TRUE;
+    }
+}
+
+/**
+ * Initializes an MTL texture, using the given width and height as
+ * a guide.
+ */
+JNIEXPORT jboolean JNICALL
+Java_sun_java2d_metal_MTLSurfaceData_initTexture(
+    JNIEnv *env, jobject mtlsd,
+    jlong pData, jboolean isOpaque,
+    jint width, jint height
+) {
+    if (!MTLSurfaceData_initTexture((BMTLSDOps *)pData, isOpaque, JNI_FALSE, width, height))
+        return JNI_FALSE;
+    MTLSD_SetNativeDimensions(env, (BMTLSDOps *)pData, width, height);
+    return JNI_TRUE;
+}
+
+/**
+ * Initializes a framebuffer object, using the given width and height as
+ * a guide.  See MTLSD_InitTextureObject() and MTLSD_initRTexture()
+ * for more information.
+ */
+JNIEXPORT jboolean JNICALL
+Java_sun_java2d_metal_MTLSurfaceData_initRTexture
+    (JNIEnv *env, jobject mtlsd,
+     jlong pData, jboolean isOpaque,
+     jint width, jint height)
+{
+    if (!MTLSurfaceData_initTexture((BMTLSDOps *)pData, isOpaque, JNI_TRUE, width, height))
+        return JNI_FALSE;
+    MTLSD_SetNativeDimensions(env, (BMTLSDOps *)pData, width, height);
+    return JNI_TRUE;
+}
+
+JNIEXPORT jlong JNICALL
+Java_sun_java2d_metal_MTLSurfaceData_getMTLTexturePointer(JNIEnv *env, jobject mtlsd, jlong pData) {
+    if (pData == 0)
+        return 0;
+    return ptr_to_jlong(((BMTLSDOps *)pData)->pTexture);
+}
+
+/**
+ * Initializes nativeWidth/Height fields of the surfaceData object with
+ * passed arguments.
+ */
+void
+MTLSD_SetNativeDimensions(JNIEnv *env, BMTLSDOps *mtlsdo,
+                          jint width, jint height)
+{
+    jobject sdObject;
+
+    sdObject = (*env)->NewLocalRef(env, mtlsdo->sdOps.sdObject);
+    if (sdObject == NULL) {
+        return;
+    }
+
+    JNU_SetFieldByName(env, NULL, sdObject, "nativeWidth", "I", width);
+    if (!((*env)->ExceptionOccurred(env))) {
+        JNU_SetFieldByName(env, NULL, sdObject, "nativeHeight", "I", height);
+    }
+
+    (*env)->DeleteLocalRef(env, sdObject);
+}
+
+/**
+ * Deletes native Metal resources associated with this surface.
+ */
+void
+MTLSD_Delete(JNIEnv *env, BMTLSDOps *bmtlsdo)
+{
+    J2dTraceLn3(J2D_TRACE_VERBOSE, "MTLSD_Delete: type=%d %p [tex=%p]", bmtlsdo->drawableType, bmtlsdo, bmtlsdo->pTexture);
+    if (bmtlsdo->drawableType == MTLSD_WINDOW) {
+        MTLSD_DestroyMTLSurface(env, bmtlsdo);
+    } else if (
+            bmtlsdo->drawableType == MTLSD_RT_TEXTURE
+            || bmtlsdo->drawableType == MTLSD_TEXTURE
+            || bmtlsdo->drawableType == MTLSD_FLIP_BACKBUFFER
+    ) {
+        [(NSObject *)bmtlsdo->pTexture release];
+        [(NSObject *)bmtlsdo->pStencilTexture release];
+        [(NSObject *)bmtlsdo->pStencilData release];
+        [(NSObject *)bmtlsdo->pStencilDataBuf release];
+        [(NSObject *)bmtlsdo->pAAStencilData release];
+        [(NSObject *)bmtlsdo->pAAStencilDataBuf release];
+        bmtlsdo->pTexture = NULL;
+        bmtlsdo->drawableType = MTLSD_UNDEFINED;
+    }
+}
+
+/**
+ * This is the implementation of the general DisposeFunc defined in
+ * SurfaceData.h and used by the Disposer mechanism.  It first flushes all
+ * native Metal resources and then frees any memory allocated within the
+ * native MTLSDOps structure.
+ */
+void
+MTLSD_Dispose(JNIEnv *env, SurfaceDataOps *ops)
+{
+    BMTLSDOps *bmtlsdo = (BMTLSDOps *)ops;
+    jobject graphicsConfig = bmtlsdo->graphicsConfig;
+
+    JNU_CallStaticMethodByName(env, NULL, "sun/java2d/metal/MTLSurfaceData",
+                               "dispose",
+                               "(JLsun/java2d/metal/MTLGraphicsConfig;)V",
+                               ptr_to_jlong(ops), graphicsConfig);
+    (*env)->DeleteGlobalRef(env, graphicsConfig);
+    bmtlsdo->graphicsConfig = NULL;
+}
+
+/**
+ * This is the implementation of the general surface LockFunc defined in
+ * SurfaceData.h.
+ */
+jint
+MTLSD_Lock(JNIEnv *env,
+           SurfaceDataOps *ops,
+           SurfaceDataRasInfo *pRasInfo,
+           jint lockflags)
+{
+    JNU_ThrowInternalError(env, "MTLSD_Lock not implemented!");
+    return SD_FAILURE;
+}
+
+/**
+ * This is the implementation of the general GetRasInfoFunc defined in
+ * SurfaceData.h.
+ */
+void
+MTLSD_GetRasInfo(JNIEnv *env,
+                 SurfaceDataOps *ops,
+                 SurfaceDataRasInfo *pRasInfo)
+{
+    JNU_ThrowInternalError(env, "MTLSD_GetRasInfo not implemented!");
+}
+
+/**
+ * This is the implementation of the general surface UnlockFunc defined in
+ * SurfaceData.h.
+ */
+void
+MTLSD_Unlock(JNIEnv *env,
+             SurfaceDataOps *ops,
+             SurfaceDataRasInfo *pRasInfo)
+{
+    JNU_ThrowInternalError(env, "MTLSD_Unlock not implemented!");
+}
+
+/**
+ * This function disposes of any native windowing system resources associated
+ * with this surface.
+ */
+void
+MTLSD_DestroyMTLSurface(JNIEnv *env, BMTLSDOps * bmtlsdo)
+{
+    J2dTraceLn(J2D_TRACE_ERROR, "MTLSD_DestroyMTLSurface not implemented!");
+    JNI_COCOA_ENTER(env);
+    bmtlsdo->drawableType = MTLSD_UNDEFINED;
+    JNI_COCOA_EXIT(env);
+}
+
+/**
+ * This function initializes a native window surface and caches the window
+ * bounds in the given BMTLSDOps.  Returns JNI_TRUE if the operation was
+ * successful; JNI_FALSE otherwise.
+ */
+jboolean
+MTLSD_InitMTLWindow(JNIEnv *env, BMTLSDOps *bmtlsdo)
+{
+    if (bmtlsdo == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSD_InitMTLWindow: ops are null");
+        return JNI_FALSE;
+    }
+
+    MTLSDOps *mtlsdo = (MTLSDOps *)bmtlsdo->privOps;
+    if (mtlsdo == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSD_InitMTLWindow: priv ops are null");
+        return JNI_FALSE;
+    }
+
+    AWTView *v = mtlsdo->peerData;
+    if (v == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSD_InitMTLWindow: view is invalid");
+        return JNI_FALSE;
+    }
+
+    JNI_COCOA_ENTER(env);
+            NSRect surfaceBounds = [v bounds];
+            bmtlsdo->drawableType = MTLSD_WINDOW;
+            bmtlsdo->isOpaque = JNI_TRUE;
+            bmtlsdo->width = surfaceBounds.size.width;
+            bmtlsdo->height = surfaceBounds.size.height;
+    JNI_COCOA_EXIT(env);
+
+    J2dTraceLn2(J2D_TRACE_VERBOSE, "  created window: w=%d h=%d", bmtlsdo->width, bmtlsdo->height);
+    return JNI_TRUE;
+}
+
+#pragma mark -
+#pragma mark "--- MTLSurfaceData methods ---"
+
+extern LockFunc        MTLSD_Lock;
+extern GetRasInfoFunc  MTLSD_GetRasInfo;
+extern UnlockFunc      MTLSD_Unlock;
+
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLSurfaceData_initOps
+    (JNIEnv *env, jobject mtlsd, jobject gc,
+     jlong pConfigInfo, jlong pPeerData, jlong layerPtr,
+     jint xoff, jint yoff, jboolean isOpaque)
+{
+    BMTLSDOps *bmtlsdo = (BMTLSDOps *)SurfaceData_InitOps(env, mtlsd, sizeof(BMTLSDOps));
+    MTLSDOps *mtlsdo = (MTLSDOps *)malloc(sizeof(MTLSDOps));
+
+    J2dTraceLn1(J2D_TRACE_INFO, "MTLSurfaceData_initOps p=%p", bmtlsdo);
+    J2dTraceLn1(J2D_TRACE_INFO, "  pPeerData=%p", jlong_to_ptr(pPeerData));
+    J2dTraceLn1(J2D_TRACE_INFO, "  layerPtr=%p", jlong_to_ptr(layerPtr));
+    J2dTraceLn2(J2D_TRACE_INFO, "  xoff=%d, yoff=%d", (int)xoff, (int)yoff);
+
+    gc = (*env)->NewGlobalRef(env, gc);
+    if (gc == NULL) {
+        JNU_ThrowOutOfMemoryError(env, "Initialization of SurfaceData failed.");
+        return;
+    }
+
+    if (mtlsdo == NULL) {
+        (*env)->DeleteGlobalRef(env, gc);
+        JNU_ThrowOutOfMemoryError(env, "Initialization of SurfaceData failed.");
+        return;
+    }
+
+    // later the graphicsConfig will be used for deallocation of mtlsdo
+    bmtlsdo->privOps = mtlsdo;
+    bmtlsdo->graphicsConfig = gc;
+
+    bmtlsdo->sdOps.Lock               = MTLSD_Lock;
+    bmtlsdo->sdOps.GetRasInfo         = MTLSD_GetRasInfo;
+    bmtlsdo->sdOps.Unlock             = MTLSD_Unlock;
+    bmtlsdo->sdOps.Dispose            = MTLSD_Dispose;
+
+    bmtlsdo->drawableType = MTLSD_UNDEFINED;
+    bmtlsdo->xOffset = xoff;
+    bmtlsdo->yOffset = yoff;
+    bmtlsdo->isOpaque = isOpaque;
+
+    mtlsdo->peerData = (AWTView *)jlong_to_ptr(pPeerData);
+    mtlsdo->layer = (MTLLayer *)jlong_to_ptr(layerPtr);
+    mtlsdo->configInfo = (MTLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo);
+
+    if (mtlsdo->configInfo == NULL) {
+        free(mtlsdo);
+        JNU_ThrowNullPointerException(env, "Config info is null in initOps");
+    }
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLSurfaceData_clearWindow
+(JNIEnv *env, jobject mtlsd)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLSurfaceData_clearWindow");
+
+    BMTLSDOps *bmtlsdo = (MTLSDOps*) SurfaceData_GetOps(env, mtlsd);
+    MTLSDOps *mtlsdo = (MTLSDOps*) bmtlsdo->privOps;
+
+    mtlsdo->peerData = NULL;
+    mtlsdo->layer = NULL;
+}
+
+NSString * getSurfaceDescription(const BMTLSDOps * bmtlsdOps) {
+    if (bmtlsdOps == NULL)
+        return @"NULL";
+    return [NSString stringWithFormat:@"%p [tex=%p, %dx%d, O=%d]", bmtlsdOps, bmtlsdOps->pTexture, bmtlsdOps->width, bmtlsdOps->height, bmtlsdOps->isOpaque];
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceDataBase.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceDataBase.h
new file mode 100644
index 00000000000..fd7f6a9956e
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLSurfaceDataBase.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLSurfaceDataBase_h_Included
+#define MTLSurfaceDataBase_h_Included
+
+#include "java_awt_image_AffineTransformOp.h"
+#include "sun_java2d_metal_MTLSurfaceData.h"
+#include "sun_java2d_pipe_hw_AccelSurface.h"
+
+#include "SurfaceData.h"
+#include "Trace.h"
+
+/**
+ * The MTLSDOps structure describes a native Metal surface and contains all
+ * information pertaining to the native surface.  Some information about
+ * the more important/different fields:
+ *
+ *     void *privOps;
+ * Pointer to native-specific (Metal) SurfaceData info, such as the
+ * native Drawable handle and GraphicsConfig data.
+ *
+ *     jint drawableType;
+ * The surface type; can be any one of the surface type constants defined
+ * below (MTLSD_WINDOW, MTLSD_TEXTURE, etc).
+ *
+ *     jboolean isOpaque;
+ * If true, the surface should be treated as being fully opaque.  If
+ * the underlying surface (e.g. pbuffer) has an alpha channel and isOpaque
+ * is true, then we should take appropriate action (i.e. call glColorMask()
+ * to disable writes into the alpha channel) to ensure that the surface
+ * remains fully opaque.
+ *
+ *     jint x/yOffset
+ * The offset in pixels of the Metal viewport origin from the lower-left
+ * corner of the heavyweight drawable.  For example, a top-level frame on
+ * Windows XP has lower-left insets of (4,4).  The Metal viewport origin
+ * would typically begin at the lower-left corner of the client region (inside
+ * the frame decorations), but AWT/Swing will take the insets into account
+ * when rendering into that window.  So in order to account for this, we
+ * need to adjust the Metal viewport origin by an x/yOffset of (-4,-4).  On
+ * X11, top-level frames typically don't have this insets issue, so their
+ * x/yOffset would be (0,0) (the same applies to pbuffers).
+ *
+ *     jint width/height;
+ * The cached surface bounds.  For offscreen surface types (MTLSD_FBOBJECT,
+ * MTLSD_TEXTURE, etc.) these values must remain constant.  Onscreen window
+ * surfaces (MTLSD_WINDOW, MTLSD_FLIP_BACKBUFFER, etc.) may have their
+ * bounds changed in response to a programmatic or user-initiated event, so
+ * these values represent the last known dimensions.  To determine the true
+ * current bounds of this surface, query the native Drawable through the
+ * privOps field.
+ *
+ *     void* pTexture;
+ * The texture object handle, as generated by MTLTextureDescriptor(). If this
+ * value is null, the texture has not yet been initialized.
+ *
+ *     jint textureWidth/Height;
+ * The actual bounds of the texture object for this surface.
+ * The texture image that we care about has dimensions specified by the width
+ * and height fields in this MTLSDOps structure.
+ */
+typedef struct {
+    SurfaceDataOps               sdOps;
+    void                         *privOps;
+    jobject                      graphicsConfig;
+    jint                         drawableType;
+    jboolean                     isOpaque;
+    jint                         xOffset;
+    jint                         yOffset;
+    jint                         width;
+    jint                         height;
+    void*                        pTexture;
+    void*                        pStencilData;      // stencil data to be rendered to this buffer
+    void*                        pStencilDataBuf;   // MTLBuffer with stencil data
+    void*                        pStencilTexture;   // stencil texture byte buffer stencil mask used in main rendering
+    void*                        pAAStencilData;    // stencil data for AA rendering
+    void*                        pAAStencilDataBuf; // MTLBuffer with AA stencil data
+    jint                         textureWidth;
+    jint                         textureHeight;
+} BMTLSDOps;
+
+#define MTLSD_UNDEFINED       sun_java2d_pipe_hw_AccelSurface_UNDEFINED
+#define MTLSD_WINDOW          sun_java2d_pipe_hw_AccelSurface_WINDOW
+#define MTLSD_TEXTURE         sun_java2d_pipe_hw_AccelSurface_TEXTURE
+#define MTLSD_FLIP_BACKBUFFER sun_java2d_pipe_hw_AccelSurface_FLIP_BACKBUFFER
+#define MTLSD_RT_TEXTURE        sun_java2d_pipe_hw_AccelSurface_RT_TEXTURE
+
+/**
+ * These are shorthand names for the filtering method constants used by
+ * image transform methods.
+ */
+#define MTLSD_XFORM_DEFAULT 0
+#define MTLSD_XFORM_NEAREST_NEIGHBOR \
+    java_awt_image_AffineTransformOp_TYPE_NEAREST_NEIGHBOR
+#define MTLSD_XFORM_BILINEAR \
+    java_awt_image_AffineTransformOp_TYPE_BILINEAR
+
+/**
+ * The SurfaceRasterFlags structure contains information about raster (of some MTLTexture):
+ *
+ *     jboolean isOpaque;
+ * If true, indicates that this pixel format hasn't alpha component (and values of this component can contain garbage).
+ *
+ *     jboolean isPremultiplied;
+ * If true, indicates that this pixel format contains color components that have been pre-multiplied by their
+ * corresponding alpha component.
+*/
+typedef struct {
+    jboolean isOpaque;
+    jboolean isPremultiplied;
+} SurfaceRasterFlags;
+
+/**
+ * Exported methods.
+ */
+jint MTLSD_Lock(JNIEnv *env,
+                SurfaceDataOps *ops, SurfaceDataRasInfo *pRasInfo,
+                jint lockflags);
+void MTLSD_GetRasInfo(JNIEnv *env,
+                      SurfaceDataOps *ops, SurfaceDataRasInfo *pRasInfo);
+void MTLSD_Unlock(JNIEnv *env,
+                  SurfaceDataOps *ops, SurfaceDataRasInfo *pRasInfo);
+void MTLSD_Dispose(JNIEnv *env, SurfaceDataOps *ops);
+void MTLSD_Delete(JNIEnv *env, BMTLSDOps *mtlsdo);
+jint MTLSD_NextPowerOfTwo(jint val, jint max);
+
+#endif /* MTLSurfaceDataBase_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.h
new file mode 100644
index 00000000000..f521be2c170
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLTextRenderer_h_Included
+#define MTLTextRenderer_h_Included
+
+#include <jni.h>
+#include <jlong.h>
+#include "sun_java2d_pipe_BufferedTextPipe.h"
+#include "MTLContext.h"
+#include "MTLSurfaceData.h"
+
+#define BYTES_PER_GLYPH_IMAGE \
+    sun_java2d_pipe_BufferedTextPipe_BYTES_PER_GLYPH_IMAGE
+#define BYTES_PER_GLYPH_POSITION \
+    sun_java2d_pipe_BufferedTextPipe_BYTES_PER_GLYPH_POSITION
+#define BYTES_PER_POSITIONED_GLYPH \
+    (BYTES_PER_GLYPH_IMAGE + BYTES_PER_GLYPH_POSITION)
+
+#define OFFSET_CONTRAST  sun_java2d_pipe_BufferedTextPipe_OFFSET_CONTRAST
+#define OFFSET_RGBORDER  sun_java2d_pipe_BufferedTextPipe_OFFSET_RGBORDER
+#define OFFSET_SUBPIXPOS sun_java2d_pipe_BufferedTextPipe_OFFSET_SUBPIXPOS
+#define OFFSET_POSITIONS sun_java2d_pipe_BufferedTextPipe_OFFSET_POSITIONS
+
+void MTLTR_EnableGlyphVertexCache(MTLContext *mtlc, BMTLSDOps *dstOps);
+void MTLTR_DisableGlyphVertexCache(MTLContext *mtlc);
+id<MTLTexture> MTLTR_GetGlyphCacheTexture();
+void MTLTR_FreeGlyphCaches();
+
+void MTLTR_DrawGlyphList(JNIEnv *env, MTLContext *mtlc, BMTLSDOps *dstOps,
+                         jint totalGlyphs, jboolean usePositions,
+                         jboolean subPixPos, jboolean rgbOrder,
+                         jint lcdContrast,
+                         jfloat glyphListOrigX, jfloat glyphListOrigY,
+                         unsigned char *images, unsigned char *positions);
+
+#endif /* MTLTextRenderer_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.m
new file mode 100644
index 00000000000..a998867af2e
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.m
@@ -0,0 +1,777 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <math.h>
+#include <jlong.h>
+
+#include "sun_java2d_metal_MTLTextRenderer.h"
+
+#include "SurfaceData.h"
+#include "MTLContext.h"
+#include "MTLRenderQueue.h"
+#include "MTLTextRenderer.h"
+#include "MTLVertexCache.h"
+#include "MTLGlyphCache.h"
+#include "MTLBlitLoops.h"
+
+/**
+ * The following constants define the inner and outer bounds of the
+ * accelerated glyph cache.
+ */
+#define MTLTR_CACHE_WIDTH       512
+#define MTLTR_CACHE_HEIGHT      512
+#define MTLTR_CACHE_CELL_WIDTH  32
+#define MTLTR_CACHE_CELL_HEIGHT 32
+
+/**
+ * The current "glyph mode" state.  This variable is used to track the
+ * codepath used to render a particular glyph.  This variable is reset to
+ * MODE_NOT_INITED at the beginning of every call to MTLTR_DrawGlyphList().
+ * As each glyph is rendered, the glyphMode variable is updated to reflect
+ * the current mode, so if the current mode is the same as the mode used
+ * to render the previous glyph, we can avoid doing costly setup operations
+ * each time.
+ */
+typedef enum {
+    MODE_NOT_INITED,
+    MODE_USE_CACHE_GRAY,
+    MODE_USE_CACHE_LCD,
+    MODE_NO_CACHE_GRAY,
+    MODE_NO_CACHE_LCD,
+    MODE_NO_CACHE_COLOR
+} GlyphMode;
+static GlyphMode glyphMode = MODE_NOT_INITED;
+
+/**
+ * There are two separate glyph caches: for AA and for LCD.
+ * Once one of them is initialized as either GRAY or LCD, it
+ * stays in that mode for the duration of the application.  It should
+ * be safe to use this one glyph cache for all screens in a multimon
+ * environment, since the glyph cache texture is shared between all contexts,
+ * and (in theory) Metal drivers should be smart enough to manage that
+ * texture across all screens.
+ */
+
+static MTLGlyphCacheInfo *glyphCacheLCD = NULL;
+static MTLGlyphCacheInfo *glyphCacheAA = NULL;
+
+/**
+ * This value tracks the previous LCD rgbOrder setting, so if the rgbOrder
+ * value has changed since the last time, it indicates that we need to
+ * invalidate the cache, which may already store glyph images in the reverse
+ * order.  Note that in most real world applications this value will not
+ * change over the course of the application, but tests like Font2DTest
+ * allow for changing the ordering at runtime, so we need to handle that case.
+ */
+static jboolean lastRGBOrder = JNI_TRUE;
+
+/**
+ * This constant defines the size of the tile to use in the
+ * MTLTR_DrawLCDGlyphNoCache() method.  See below for more on why we
+ * restrict this value to a particular size.
+ */
+#define MTLTR_NOCACHE_TILE_SIZE 32
+
+static struct TxtVertex txtVertices[6];
+static jint vertexCacheIndex = 0;
+static id<MTLRenderCommandEncoder> lcdCacheEncoder = nil;
+
+#define LCD_ADD_VERTEX(TX, TY, DX, DY, DZ) \
+    do { \
+        struct TxtVertex *v = &txtVertices[vertexCacheIndex++]; \
+        v->txtpos[0] = TX; \
+        v->txtpos[1] = TY; \
+        v->position[0]= DX; \
+        v->position[1] = DY; \
+    } while (0)
+
+#define LCD_ADD_TRIANGLES(TX1, TY1, TX2, TY2, DX1, DY1, DX2, DY2) \
+    do { \
+        LCD_ADD_VERTEX(TX1, TY1, DX1, DY1, 0); \
+        LCD_ADD_VERTEX(TX2, TY1, DX2, DY1, 0); \
+        LCD_ADD_VERTEX(TX2, TY2, DX2, DY2, 0); \
+        LCD_ADD_VERTEX(TX2, TY2, DX2, DY2, 0); \
+        LCD_ADD_VERTEX(TX1, TY2, DX1, DY2, 0); \
+        LCD_ADD_VERTEX(TX1, TY1, DX1, DY1, 0); \
+    } while (0)
+
+/**
+ * Initializes the one glyph cache (texture and data structure).
+ * If lcdCache is JNI_TRUE, the texture will contain RGB data,
+ * otherwise we will simply store the grayscale/monochrome glyph images
+ * as intensity values.
+ */
+static jboolean
+MTLTR_InitGlyphCache(MTLContext *mtlc, jboolean lcdCache)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTR_InitGlyphCache");
+    // TODO : Need to verify RGB order in case of LCD
+    MTLPixelFormat pixelFormat =
+        lcdCache ? MTLPixelFormatBGRA8Unorm : MTLPixelFormatA8Unorm;
+
+    MTLGlyphCacheInfo *gcinfo;
+    // init glyph cache data structure
+    gcinfo = MTLGlyphCache_Init(MTLTR_CACHE_WIDTH,
+                                MTLTR_CACHE_HEIGHT,
+                                MTLTR_CACHE_CELL_WIDTH,
+                                MTLTR_CACHE_CELL_HEIGHT,
+                                MTLVertexCache_FlushGlyphVertexCache);
+
+    if (gcinfo == NULL) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+                      "MTLTR_InitGlyphCache: could not init MTL glyph cache");
+        return JNI_FALSE;
+    }
+
+    MTLTextureDescriptor *textureDescriptor =
+        [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixelFormat
+                                                            width:MTLTR_CACHE_WIDTH
+                                                            height:MTLTR_CACHE_HEIGHT
+                                                            mipmapped:NO];
+
+    gcinfo->texture = [mtlc.device newTextureWithDescriptor:textureDescriptor];
+
+    if (lcdCache) {
+        glyphCacheLCD = gcinfo;
+    } else {
+        glyphCacheAA = gcinfo;
+    }
+
+    return JNI_TRUE;
+}
+
+id<MTLTexture>
+MTLTR_GetGlyphCacheTexture()
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTR_GetGlyphCacheTexture");
+    if (glyphCacheAA != NULL) {
+        return glyphCacheAA->texture;
+    }
+    return NULL;
+}
+
+/**
+ * Adds the given glyph to the glyph cache (texture and data structure)
+ * associated with the given MTLContext.
+ */
+static void
+MTLTR_AddToGlyphCache(GlyphInfo *glyph, MTLContext *mtlc,
+                      jboolean lcdCache)
+{
+    MTLCacheCellInfo *ccinfo;
+    MTLGlyphCacheInfo *gcinfo;
+    jint w = glyph->width;
+    jint h = glyph->height;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTR_AddToGlyphCache");
+    if (!lcdCache) {
+        gcinfo = glyphCacheAA;
+    } else {
+        gcinfo = glyphCacheLCD;
+    }
+
+    if ((gcinfo == NULL) || (glyph->image == NULL)) {
+        return;
+    }
+
+    bool isCacheFull = MTLGlyphCache_IsCacheFull(gcinfo, glyph);
+    if (isCacheFull) {
+        MTLGlyphCache_Free(gcinfo);
+        if (!lcdCache) {
+            MTLTR_InitGlyphCache(mtlc, JNI_FALSE);
+            gcinfo = glyphCacheAA;
+        } else {
+            MTLTR_InitGlyphCache(mtlc, JNI_TRUE);
+            gcinfo = glyphCacheLCD;
+        }
+    }
+    MTLGlyphCache_AddGlyph(gcinfo, glyph);
+    ccinfo = (MTLCacheCellInfo *) glyph->cellInfo;
+
+    if (ccinfo != NULL) {
+        // store glyph image in texture cell
+        MTLRegion region = {
+                {ccinfo->x,  ccinfo->y,   0},
+                {w, h, 1}
+        };
+        if (!lcdCache) {
+            NSUInteger bytesPerRow = 1 * w;
+            [gcinfo->texture replaceRegion:region
+                             mipmapLevel:0
+                             withBytes:glyph->image
+                             bytesPerRow:bytesPerRow];
+        } else {
+            unsigned int imageBytes = w * h * 4;
+            unsigned char imageData[imageBytes];
+            memset(&imageData, 0, sizeof(imageData));
+
+            for (int i = 0; i < h; i++) {
+                for (int j = 0; j < w; j++) {
+                    imageData[(i * w * 4) + j * 4] = glyph->image[(i * w * 3) + j * 3];
+                    imageData[(i * w * 4) + j * 4 + 1] = glyph->image[(i * w * 3) + j * 3 + 1];
+                    imageData[(i * w * 4) + j * 4 + 2] = glyph->image[(i * w * 3) + j * 3 + 2];
+                    imageData[(i * w * 4) + j * 4 + 3] = 0xFF;
+                }
+            }
+
+            NSUInteger bytesPerRow = 4 * w;
+            [gcinfo->texture replaceRegion:region
+                             mipmapLevel:0
+                             withBytes:imageData
+                             bytesPerRow:bytesPerRow];
+        }
+    }
+}
+
+static jboolean
+MTLTR_SetLCDContrast(MTLContext *mtlc,
+                     jint contrast,
+                     id<MTLRenderCommandEncoder> encoder)
+{
+    if (![mtlc.paint isKindOfClass:[MTLColorPaint class]]) {
+        return JNI_FALSE;
+    }
+    MTLColorPaint* cPaint = (MTLColorPaint *) mtlc.paint;
+    // update the current color settings
+    double gamma = ((double)contrast) / 100.0;
+    double invgamma = 1.0/gamma;
+    jfloat radj, gadj, badj;
+    jfloat clr[4];
+    jint col = cPaint.color;
+
+    J2dTraceLn2(J2D_TRACE_INFO, "primary color %x, contrast %d", col, contrast);
+    J2dTraceLn2(J2D_TRACE_INFO, "gamma %f, invgamma %f", gamma, invgamma);
+
+    clr[0] = ((col >> 16) & 0xFF)/255.0f;
+    clr[1] = ((col >> 8) & 0xFF)/255.0f;
+    clr[2] = ((col) & 0xFF)/255.0f;
+
+    // gamma adjust the primary color
+    radj = (float)pow(clr[0], gamma);
+    gadj = (float)pow(clr[1], gamma);
+    badj = (float)pow(clr[2], gamma);
+
+    struct LCDFrameUniforms uf = {
+            {radj, gadj, badj},
+            {gamma, gamma, gamma},
+            {invgamma, invgamma, invgamma}};
+    [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+    return JNI_TRUE;
+}
+
+void
+MTLTR_EnableGlyphVertexCache(MTLContext *mtlc, BMTLSDOps *dstOps)
+{
+J2dTraceLn(J2D_TRACE_INFO, "MTLTR_EnableGlyphVertexCache");
+
+    if (!MTLVertexCache_InitVertexCache()) {
+        return;
+    }
+
+    if (glyphCacheAA == NULL) {
+        if (!MTLTR_InitGlyphCache(mtlc, JNI_FALSE)) {
+            return;
+        }
+    }
+    MTLVertexCache_CreateSamplingEncoder(mtlc, dstOps);
+}
+
+void
+MTLTR_DisableGlyphVertexCache(MTLContext *mtlc)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DisableGlyphVertexCache");
+    MTLVertexCache_FlushGlyphVertexCache();
+    MTLVertexCache_FreeVertexCache();
+}
+
+void MTLTR_FreeGlyphCaches() {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTR_FreeGlyphCaches : freeing glyph caches.");
+
+    if (glyphCacheAA != NULL) {
+        [glyphCacheAA->texture release];
+        MTLGlyphCache_Free(glyphCacheAA);
+        glyphCacheAA = NULL;
+    }
+
+    if (glyphCacheLCD != NULL) {
+        [glyphCacheLCD->texture release];
+        MTLGlyphCache_Free(glyphCacheLCD);
+        glyphCacheLCD = NULL;
+    }
+}
+
+static jboolean
+MTLTR_DrawGrayscaleGlyphViaCache(MTLContext *mtlc,
+                                 GlyphInfo *ginfo, jint x, jint y, BMTLSDOps *dstOps)
+{
+    MTLCacheCellInfo *cell;
+    jfloat x1, y1, x2, y2;
+
+    if (glyphMode != MODE_USE_CACHE_GRAY) {
+        if (glyphMode == MODE_NO_CACHE_GRAY) {
+            MTLVertexCache_DisableMaskCache(mtlc);
+        } else if (glyphMode == MODE_USE_CACHE_LCD) {
+            [mtlc.encoderManager endEncoder];
+            lcdCacheEncoder = nil;
+        }
+        MTLTR_EnableGlyphVertexCache(mtlc, dstOps);
+        glyphMode = MODE_USE_CACHE_GRAY;
+    }
+
+    if (ginfo->cellInfo == NULL) {
+        // attempt to add glyph to accelerated glyph cache
+        MTLTR_AddToGlyphCache(ginfo, mtlc, JNI_FALSE);
+
+        if (ginfo->cellInfo == NULL) {
+            // we'll just no-op in the rare case that the cell is NULL
+            return JNI_TRUE;
+        }
+    }
+
+    cell = (MTLCacheCellInfo *) (ginfo->cellInfo);
+    cell->timesRendered++;
+
+    x1 = (jfloat)x;
+    y1 = (jfloat)y;
+    x2 = x1 + ginfo->width;
+    y2 = y1 + ginfo->height;
+
+    MTLVertexCache_AddGlyphQuad(mtlc,
+                                cell->tx1, cell->ty1,
+                                cell->tx2, cell->ty2,
+                                x1, y1, x2, y2);
+
+    return JNI_TRUE;
+}
+
+static jboolean
+MTLTR_DrawLCDGlyphViaCache(MTLContext *mtlc, BMTLSDOps *dstOps,
+                           GlyphInfo *ginfo, jint x, jint y,
+                           jboolean rgbOrder, jint contrast)
+{
+    CacheCellInfo *cell;
+    jfloat tx1, ty1, tx2, ty2;
+    jint w = ginfo->width;
+    jint h = ginfo->height;
+
+    if (glyphMode != MODE_USE_CACHE_LCD) {
+        if (glyphMode == MODE_NO_CACHE_GRAY) {
+            MTLVertexCache_DisableMaskCache(mtlc);
+        } else if (glyphMode == MODE_USE_CACHE_GRAY) {
+            MTLTR_DisableGlyphVertexCache(mtlc);
+        }
+
+        if (glyphCacheLCD == NULL) {
+            if (!MTLTR_InitGlyphCache(mtlc, JNI_TRUE)) {
+                return JNI_FALSE;
+            }
+        }
+        if (lcdCacheEncoder == nil) {
+            lcdCacheEncoder = [mtlc.encoderManager getLCDEncoder:dstOps->pTexture isSrcOpaque:YES isDstOpaque:YES];
+        }
+        if (rgbOrder != lastRGBOrder) {
+            // need to invalidate the cache in this case; see comments
+            // for lastRGBOrder above
+            MTLGlyphCache_Invalidate(glyphCacheLCD);
+            lastRGBOrder = rgbOrder;
+        }
+
+        glyphMode = MODE_USE_CACHE_LCD;
+    }
+
+    if (ginfo->cellInfo == NULL) {
+        // attempt to add glyph to accelerated glyph cache
+        // TODO : Handle RGB order
+        MTLTR_AddToGlyphCache(ginfo, mtlc, JNI_TRUE);
+
+        if (ginfo->cellInfo == NULL) {
+            // we'll just no-op in the rare case that the cell is NULL
+            return JNI_TRUE;
+        }
+    }
+    cell = (CacheCellInfo *) (ginfo->cellInfo);
+    cell->timesRendered++;
+
+    MTLTR_SetLCDContrast(mtlc, contrast, lcdCacheEncoder);
+    tx1 = cell->tx1;
+    ty1 = cell->ty1;
+    tx2 = cell->tx2;
+    ty2 = cell->ty2;
+
+    J2dTraceLn4(J2D_TRACE_INFO, "tx1 %f, ty1 %f, tx2 %f, ty2 %f", tx1, ty1, tx2, ty2);
+    J2dTraceLn2(J2D_TRACE_INFO, "textureWidth %d textureHeight %d", dstOps->textureWidth, dstOps->textureHeight);
+
+    LCD_ADD_TRIANGLES(tx1, ty1, tx2, ty2, x, y, x+w, y+h);
+
+    [lcdCacheEncoder setVertexBytes:txtVertices length:sizeof(txtVertices) atIndex:MeshVertexBuffer];
+    [lcdCacheEncoder setFragmentTexture:glyphCacheLCD->texture atIndex:0];
+    [lcdCacheEncoder setFragmentTexture:dstOps->pTexture atIndex:1];
+
+    [lcdCacheEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
+
+    vertexCacheIndex = 0;
+
+    return JNI_TRUE;
+}
+
+static jboolean
+MTLTR_DrawGrayscaleGlyphNoCache(MTLContext *mtlc,
+                                GlyphInfo *ginfo, jint x, jint y, BMTLSDOps *dstOps)
+{
+    jint tw, th;
+    jint sx, sy, sw, sh;
+    jint x0;
+    jint w = ginfo->width;
+    jint h = ginfo->height;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGrayscaleGlyphNoCache");
+    if (glyphMode != MODE_NO_CACHE_GRAY) {
+        if (glyphMode == MODE_USE_CACHE_GRAY) {
+            MTLTR_DisableGlyphVertexCache(mtlc);
+        } else if (glyphMode == MODE_USE_CACHE_LCD) {
+            [mtlc.encoderManager endEncoder];
+            lcdCacheEncoder = nil;
+        }
+        MTLVertexCache_EnableMaskCache(mtlc, dstOps);
+        glyphMode = MODE_NO_CACHE_GRAY;
+    }
+
+    x0 = x;
+    tw = MTLVC_MASK_CACHE_TILE_WIDTH;
+    th = MTLVC_MASK_CACHE_TILE_HEIGHT;
+
+    for (sy = 0; sy < h; sy += th, y += th) {
+        x = x0;
+        sh = ((sy + th) > h) ? (h - sy) : th;
+
+        for (sx = 0; sx < w; sx += tw, x += tw) {
+            sw = ((sx + tw) > w) ? (w - sx) : tw;
+
+            J2dTraceLn7(J2D_TRACE_INFO, "sx = %d sy = %d x = %d y = %d sw = %d sh = %d w = %d", sx, sy, x, y, sw, sh, w);
+            MTLVertexCache_AddMaskQuad(mtlc,
+                                       sx, sy, x, y, sw, sh,
+                                       w, ginfo->image,
+                                       dstOps);
+        }
+    }
+
+    return JNI_TRUE;
+}
+
+
+static jboolean
+MTLTR_DrawLCDGlyphNoCache(MTLContext *mtlc, BMTLSDOps *dstOps,
+                          GlyphInfo *ginfo, jint x, jint y,
+                          jint rowBytesOffset,
+                          jboolean rgbOrder, jint contrast)
+{
+    jfloat tx1, ty1, tx2, ty2;
+    jint tw, th;
+    jint w = ginfo->width;
+    jint h = ginfo->height;
+    id<MTLTexture> blitTexture = nil;
+
+    J2dTraceLn2(J2D_TRACE_INFO, "MTLTR_DrawLCDGlyphNoCache x %d, y%d", x, y);
+    J2dTraceLn3(J2D_TRACE_INFO, "MTLTR_DrawLCDGlyphNoCache rowBytesOffset=%d, rgbOrder=%d, contrast=%d", rowBytesOffset, rgbOrder, contrast);
+
+
+    id<MTLRenderCommandEncoder> encoder = nil;
+
+    MTLTextureDescriptor *textureDescriptor =
+        [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
+                                                            width:w
+                                                            height:h
+                                                            mipmapped:NO];
+
+    blitTexture = [mtlc.device newTextureWithDescriptor:textureDescriptor];
+
+    if (glyphMode != MODE_NO_CACHE_LCD) {
+        if (glyphMode == MODE_NO_CACHE_GRAY) {
+            MTLVertexCache_DisableMaskCache(mtlc);
+        } else if (glyphMode == MODE_USE_CACHE_GRAY) {
+            MTLTR_DisableGlyphVertexCache(mtlc);
+        } else if (glyphMode == MODE_USE_CACHE_LCD) {
+            [mtlc.encoderManager endEncoder];
+            lcdCacheEncoder = nil;
+        }
+
+        if (blitTexture == nil) {
+            J2dTraceLn(J2D_TRACE_ERROR, "can't obtain temporary texture object from pool");
+            return JNI_FALSE;
+        }
+
+
+        glyphMode = MODE_NO_CACHE_LCD;
+    }
+    encoder = [mtlc.encoderManager getLCDEncoder:dstOps->pTexture isSrcOpaque:YES isDstOpaque:YES];
+    MTLTR_SetLCDContrast(mtlc, contrast, encoder);
+
+    unsigned int imageBytes = w * h *4;
+    unsigned char imageData[imageBytes];
+    memset(&imageData, 0, sizeof(imageData));
+
+    for (int i = 0; i < h; i++) {
+        for (int j = 0; j < w; j++) {
+            imageData[(i * w * 4) + j * 4] = ginfo->image[((i * w * 3) + j * 3) + rowBytesOffset];
+            imageData[(i * w * 4) + j * 4 + 1] = ginfo->image[((i * w * 3) + j * 3 + 1) + rowBytesOffset];
+            imageData[(i * w * 4) + j * 4 + 2] = ginfo->image[((i * w * 3) + j * 3 + 2) + rowBytesOffset];
+            imageData[(i * w * 4) + j * 4 + 3] = 0xFF;
+        }
+    }
+
+    // copy LCD mask into glyph texture tile
+    MTLRegion region = MTLRegionMake2D(0, 0, w, h);
+
+    NSUInteger bytesPerRow = 4 * ginfo->width;
+    [blitTexture replaceRegion:region
+                 mipmapLevel:0
+                 withBytes:imageData
+                 bytesPerRow:bytesPerRow];
+
+    tx1 = 0.0f;
+    ty1 = 0.0f;
+    tx2 = 1.0f;
+    ty2 = 1.0f;
+
+    J2dTraceLn3(J2D_TRACE_INFO, "xOffset %d yOffset %d, dstOps->height %d", dstOps->xOffset, dstOps->yOffset, dstOps->height);
+    J2dTraceLn4(J2D_TRACE_INFO, "tx1 %f, ty1 %f, tx2 %f, ty2 %f", tx1, ty1, tx2, ty2);
+    J2dTraceLn2(J2D_TRACE_INFO, "textureWidth %d textureHeight %d", dstOps->textureWidth, dstOps->textureHeight);
+
+    LCD_ADD_TRIANGLES(tx1, ty1, tx2, ty2, x, y, x+w, y+h);
+
+    [encoder setVertexBytes:txtVertices length:sizeof(txtVertices) atIndex:MeshVertexBuffer];
+    [encoder setFragmentTexture:blitTexture atIndex:0];
+    [encoder setFragmentTexture:dstOps->pTexture atIndex:1];
+
+    [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
+
+    vertexCacheIndex = 0;
+    [mtlc.encoderManager endEncoder];
+    [blitTexture release];
+
+    MTLCommandBufferWrapper* cbwrapper = [mtlc pullCommandBufferWrapper];
+
+    id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+    [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+        [cbwrapper release];
+    }];
+
+    [commandbuf commit];
+    [commandbuf waitUntilCompleted];
+
+    return JNI_TRUE;
+}
+
+// see DrawGlyphList.c for more on this macro...
+#define FLOOR_ASSIGN(l, r) \
+    if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r))
+
+void
+MTLTR_DrawGlyphList(JNIEnv *env, MTLContext *mtlc, BMTLSDOps *dstOps,
+                    jint totalGlyphs, jboolean usePositions,
+                    jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
+                    jfloat glyphListOrigX, jfloat glyphListOrigY,
+                    unsigned char *images, unsigned char *positions)
+{
+    int glyphCounter;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList");
+
+    RETURN_IF_NULL(mtlc);
+    RETURN_IF_NULL(dstOps);
+    RETURN_IF_NULL(images);
+    if (usePositions) {
+        RETURN_IF_NULL(positions);
+    }
+
+    glyphMode = MODE_NOT_INITED;
+    J2dTraceLn1(J2D_TRACE_INFO, "totalGlyphs = %d", totalGlyphs);
+    jboolean flushBeforeLCD = JNI_FALSE;
+
+    for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
+        J2dTraceLn(J2D_TRACE_INFO, "Entered for loop for glyph list");
+        jint x, y;
+        jfloat glyphx, glyphy;
+        jboolean grayscale, ok;
+        GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images));
+
+        if (ginfo == NULL) {
+            // this shouldn't happen, but if it does we'll just break out...
+            J2dRlsTraceLn(J2D_TRACE_ERROR,
+                          "MTLTR_DrawGlyphList: glyph info is null");
+            break;
+        }
+
+        grayscale = (ginfo->rowBytes == ginfo->width);
+
+        if (usePositions) {
+            jfloat posx = NEXT_FLOAT(positions);
+            jfloat posy = NEXT_FLOAT(positions);
+            glyphx = glyphListOrigX + posx + ginfo->topLeftX;
+            glyphy = glyphListOrigY + posy + ginfo->topLeftY;
+            FLOOR_ASSIGN(x, glyphx);
+            FLOOR_ASSIGN(y, glyphy);
+        } else {
+            glyphx = glyphListOrigX + ginfo->topLeftX;
+            glyphy = glyphListOrigY + ginfo->topLeftY;
+            FLOOR_ASSIGN(x, glyphx);
+            FLOOR_ASSIGN(y, glyphy);
+            glyphListOrigX += ginfo->advanceX;
+            glyphListOrigY += ginfo->advanceY;
+        }
+
+        if (ginfo->image == NULL) {
+            J2dTraceLn(J2D_TRACE_INFO, "Glyph image is null");
+            continue;
+        }
+
+        J2dTraceLn2(J2D_TRACE_INFO, "Glyph width = %d height = %d", ginfo->width, ginfo->height);
+        J2dTraceLn1(J2D_TRACE_INFO, "rowBytes = %d", ginfo->rowBytes);
+        if (grayscale) {
+            // grayscale or monochrome glyph data
+            if (ginfo->width <= MTLTR_CACHE_CELL_WIDTH &&
+                ginfo->height <= MTLTR_CACHE_CELL_HEIGHT)
+            {
+                J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList Grayscale cache");
+                ok = MTLTR_DrawGrayscaleGlyphViaCache(mtlc, ginfo, x, y, dstOps);
+            } else {
+                J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList Grayscale no cache");
+                ok = MTLTR_DrawGrayscaleGlyphNoCache(mtlc, ginfo, x, y, dstOps);
+            }
+        } else {
+            if (!flushBeforeLCD) {
+                [mtlc.encoderManager endEncoder];
+                MTLCommandBufferWrapper* cbwrapper = [mtlc pullCommandBufferWrapper];
+
+                id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+                [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+                    [cbwrapper release];
+                }];
+
+                [commandbuf commit];
+                flushBeforeLCD = JNI_TRUE;
+            }
+
+            // LCD-optimized glyph data
+            jint rowBytesOffset = 0;
+
+            if (subPixPos) {
+                jint frac = (jint)((glyphx - x) * 3);
+                if (frac != 0) {
+                    rowBytesOffset = 3 - frac;
+                    x += 1;
+                }
+            }
+
+            if (rowBytesOffset == 0 &&
+                ginfo->width <= MTLTR_CACHE_CELL_WIDTH &&
+                ginfo->height <= MTLTR_CACHE_CELL_HEIGHT)
+            {
+                J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList LCD cache");
+                ok = MTLTR_DrawLCDGlyphViaCache(mtlc, dstOps,
+                                                ginfo, x, y,
+                                                rgbOrder, lcdContrast);
+            } else {
+                J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList LCD no cache");
+                ok = MTLTR_DrawLCDGlyphNoCache(mtlc, dstOps,
+                                               ginfo, x, y,
+                                               rowBytesOffset,
+                                               rgbOrder, lcdContrast);
+            }
+        }
+
+        if (!ok) {
+            break;
+        }
+    }
+    /*
+     * Only in case of grayscale text drawing we need to flush
+     * cache. Still in case of LCD we are not using any intermediate
+     * cache.
+     */
+    if (glyphMode == MODE_NO_CACHE_GRAY) {
+        MTLVertexCache_DisableMaskCache(mtlc);
+    } else if (glyphMode == MODE_USE_CACHE_GRAY) {
+        MTLTR_DisableGlyphVertexCache(mtlc);
+    } else if (glyphMode == MODE_USE_CACHE_LCD) {
+        [mtlc.encoderManager endEncoder];
+        lcdCacheEncoder = nil;
+    }
+}
+
+JNIEXPORT void JNICALL
+Java_sun_java2d_metal_MTLTextRenderer_drawGlyphList
+    (JNIEnv *env, jobject self,
+     jint numGlyphs, jboolean usePositions,
+     jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
+     jfloat glyphListOrigX, jfloat glyphListOrigY,
+     jlongArray imgArray, jfloatArray posArray)
+{
+    unsigned char *images;
+
+    J2dTraceLn(J2D_TRACE_INFO, "MTLTextRenderer_drawGlyphList");
+
+    images = (unsigned char *)
+        (*env)->GetPrimitiveArrayCritical(env, imgArray, NULL);
+    if (images != NULL) {
+        MTLContext *mtlc = MTLRenderQueue_GetCurrentContext();
+        BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
+
+        if (usePositions) {
+            unsigned char *positions = (unsigned char *)
+                (*env)->GetPrimitiveArrayCritical(env, posArray, NULL);
+            if (positions != NULL) {
+                MTLTR_DrawGlyphList(env, mtlc, dstOps,
+                                    numGlyphs, usePositions,
+                                    subPixPos, rgbOrder, lcdContrast,
+                                    glyphListOrigX, glyphListOrigY,
+                                    images, positions);
+                (*env)->ReleasePrimitiveArrayCritical(env, posArray,
+                                                      positions, JNI_ABORT);
+            }
+        } else {
+            MTLTR_DrawGlyphList(env, mtlc, dstOps,
+                                numGlyphs, usePositions,
+                                subPixPos, rgbOrder, lcdContrast,
+                                glyphListOrigX, glyphListOrigY,
+                                images, NULL);
+        }
+        if (mtlc != NULL) {
+            RESET_PREVIOUS_OP();
+            [mtlc.encoderManager endEncoder];
+            MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
+            id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
+            [commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
+                [cbwrapper release];
+            }];
+            [commandbuf commit];
+        }
+
+        (*env)->ReleasePrimitiveArrayCritical(env, imgArray,
+                                              images, JNI_ABORT);
+    }
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTexturePool.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTexturePool.h
new file mode 100644
index 00000000000..a14cf618e64
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTexturePool.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLTexturePool_h_Included
+#define MTLTexturePool_h_Included
+#include <time.h>
+#import "MTLUtils.h"
+
+@class MTLPoolCell;
+
+@interface MTLTexturePoolItem : NSObject
+@property (readwrite, retain) id<MTLTexture> texture;
+@property (readwrite) bool isBusy;
+@property (readwrite) time_t lastUsed;
+@property (readwrite) bool isMultiSample;
+@property (readwrite, assign) MTLTexturePoolItem* prev;
+@property (readwrite, retain) MTLTexturePoolItem* next;
+@property (readwrite, assign) MTLPoolCell* cell;
+
+- (id) initWithTexture:(id<MTLTexture>)tex cell:(MTLPoolCell*)cell;
+@end
+
+@interface MTLPooledTextureHandle : NSObject
+@property (readonly, assign) id<MTLTexture> texture;
+@property (readonly) MTLRegion rect;
+- (void) releaseTexture;
+@end
+
+// NOTE: owns all MTLTexture objects
+@interface MTLTexturePool : NSObject
+@property (readwrite, retain) id<MTLDevice> device;
+
+- (id) initWithDevice:(id<MTLDevice>)device;
+- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format;
+- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format
+                          isMultiSample:(bool)isMultiSample;
+@end
+
+@interface MTLPoolCell : NSObject
+@property (readwrite, retain) MTLTexturePoolItem* available;
+@property (readwrite, assign) MTLTexturePoolItem* availableTail;
+@property (readwrite, retain) MTLTexturePoolItem* occupied;
+- (MTLTexturePoolItem *)createItem:(id<MTLDevice>)dev
+                             width:(int)width
+                            height:(int)height
+                            format:(MTLPixelFormat)format
+                     isMultiSample:(bool)isMultiSample;
+- (NSUInteger)cleanIfBefore:(time_t)lastUsedTimeToRemove;
+- (void)releaseItem:(MTLTexturePoolItem *)item;
+@end
+
+#endif /* MTLTexturePool_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTexurePool.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTexurePool.m
new file mode 100644
index 00000000000..5071afe5657
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTexurePool.m
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import "MTLTexturePool.h"
+#import "Trace.h"
+
+#define SCREEN_MEMORY_SIZE_4K (4096*2160*4) //~33,7 mb
+#define MAX_POOL_MEMORY SCREEN_MEMORY_SIZE_4K/2
+#define MAX_POOL_ITEM_LIFETIME_SEC 30
+
+#define CELL_WIDTH_BITS 5 // ~ 32 pixel
+#define CELL_HEIGHT_BITS 5 // ~ 32 pixel
+
+@implementation MTLTexturePoolItem
+
+@synthesize texture, isBusy, lastUsed, isMultiSample, next, cell;
+
+- (id) initWithTexture:(id<MTLTexture>)tex cell:(MTLPoolCell*)c{
+    self = [super init];
+    if (self == nil) return self;
+    self.texture = tex;
+    isBusy = NO;
+    self.next = nil;
+    self.prev = nil;
+    self.cell = c;
+    return self;
+}
+
+- (void) dealloc {
+    [texture release];
+    [super dealloc];
+}
+
+@end
+
+@implementation MTLPooledTextureHandle
+{
+    MTLRegion _rect;
+    id<MTLTexture> _texture;
+    MTLTexturePoolItem * _poolItem;
+}
+@synthesize texture = _texture, rect = _rect;
+
+- (id) initWithPoolItem:(id<MTLTexture>)texture rect:(MTLRegion)rectangle poolItem:(MTLTexturePoolItem *)poolItem {
+    self = [super init];
+    if (self == nil) return self;
+
+    _rect = rectangle;
+    _texture = texture;
+    _poolItem = poolItem;
+    return self;
+}
+
+- (void) releaseTexture {
+    [_poolItem.cell releaseItem:_poolItem];
+}
+
+@end
+
+@implementation MTLPoolCell {
+    NSLock* _lock;
+}
+@synthesize available, availableTail, occupied;
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        self.available = nil;
+        self.availableTail = nil;
+        self.occupied = nil;
+        _lock = [[NSLock alloc] init];
+    }
+    return self;
+}
+
+- (void)occupyItem:(MTLTexturePoolItem *)item {
+    if (item.isBusy) return;
+    [item retain];
+    if (item.prev == nil) {
+        self.available = item.next;
+        if (item.next) {
+            item.next.prev = nil;
+        } else {
+            self.availableTail = item.prev;
+        }
+    } else {
+        item.prev.next = item.next;
+        if (item.next) {
+            item.next.prev = item.prev;
+        } else {
+            self.availableTail = item.prev;
+        }
+        item.prev = nil;
+    }
+    if (occupied) occupied.prev = item;
+    item.next = occupied;
+    self.occupied = item;
+    [item release];
+    item.isBusy = YES;
+}
+
+- (void)releaseItem:(MTLTexturePoolItem *)item {
+    [_lock lock];
+    @try {
+        if (!item.isBusy) return;
+        [item retain];
+        if (item.prev == nil) {
+            self.occupied = item.next;
+            if (item.next) item.next.prev = nil;
+        } else {
+            item.prev.next = item.next;
+            if (item.next) item.next.prev = item.prev;
+            item.prev = nil;
+        }
+        if (self.available) {
+            self.available.prev = item;
+        } else {
+            self.availableTail = item;
+        }
+        item.next = self.available;
+        self.available = item;
+        item.isBusy = NO;
+        [item release];
+    } @finally {
+        [_lock unlock];
+    }
+}
+
+- (void)addOccupiedItem:(MTLTexturePoolItem *)item {
+    if (self.occupied) self.occupied.prev = item;
+    item.next = self.occupied;
+    item.isBusy = YES;
+    self.occupied = item;
+}
+
+- (void)removeAvailableItem:(MTLTexturePoolItem*)item {
+    [item retain];
+    if (item.prev == nil) {
+        self.available = item.next;
+        if (item.next) {
+            item.next.prev = nil;
+            item.next = nil;
+        } else {
+            self.availableTail = item.prev;
+        }
+    } else {
+        item.prev.next = item.next;
+        if (item.next) {
+            item.next.prev = item.prev;
+            item.next = nil;
+        } else {
+            self.availableTail = item.prev;
+        }
+    }
+    [item release];
+}
+
+- (void)removeAllItems {
+    MTLTexturePoolItem *cur = self.available;
+    while (cur != nil) {
+        cur = cur.next;
+        self.available = cur;
+    }
+    cur = self.occupied;
+    while (cur != nil) {
+        cur = cur.next;
+        self.occupied = cur;
+    }
+    self.availableTail = nil;
+}
+
+- (MTLTexturePoolItem *)createItem:(id<MTLDevice>)dev
+                             width:(int)width
+                            height:(int)height
+                            format:(MTLPixelFormat)format
+                     isMultiSample:(bool)isMultiSample
+{
+    MTLTextureDescriptor *textureDescriptor =
+            [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
+                                                               width:(NSUInteger) width
+                                                              height:(NSUInteger) height
+                                                           mipmapped:NO];
+    if (isMultiSample) {
+        textureDescriptor.textureType = MTLTextureType2DMultisample;
+        textureDescriptor.sampleCount = MTLAASampleCount;
+        textureDescriptor.storageMode = MTLStorageModePrivate;
+    }
+
+    id <MTLTexture> tex = (id <MTLTexture>) [[dev newTextureWithDescriptor:textureDescriptor] autorelease];
+    MTLTexturePoolItem* item = [[[MTLTexturePoolItem alloc] initWithTexture:tex cell:self] autorelease];
+    item.isMultiSample = isMultiSample;
+    [_lock lock];
+    @try {
+        [self addOccupiedItem:item];
+    } @finally {
+        [_lock unlock];
+    }
+    return item;
+}
+
+
+- (NSUInteger)cleanIfBefore:(time_t)lastUsedTimeToRemove {
+    NSUInteger deallocMem = 0;
+    [_lock lock];
+    MTLTexturePoolItem *cur = availableTail;
+    @try {
+        while (cur != nil) {
+            MTLTexturePoolItem *prev = cur.prev;
+            if (lastUsedTimeToRemove <= 0 ||
+                cur.lastUsed < lastUsedTimeToRemove) {
+#ifdef DEBUG
+                J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE,
+                             "MTLTexturePool: remove pool item: tex=%p, w=%d h=%d, elapsed=%d",
+                             cur.texture, cur.texture.width, cur.texture.height,
+                             time(NULL) - cur.lastUsed);
+#endif //DEBUG
+                deallocMem += cur.texture.width * cur.texture.height * 4;
+                [self removeAvailableItem:cur];
+            } else {
+                if (lastUsedTimeToRemove > 0) break;
+            }
+            cur = prev;
+        }
+    } @finally {
+        [_lock unlock];
+    }
+    return deallocMem;
+}
+
+- (MTLTexturePoolItem *)occupyItem:(int)width height:(int)height format:(MTLPixelFormat)format
+                     isMultiSample:(bool)isMultiSample {
+    int minDeltaArea = -1;
+    const int requestedPixels = width*height;
+    MTLTexturePoolItem *minDeltaTpi = nil;
+    [_lock lock];
+    @try {
+        for (MTLTexturePoolItem *cur = available; cur != nil; cur = cur.next) {
+            if (cur.texture.pixelFormat != format
+                || cur.isMultiSample != isMultiSample) { // TODO: use swizzle when formats are not equal
+                continue;
+            }
+            if (cur.texture.width < width || cur.texture.height < height) {
+                continue;
+            }
+            const int deltaArea = (const int) (cur.texture.width * cur.texture.height - requestedPixels);
+            if (minDeltaArea < 0 || deltaArea < minDeltaArea) {
+                minDeltaArea = deltaArea;
+                minDeltaTpi = cur;
+                if (deltaArea == 0) {
+                    // found exact match in current cell
+                    break;
+                }
+            }
+        }
+
+        if (minDeltaTpi) {
+            [self occupyItem:minDeltaTpi];
+        }
+    } @finally {
+        [_lock unlock];
+    }
+    return minDeltaTpi;
+}
+
+- (void) dealloc {
+    [_lock lock];
+    @try {
+        [self removeAllItems];
+    } @finally {
+        [_lock unlock];
+    }
+    [_lock release];
+    [super dealloc];
+}
+
+@end
+
+@implementation MTLTexturePool {
+    int _memoryTotalAllocated;
+
+    void ** _cells;
+    int _poolCellWidth;
+    int _poolCellHeight;
+}
+
+@synthesize device;
+
+- (id) initWithDevice:(id<MTLDevice>)dev {
+    self = [super init];
+    if (self == nil) return self;
+
+    _memoryTotalAllocated = 0;
+    _poolCellWidth = 10;
+    _poolCellHeight = 10;
+    const int cellsCount = _poolCellWidth * _poolCellHeight;
+    _cells = (void **)malloc(cellsCount * sizeof(void*));
+    memset(_cells, 0, cellsCount * sizeof(void*));
+    self.device = dev;
+    return self;
+}
+
+- (void) dealloc {
+    for (int c = 0; c < _poolCellWidth * _poolCellHeight; ++c) {
+        MTLPoolCell * cell = _cells[c];
+        if (cell != NULL) {
+            [cell release];
+        }
+    }
+    free(_cells);
+    [super dealloc];
+}
+
+// NOTE: called from RQ-thread (on blit operations)
+- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format {
+    return [self getTexture:width height:height format:format isMultiSample:NO];
+}
+
+// NOTE: called from RQ-thread (on blit operations)
+- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format
+                          isMultiSample:(bool)isMultiSample {
+        // 1. clean pool if necessary
+        const int requestedPixels = width*height;
+        const int requestedBytes = requestedPixels*4;
+        if (_memoryTotalAllocated + requestedBytes > MAX_POOL_MEMORY) {
+            [self cleanIfNecessary:0]; // release all free textures
+        } else if (_memoryTotalAllocated + requestedBytes > MAX_POOL_MEMORY/2) {
+            [self cleanIfNecessary:MAX_POOL_ITEM_LIFETIME_SEC]; // release only old free textures
+        }
+
+        // 2. find free item
+        const int cellX0 = width    >> CELL_WIDTH_BITS;
+        const int cellY0 = height   >> CELL_HEIGHT_BITS;
+        const int cellX1 = cellX0 + 1;
+        const int cellY1 = cellY0 + 1;
+        if (cellX1 > _poolCellWidth || cellY1 > _poolCellHeight) {
+            const int newCellWidth = cellX1 <= _poolCellWidth ? _poolCellWidth : cellX1;
+            const int newCellHeight = cellY1 <= _poolCellHeight ? _poolCellHeight : cellY1;
+            const int newCellsCount = newCellWidth*newCellHeight;
+#ifdef DEBUG
+            J2dTraceLn2(J2D_TRACE_VERBOSE, "MTLTexturePool: resize: %d -> %d", _poolCellWidth * _poolCellHeight, newCellsCount);
+#endif
+            void ** newcells = malloc(newCellsCount*sizeof(void*));
+            const int strideBytes = _poolCellWidth * sizeof(void*);
+            for (int cy = 0; cy < _poolCellHeight; ++cy) {
+                void ** dst = newcells + cy*newCellWidth;
+                void ** src = _cells + cy * _poolCellWidth;
+                memcpy(dst, src, strideBytes);
+                if (newCellWidth > _poolCellWidth)
+                    memset(dst + _poolCellWidth, 0, (newCellWidth - _poolCellWidth) * sizeof(void*));
+            }
+            if (newCellHeight > _poolCellHeight) {
+                void ** dst = newcells + _poolCellHeight * newCellWidth;
+                memset(dst, 0, (newCellHeight - _poolCellHeight) * newCellWidth * sizeof(void*));
+            }
+            free(_cells);
+            _cells = newcells;
+            _poolCellWidth = newCellWidth;
+            _poolCellHeight = newCellHeight;
+        }
+
+        MTLTexturePoolItem * minDeltaTpi = nil;
+        int minDeltaArea = -1;
+        for (int cy = cellY0; cy < cellY1; ++cy) {
+            for (int cx = cellX0; cx < cellX1; ++cx) {
+                MTLPoolCell * cell = _cells[cy * _poolCellWidth + cx];
+                if (cell != NULL) {
+                    MTLTexturePoolItem* tpi = [cell occupyItem:width height:height
+                                                        format:format isMultiSample:isMultiSample];
+                    if (!tpi) continue;
+                    const int deltaArea = (const int) (tpi.texture.width * tpi.texture.height - requestedPixels);
+                    if (minDeltaArea < 0 || deltaArea < minDeltaArea) {
+                        minDeltaArea = deltaArea;
+                        minDeltaTpi = tpi;
+                        if (deltaArea == 0) {
+                            // found exact match in current cell
+                            break;
+                        }
+                    }
+                }
+            }
+            if (minDeltaTpi != nil) {
+                break;
+            }
+        }
+
+        if (minDeltaTpi == NULL) {
+            MTLPoolCell* cell = _cells[cellY0 * _poolCellWidth + cellX0];
+            if (cell == NULL) {
+                cell = [[MTLPoolCell alloc] init];
+                _cells[cellY0 * _poolCellWidth + cellX0] = cell;
+            }
+            minDeltaTpi = [cell createItem:device width:width height:height format:format isMultiSample:isMultiSample];
+            _memoryTotalAllocated += requestedBytes;
+            J2dTraceLn5(J2D_TRACE_VERBOSE, "MTLTexturePool: created pool item: tex=%p, w=%d h=%d, pf=%d | total memory = %d Kb", minDeltaTpi.texture, width, height, format, _memoryTotalAllocated/1024);
+        }
+
+        minDeltaTpi.isBusy = YES;
+        minDeltaTpi.lastUsed = time(NULL);
+        return [[[MTLPooledTextureHandle alloc] initWithPoolItem:minDeltaTpi.texture
+                                                            rect:MTLRegionMake2D(0, 0,
+                                                                                 minDeltaTpi.texture.width,
+                                                                                 minDeltaTpi.texture.height)
+                                                        poolItem:minDeltaTpi] autorelease];
+}
+
+- (void) cleanIfNecessary:(int)lastUsedTimeThreshold {
+    time_t lastUsedTimeToRemove =
+            lastUsedTimeThreshold > 0 ?
+                time(NULL) - lastUsedTimeThreshold :
+                lastUsedTimeThreshold;
+    for (int cy = 0; cy < _poolCellHeight; ++cy) {
+        for (int cx = 0; cx < _poolCellWidth; ++cx) {
+            MTLPoolCell * cell = _cells[cy * _poolCellWidth + cx];
+            if (cell != NULL) {
+                _memoryTotalAllocated -= [cell cleanIfBefore:lastUsedTimeToRemove];
+            }
+        }
+    }
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTransform.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTransform.h
new file mode 100644
index 00000000000..59d0d6687db
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTransform.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLTransform_h_Included
+#define MTLTransform_h_Included
+
+#import <Metal/Metal.h>
+
+#include <jni.h>
+
+@interface MTLTransform : NSObject
+- (id)init;
+- (BOOL)isEqual:(MTLTransform *)other;
+- (void)copyFrom:(MTLTransform *)other;
+
+- (void)setTransformM00:(jdouble) m00 M10:(jdouble) m10
+                    M01:(jdouble) m01 M11:(jdouble) m11
+                    M02:(jdouble) m02 M12:(jdouble) m12;
+- (void)resetTransform;
+
+- (void)setVertexMatrix:(id<MTLRenderCommandEncoder>)encoder
+              destWidth:(NSUInteger)dw
+             destHeight:(NSUInteger)dh;
+@end
+
+#endif // MTLTransform_h_Included
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTransform.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTransform.m
new file mode 100644
index 00000000000..2015cbf5439
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTransform.m
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "MTLTransform.h"
+
+#include <jni.h>
+#include <simd/simd.h>
+
+#include "common.h"
+
+@implementation MTLTransform {
+    jboolean      _useTransform;
+    simd_float4x4 _transform4x4;
+    simd_float4x4 _normalize4x4; // just a buffer for setVertexMatrix
+}
+
+- (id)init {
+    self = [super init];
+    if (self) {
+        memset(&_normalize4x4, 0, sizeof(_normalize4x4));
+        _normalize4x4.columns[3][0] = -1.f;
+        _normalize4x4.columns[3][1] = 1.f;
+        _normalize4x4.columns[3][3] = 1.0;
+
+        _useTransform = JNI_FALSE;
+    }
+    return self;
+}
+
+- (BOOL)isEqual:(MTLTransform *)other {
+    if (self == other)
+        return YES;
+    return _useTransform == other->_useTransform
+           && simd_equal(_transform4x4, other->_transform4x4);
+}
+
+- (void)copyFrom:(MTLTransform *)other {
+    _useTransform = other->_useTransform;
+    if (_useTransform) {
+        _transform4x4 = other->_transform4x4;
+    }
+}
+
+- (void)setTransformM00:(jdouble) m00 M10:(jdouble) m10
+                    M01:(jdouble) m01 M11:(jdouble) m11
+                    M02:(jdouble) m02 M12:(jdouble) m12 {
+    memset(&(_transform4x4), 0, sizeof(_transform4x4));
+    _transform4x4.columns[0][0] = m00;
+    _transform4x4.columns[0][1] = m10;
+    _transform4x4.columns[1][0] = m01;
+    _transform4x4.columns[1][1] = m11;
+    _transform4x4.columns[3][0] = m02;
+    _transform4x4.columns[3][1] = m12;
+    _transform4x4.columns[3][3] = 1.0;
+    _useTransform = JNI_TRUE;
+}
+
+- (void)resetTransform {
+    _useTransform = JNI_FALSE;
+}
+
+- (void)setVertexMatrix:(id<MTLRenderCommandEncoder>)encoder
+              destWidth:(NSUInteger)dw
+             destHeight:(NSUInteger)dh {
+    // update matrix for vertex shader
+    _normalize4x4.columns[0][0] = 2/(double)dw;
+    _normalize4x4.columns[1][1] = -2/(double)dh;
+
+    if (_useTransform) {
+        simd_float4x4 vertexMatrix = simd_mul(_normalize4x4, _transform4x4);
+        [encoder setVertexBytes:&(vertexMatrix) length:sizeof(vertexMatrix) atIndex:MatrixBuffer];
+    } else {
+        [encoder setVertexBytes:&(_normalize4x4) length:sizeof(_normalize4x4) atIndex:MatrixBuffer];
+    }
+}
+
+@end
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLUtils.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLUtils.h
new file mode 100644
index 00000000000..ff417f43854
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLUtils.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLUtils_h_Included
+#define MTLUtils_h_Included
+
+#import <Metal/Metal.h>
+
+#define MTLAASampleCount 4
+
+#endif /* MTLUtils_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLUtils.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLUtils.m
new file mode 100644
index 00000000000..fe147d5d2ea
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLUtils.m
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "MTLUtils.h"
+
+#include <jni.h>
+#include <simd/simd.h>
+#import <ThreadUtilities.h>
+#import <PropertiesUtilities.h>
+#include "common.h"
+#include "Trace.h"
+
+extern void J2dTraceImpl(int level, jboolean cr, const char *string, ...);
+void J2dTraceTraceVector(simd_float4 pt) {
+    J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE, "[%lf %lf %lf %lf]", pt.x, pt.y, pt.z, pt.w);
+}
+
+void checkTransform(float * position, simd_float4x4 transform4x4) {
+    J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE, "check transform: ");
+
+    simd_float4 fpt = simd_make_float4(position[0], position[1], position[2], 1.f);
+    simd_float4 fpt_trans = simd_mul(transform4x4, fpt);
+    J2dTraceTraceVector(fpt);
+    J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE, "  ===>>>  ");
+    J2dTraceTraceVector(fpt_trans);
+    J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, " ");
+}
+
+static void traceMatrix(simd_float4x4 * mtx) {
+    for (int row = 0; row < 4; ++row) {
+        J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_FALSE, "  [%lf %lf %lf %lf]",
+                    mtx->columns[0][row], mtx->columns[1][row], mtx->columns[2][row], mtx->columns[3][row]);
+    }
+}
+
+void traceRaster(char * p, int width, int height, int stride) {
+    for (int y = 0; y < height; ++y) {
+        for (int x = 0; x < width; ++x) {
+            unsigned char pix0 = p[y*stride + x*4];
+            unsigned char pix1 = p[y*stride + x*4 + 1];
+            unsigned char pix2 = p[y*stride + x*4 + 2];
+            unsigned char pix3 = p[y*stride + x*4 + 3];
+            J2dTraceImpl(J2D_TRACE_INFO, JNI_FALSE,"[%u,%u,%u,%u], ", pix0, pix1, pix2, pix3);
+        }
+        J2dTraceImpl(J2D_TRACE_INFO, JNI_TRUE, "");
+    }
+}
+
+void tracePoints(jint nPoints, jint *xPoints, jint *yPoints) {
+    for (int i = 0; i < nPoints; i++)
+        J2dTraceImpl(J2D_TRACE_INFO, JNI_TRUE, "\t(%d, %d)", *(xPoints++), *(yPoints++));
+}
+
+
+jboolean isOptionEnabled(const char * option) {
+    JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
+
+    NSString * optionProp = [PropertiesUtilities
+            javaSystemPropertyForKey:[NSString stringWithUTF8String:option] withEnv:env];
+    NSString * lowerCaseProp = [optionProp localizedLowercaseString];
+    return [@"true" isEqual:lowerCaseProp];
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLVertexCache.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLVertexCache.h
new file mode 100644
index 00000000000..240798b7b8a
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLVertexCache.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MTLVertexCache_h_Included
+#define MTLVertexCache_h_Included
+#include "j2d_md.h"
+#include "MTLContext.h"
+#include "fontscalerdefs.h"
+
+/**
+ * Constants that control the size of the vertex cache.
+ */
+#define MTLVC_MAX_INDEX         250
+
+/**
+ * Constants that control the size of the texture tile cache used for
+ * mask operations.
+ */
+#define MTLVC_MASK_CACHE_TILE_WIDTH       32
+#define MTLVC_MASK_CACHE_TILE_HEIGHT      32
+#define MTLVC_MASK_CACHE_TILE_SIZE \
+   (MTLVC_MASK_CACHE_TILE_WIDTH * MTLVC_MASK_CACHE_TILE_HEIGHT)
+
+#define MTLVC_MASK_CACHE_WIDTH_IN_TILES   8
+#define MTLVC_MASK_CACHE_HEIGHT_IN_TILES  4
+
+#define MTLVC_MASK_CACHE_WIDTH_IN_TEXELS \
+   (MTLVC_MASK_CACHE_TILE_WIDTH * MTLVC_MASK_CACHE_WIDTH_IN_TILES)
+#define MTLVC_MASK_CACHE_HEIGHT_IN_TEXELS \
+   (MTLVC_MASK_CACHE_TILE_HEIGHT * MTLVC_MASK_CACHE_HEIGHT_IN_TILES)
+
+/*
+ * We reserve one (fully opaque) tile in the upper-right corner for
+ * operations where the mask is null.
+ */
+#define MTLVC_MASK_CACHE_MAX_INDEX \
+   ((MTLVC_MASK_CACHE_WIDTH_IN_TILES * MTLVC_MASK_CACHE_HEIGHT_IN_TILES) - 1)
+#define MTLVC_MASK_CACHE_SPECIAL_TILE_X \
+   (MTLVC_MASK_CACHE_WIDTH_IN_TEXELS - MTLVC_MASK_CACHE_TILE_WIDTH)
+#define MTLVC_MASK_CACHE_SPECIAL_TILE_Y \
+   (MTLVC_MASK_CACHE_HEIGHT_IN_TEXELS - MTLVC_MASK_CACHE_TILE_HEIGHT)
+
+/**
+ * Exported methods.
+ */
+jboolean MTLVertexCache_InitVertexCache();
+void MTLVertexCache_FlushVertexCache(MTLContext *mtlc);
+void MTLVertexCache_FlushGlyphVertexCache();
+void MTLVertexCache_FreeVertexCache();
+
+void MTLVertexCache_EnableMaskCache(MTLContext *mtlc, BMTLSDOps *dstOps);
+void MTLVertexCache_DisableMaskCache(MTLContext *mtlc);
+void MTLVertexCache_AddMaskQuad(MTLContext *mtlc,
+                                jint srcx, jint srcy,
+                                jint dstx, jint dsty,
+                                jint width, jint height,
+                                jint maskscan, void *mask,
+                                BMTLSDOps *dstOps);
+void
+MTLVertexCache_AddGlyphQuad(MTLContext *mtlc,
+                            jfloat tx1, jfloat ty1, jfloat tx2, jfloat ty2,
+                            jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2);
+void MTLVertexCache_CreateSamplingEncoder(MTLContext *mtlc, BMTLSDOps *dstOps);
+#endif /* MTLVertexCache_h_Included */
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLVertexCache.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLVertexCache.m
new file mode 100644
index 00000000000..35bd357d62d
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLVertexCache.m
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sun_java2d_SunGraphics2D.h"
+
+#include "MTLPaints.h"
+#include "MTLVertexCache.h"
+#include "MTLTexturePool.h"
+#include "MTLTextRenderer.h"
+#include "common.h"
+
+typedef struct _J2DVertex {
+    float position[2];
+    float txtpos[2];
+} J2DVertex;
+
+static J2DVertex *vertexCache = NULL;
+static jint vertexCacheIndex = 0;
+
+static MTLPooledTextureHandle * maskCacheTex = NULL;
+static jint maskCacheIndex = 0;
+static id<MTLRenderCommandEncoder> encoder = NULL;
+
+#define MTLVC_ADD_VERTEX(TX, TY, DX, DY, DZ) \
+    do { \
+        J2DVertex *v = &vertexCache[vertexCacheIndex++]; \
+        v->txtpos[0] = TX; \
+        v->txtpos[1] = TY; \
+        v->position[0]= DX; \
+        v->position[1] = DY; \
+    } while (0)
+
+#define MTLVC_ADD_TRIANGLES(TX1, TY1, TX2, TY2, DX1, DY1, DX2, DY2) \
+    do { \
+        MTLVC_ADD_VERTEX(TX1, TY1, DX1, DY1, 0); \
+        MTLVC_ADD_VERTEX(TX2, TY1, DX2, DY1, 0); \
+        MTLVC_ADD_VERTEX(TX2, TY2, DX2, DY2, 0); \
+        MTLVC_ADD_VERTEX(TX2, TY2, DX2, DY2, 0); \
+        MTLVC_ADD_VERTEX(TX1, TY2, DX1, DY2, 0); \
+        MTLVC_ADD_VERTEX(TX1, TY1, DX1, DY1, 0); \
+    } while (0)
+
+jboolean
+MTLVertexCache_InitVertexCache()
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_InitVertexCache");
+
+    if (vertexCache == NULL) {
+        J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_InitVertexCache : vertexCache == NULL");
+        vertexCache = (J2DVertex *)malloc(MTLVC_MAX_INDEX * sizeof(J2DVertex));
+        if (vertexCache == NULL) {
+            return JNI_FALSE;
+        }
+    }
+
+    return JNI_TRUE;
+}
+
+void
+MTLVertexCache_FlushVertexCache(MTLContext *mtlc)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_FlushVertexCache");
+
+    if (vertexCacheIndex > 0) {
+        [encoder setVertexBytes: vertexCache length:vertexCacheIndex * sizeof(J2DVertex)
+                                                atIndex:MeshVertexBuffer];
+
+        [encoder setFragmentTexture:maskCacheTex.texture atIndex: 0];
+        J2dTraceLn1(J2D_TRACE_INFO,
+            "MTLVertexCache_FlushVertexCache : encode %d characters", (vertexCacheIndex / 6));
+        [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:vertexCacheIndex];
+    }
+    vertexCacheIndex = 0;
+    maskCacheIndex = 0;
+
+    if (maskCacheTex != nil) {
+        [[mtlc getCommandBufferWrapper] registerPooledTexture:maskCacheTex];
+        [maskCacheTex release];
+        maskCacheTex = nil;
+    }
+}
+
+void
+MTLVertexCache_FlushGlyphVertexCache()
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_FlushGlyphVertexCache");
+
+    if (vertexCacheIndex > 0) {
+        [encoder setVertexBytes: vertexCache length:vertexCacheIndex * sizeof(J2DVertex)
+                                                atIndex:MeshVertexBuffer];
+        id<MTLTexture> glyphCacheTex = MTLTR_GetGlyphCacheTexture();
+        [encoder setFragmentTexture:glyphCacheTex atIndex: 0];
+        J2dTraceLn1(J2D_TRACE_INFO,
+            "MTLVertexCache_FlushGlyphVertexCache : encode %d characters", (vertexCacheIndex / 6));
+        [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:vertexCacheIndex];
+    }
+    vertexCacheIndex = 0;
+}
+
+void MTLVertexCache_FreeVertexCache()
+{
+    free(vertexCache);
+    vertexCache = NULL;
+}
+
+static jboolean
+MTLVertexCache_InitMaskCache(MTLContext *mtlc) {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_InitMaskCache");
+    if (maskCacheTex == NULL) {
+        maskCacheTex = [mtlc.texturePool getTexture:MTLVC_MASK_CACHE_WIDTH_IN_TEXELS
+                                             height:MTLVC_MASK_CACHE_HEIGHT_IN_TEXELS
+                                             format:MTLPixelFormatA8Unorm];
+        [maskCacheTex retain];
+        if (maskCacheTex == nil) {
+            J2dTraceLn(J2D_TRACE_ERROR, "MTLVertexCache_InitMaskCache: can't obtain temporary texture object from pool");
+            return JNI_FALSE;
+        }
+    }
+    // init special fully opaque tile in the upper-right corner of
+    // the mask cache texture
+
+    char tile[MTLVC_MASK_CACHE_TILE_SIZE];
+    memset(tile, 0xff, MTLVC_MASK_CACHE_TILE_SIZE);
+
+    jint texx = MTLVC_MASK_CACHE_TILE_WIDTH * (MTLVC_MASK_CACHE_WIDTH_IN_TILES - 1);
+
+    jint texy = MTLVC_MASK_CACHE_TILE_HEIGHT * (MTLVC_MASK_CACHE_HEIGHT_IN_TILES - 1);
+
+    NSUInteger bytesPerRow = 1 * MTLVC_MASK_CACHE_TILE_WIDTH;
+
+    MTLRegion region = {
+            {texx,  texy,   0},
+            {MTLVC_MASK_CACHE_TILE_WIDTH, MTLVC_MASK_CACHE_TILE_HEIGHT, 1}
+    };
+
+
+    // do we really need this??
+    [maskCacheTex.texture replaceRegion:region
+                    mipmapLevel:0
+                      withBytes:tile
+                    bytesPerRow:bytesPerRow];
+
+    return JNI_TRUE;
+}
+
+void
+MTLVertexCache_EnableMaskCache(MTLContext *mtlc, BMTLSDOps *dstOps)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_EnableMaskCache");
+
+    if (!MTLVertexCache_InitVertexCache()) {
+        return;
+    }
+
+    if (maskCacheTex == NULL) {
+        if (!MTLVertexCache_InitMaskCache(mtlc)) {
+            return;
+        }
+    }
+    MTLVertexCache_CreateSamplingEncoder(mtlc, dstOps);
+}
+
+void
+MTLVertexCache_DisableMaskCache(MTLContext *mtlc)
+{
+    // TODO : Once we enable check_previous_op
+    // we will start using DisableMaskCache until then
+    // we are force flushing vertexcache.
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_DisableMaskCache");
+    MTLVertexCache_FlushVertexCache(mtlc);
+    maskCacheIndex = 0;
+    free(vertexCache);
+    vertexCache = NULL;
+}
+
+void
+MTLVertexCache_CreateSamplingEncoder(MTLContext *mtlc, BMTLSDOps *dstOps) {
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_CreateSamplingEncoder");
+    encoder = [mtlc.encoderManager getTextEncoder:dstOps
+                                         isSrcOpaque:NO];
+}
+
+void
+MTLVertexCache_AddMaskQuad(MTLContext *mtlc,
+                           jint srcx, jint srcy,
+                           jint dstx, jint dsty,
+                           jint width, jint height,
+                           jint maskscan, void *mask,
+                           BMTLSDOps *dstOps)
+{
+    jfloat tx1, ty1, tx2, ty2;
+    jfloat dx1, dy1, dx2, dy2;
+
+    J2dTraceLn1(J2D_TRACE_INFO, "MTLVertexCache_AddMaskQuad: %d",
+                maskCacheIndex);
+
+    if (maskCacheIndex >= MTLVC_MASK_CACHE_MAX_INDEX)
+    {
+        J2dTraceLn2(J2D_TRACE_INFO, "maskCacheIndex = %d, vertexCacheIndex = %d", maskCacheIndex, vertexCacheIndex);
+        MTLVertexCache_FlushVertexCache(mtlc);
+        MTLVertexCache_EnableMaskCache(mtlc, dstOps);
+        maskCacheIndex = 0;
+    }
+
+    if (mask != NULL) {
+        jint texx = MTLVC_MASK_CACHE_TILE_WIDTH *
+                    (maskCacheIndex % MTLVC_MASK_CACHE_WIDTH_IN_TILES);
+        jint texy = MTLVC_MASK_CACHE_TILE_HEIGHT *
+                    (maskCacheIndex / MTLVC_MASK_CACHE_WIDTH_IN_TILES);
+        J2dTraceLn5(J2D_TRACE_INFO, "texx = %d texy = %d width = %d height = %d maskscan = %d", texx, texy, width,
+                    height, maskscan);
+        NSUInteger bytesPerRow = 1 * width;
+        NSUInteger slice = bytesPerRow * srcy + srcx;
+        MTLRegion region = {
+                {texx,  texy,   0},
+                {width, height, 1}
+        };
+
+        // Whenever we have source stride bigger that destination stride
+        // we need to pick appropriate source subtexture. In repalceRegion
+        // we can give destination subtexturing properly but we can't
+        // subtexture from system memory glyph we have. So in such
+        // cases we are creating seperate tile and scan the source
+        // stride into destination using memcpy. In case of OpenGL we
+        // can update source pointers, in case of D3D we ar doing memcpy.
+        // We can use MTLBuffer and then copy source subtexture but that
+        // adds extra blitting logic.
+        // TODO : Research more and try removing memcpy logic.
+        if (maskscan <= width) {
+            int height_offset = bytesPerRow * srcy;
+            [maskCacheTex.texture replaceRegion:region
+                            mipmapLevel:0
+                              withBytes:mask + height_offset
+                            bytesPerRow:bytesPerRow];
+        } else {
+            int dst_offset, src_offset;
+            int size = 1 * width * height;
+            char tile[size];
+            dst_offset = 0;
+            for (int i = srcy; i < srcy + height; i++) {
+                J2dTraceLn2(J2D_TRACE_INFO, "srcx = %d srcy = %d", srcx, srcy);
+                src_offset = maskscan * i + srcx;
+                J2dTraceLn2(J2D_TRACE_INFO, "src_offset = %d dst_offset = %d", src_offset, dst_offset);
+                memcpy(tile + dst_offset, mask + src_offset, width);
+                dst_offset = dst_offset + width;
+            }
+            [maskCacheTex.texture replaceRegion:region
+                            mipmapLevel:0
+                              withBytes:tile
+                            bytesPerRow:bytesPerRow];
+        }
+
+        tx1 = ((jfloat) texx) / MTLVC_MASK_CACHE_WIDTH_IN_TEXELS;
+        ty1 = ((jfloat) texy) / MTLVC_MASK_CACHE_HEIGHT_IN_TEXELS;
+    } else {
+        tx1 = ((jfloat)MTLVC_MASK_CACHE_SPECIAL_TILE_X) /
+              MTLVC_MASK_CACHE_WIDTH_IN_TEXELS;
+        ty1 = ((jfloat)MTLVC_MASK_CACHE_SPECIAL_TILE_Y) /
+              MTLVC_MASK_CACHE_HEIGHT_IN_TEXELS;
+    }
+    maskCacheIndex++;
+
+    tx2 = tx1 + (((jfloat)width) / MTLVC_MASK_CACHE_WIDTH_IN_TEXELS);
+    ty2 = ty1 + (((jfloat)height) / MTLVC_MASK_CACHE_HEIGHT_IN_TEXELS);
+
+    dx1 = (jfloat)dstx;
+    dy1 = (jfloat)dsty;
+    dx2 = dx1 + width;
+    dy2 = dy1 + height;
+
+    J2dTraceLn8(J2D_TRACE_INFO, "tx1 = %f ty1 = %f tx2 = %f ty2 = %f dx1 = %f dy1 = %f dx2 = %f dy2 = %f", tx1, ty1, tx2, ty2, dx1, dy1, dx2, dy2);
+    MTLVC_ADD_TRIANGLES(tx1, ty1, tx2, ty2,
+                        dx1, dy1, dx2, dy2);
+}
+
+void
+MTLVertexCache_AddGlyphQuad(MTLContext *mtlc,
+                            jfloat tx1, jfloat ty1, jfloat tx2, jfloat ty2,
+                            jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2)
+{
+    J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_AddGlyphQuad");
+
+    if (vertexCacheIndex >= MTLVC_MAX_INDEX)
+    {
+        J2dTraceLn2(J2D_TRACE_INFO, "maskCacheIndex = %d, vertexCacheIndex = %d", maskCacheIndex, vertexCacheIndex);
+        MTLVertexCache_FlushGlyphVertexCache();
+    }
+
+    MTLVC_ADD_TRIANGLES(tx1, ty1, tx2, ty2,
+                        dx1, dy1, dx2, dy2);
+}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/RenderOptions.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/RenderOptions.h
new file mode 100644
index 00000000000..46521ca5b09
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/RenderOptions.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef __RENDEROPTIONS_H
+#define __RENDEROPTIONS_H
+
+#include <jni.h>
+#include "MTLSurfaceDataBase.h"
+
+// Utility struct to transfer rendering paramenters
+typedef struct {
+    jboolean isTexture;
+    jboolean isAA;
+    int interpolation;
+    SurfaceRasterFlags srcFlags;
+    SurfaceRasterFlags dstFlags;
+    jboolean isText;
+    jboolean isLCD;
+    jboolean isAAShader;
+} RenderOptions;
+
+
+#endif //__RENDEROPTIONS_H
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/common.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/common.h
new file mode 100644
index 00000000000..22b3b0c22ce
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/common.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <simd/simd.h>
+
+#define PGRAM_VERTEX_COUNT 6
+#define QUAD_VERTEX_COUNT 4
+#define GRAD_MAX_FRACTIONS 12
+
+enum GradCycleMethod {
+    GradNoCycle = 0,
+    GradReflect = 1,
+    GradRepeat = 2
+};
+enum VertexAttributes {
+    VertexAttributePosition = 0,
+    VertexAttributeTexPos = 1,
+    VertexAttributeITexPos = 2
+};
+
+enum BufferIndex  {
+    MeshVertexBuffer = 0,
+    FrameUniformBuffer = 1,
+    MatrixBuffer = 2
+};
+
+struct FrameUniforms {
+    vector_float4 color;
+};
+
+struct TransformMatrix {
+    matrix_float4x4 transformMatrix;
+};
+
+struct GradFrameUniforms {
+    vector_float3 params;
+    vector_float4 color1;
+    vector_float4 color2;
+    int isCyclic;
+    float extraAlpha;
+};
+
+struct LinGradFrameUniforms {
+    vector_float3 params;
+    float fract[GRAD_MAX_FRACTIONS];
+    vector_float4 color[GRAD_MAX_FRACTIONS];
+    int numFracts;
+    int isLinear;
+    int cycleMethod;
+    float extraAlpha;
+};
+
+struct RadGradFrameUniforms {
+    float fract[GRAD_MAX_FRACTIONS];
+    vector_float4 color[GRAD_MAX_FRACTIONS];
+    int numFracts;
+    int isLinear;
+    int cycleMethod;
+    vector_float3 m0;
+    vector_float3 m1;
+    vector_float3 precalc;
+    float extraAlpha;
+};
+
+struct Vertex {
+    float position[2];
+};
+
+struct TxtVertex {
+    float position[2];
+    float txtpos[2];
+};
+
+struct AAVertex {
+    float position[2];
+    float otxtpos[2];
+    float itxtpos[2];
+};
+
+// These values are mapped from AffineTransformOp
+#define INTERPOLATION_NEAREST_NEIGHBOR 1
+#define INTERPOLATION_BILINEAR 2
+// #define INTERPOLATION_BICUBIC 3
+// NOTE: Metal samplers doesn't supports bicubic interpolation
+// see table 2.7 from https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+// (probably we need to implement separate fragment shader with bicubic interpolation)
+
+struct TxtFrameUniforms {
+    vector_float4 color;
+    int mode; // NOTE: consider to use bit fields
+    int isSrcOpaque;
+    int isDstOpaque;
+    float extraAlpha;
+};
+
+struct TxtFrameOpRescaleUniforms {
+    vector_float4 color;
+    float extraAlpha;
+
+    int isSrcOpaque;
+    int isNonPremult;
+
+    vector_float4 normScaleFactors;
+    vector_float4 normOffsets;
+};
+
+struct TxtFrameOpConvolveUniforms {
+    float extraAlpha;
+    int isSrcOpaque;
+    vector_float4 imgEdge;
+    int kernelSize;
+    int isEdgeZeroFill;
+};
+
+struct TxtFrameOpLookupUniforms {
+    float extraAlpha;
+    int isSrcOpaque;
+    vector_float4 offset;
+    int isUseSrcAlpha;
+    int isNonPremult;
+};
+
+struct AnchorData
+{
+    vector_float3 xParams;
+    vector_float3 yParams;
+};
+
+struct LCDFrameUniforms {
+    vector_float3 src_adj;
+    vector_float3 gamma;
+    vector_float3 invgamma;
+};
+#endif
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal
new file mode 100644
index 00000000000..44f79c868a9
--- /dev/null
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal
@@ -0,0 +1,821 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <simd/simd.h>
+#include <metal_stdlib>
+#include "common.h"
+
+using namespace metal;
+
+struct VertexInput {
+    float2 position [[attribute(VertexAttributePosition)]];
+};
+
+struct TxtVertexInput {
+    float2 position [[attribute(VertexAttributePosition)]];
+    float2 texCoords [[attribute(VertexAttributeTexPos)]];
+};
+
+struct AAVertexInput {
+    float2 position [[attribute(VertexAttributePosition)]];
+    float2 oTexCoords [[attribute(VertexAttributeTexPos)]];
+    float2 iTexCoords [[attribute(VertexAttributeITexPos)]];
+};
+
+struct ColShaderInOut {
+    float4 position [[position]];
+    half4  color;
+};
+
+struct AAShaderInOut {
+    float4 position [[position]];
+    float2 outerTexCoords;
+    float2 innerTexCoords;
+    half4  color;
+};
+
+struct StencilShaderInOut {
+    float4 position [[position]];
+    char color;
+};
+
+struct TxtShaderInOut {
+    float4 position [[position]];
+    float2 texCoords;
+    float2 tpCoords;
+};
+
+struct LCDShaderInOut {
+    float4 position [[position]];
+    float2 orig_pos;
+    float2 texCoords;
+};
+
+struct GradShaderInOut {
+    float4 position [[position]];
+    float2 texCoords;
+};
+
+
+struct ColShaderInOut_XOR {
+    float4 position [[position]];
+    float2 orig_pos;
+    half4  color;
+};
+
+struct TxtShaderInOut_XOR {
+    float4 position [[position]];
+    float2 orig_pos;
+    float2 texCoords;
+    float2 tpCoords;
+};
+
+inline float fromLinear(float c)
+{
+    if (isnan(c)) c = 0.0;
+    if (c > 1.0)
+          c = 1.0;
+    else if (c < 0.0)
+          c = 0.0;
+    else if (c < 0.0031308)
+          c = 12.92 * c;
+    else
+    c = 1.055 * powr(c, 1.0/2.4) - 0.055;
+    return c;
+}
+
+inline float3 fromLinear3(float3 c) {
+    //c.r = fromLinear(c.r);
+    //c.g = fromLinear(c.g);
+    //c.b = fromLinear(c.b);
+    // Use approximated calculations to match software rendering
+    c.rgb = 1.055 * pow(c.rgb, float3(0.416667)) - 0.055;
+    return c;
+}
+
+template <typename Uniforms> inline
+float4 frag_single_grad(float a, Uniforms uniforms)
+{
+    int fa = floor(a);
+    if (uniforms.isCyclic) {
+        if (fa%2) {
+            a = 1.0 + fa - a;
+        } else {
+            a = a - fa;
+        }
+    } else {
+        a = saturate(a);
+    }
+    return mix(uniforms.color1, uniforms.color2, a);
+}
+
+template <typename Uniforms> inline
+float4 frag_multi_grad(float a, Uniforms uniforms)
+{
+      if (uniforms.cycleMethod > GradNoCycle) {
+          int fa = floor(a);
+          a = a - fa;
+          if (uniforms.cycleMethod == GradReflect && fa%2) {
+              a = 1.0 - a;
+          }
+      } else {
+          a = saturate(a);
+      }
+
+      int n = 0;
+      for (;n < GRAD_MAX_FRACTIONS - 1; n++) {
+          if (a <= uniforms.fract[n + 1]) break;
+      }
+
+      a = (a - uniforms.fract[n]) / (uniforms.fract[n + 1] - uniforms.fract[n]);
+
+      float4 c = mix(uniforms.color[n], uniforms.color[n + 1], a);
+      if (uniforms.isLinear) {
+          c.rgb = fromLinear3(c.rgb);
+      }
+      return c;
+}
+
+vertex ColShaderInOut vert_col(VertexInput in [[stage_in]],
+       constant FrameUniforms& uniforms [[buffer(FrameUniformBuffer)]],
+       constant TransformMatrix& transform [[buffer(MatrixBuffer)]]) {
+    ColShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    out.color = half4(uniforms.color.r, uniforms.color.g, uniforms.color.b, uniforms.color.a);
+    return out;
+}
+
+vertex AAShaderInOut vert_col_aa(AAVertexInput in [[stage_in]],
+       constant FrameUniforms& uniforms [[buffer(FrameUniformBuffer)]],
+       constant TransformMatrix& transform [[buffer(MatrixBuffer)]]) {
+    AAShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    out.color = half4(uniforms.color.r, uniforms.color.g, uniforms.color.b, uniforms.color.a);
+    out.outerTexCoords = in.oTexCoords;
+    out.innerTexCoords = in.iTexCoords;
+    return out;
+}
+
+vertex StencilShaderInOut vert_stencil(VertexInput in [[stage_in]],
+       constant FrameUniforms& uniforms [[buffer(FrameUniformBuffer)]],
+       constant TransformMatrix& transform [[buffer(MatrixBuffer)]]) {
+    StencilShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix * pos4;
+    out.color = 0xFF;
+    return out;
+}
+
+vertex GradShaderInOut vert_grad(VertexInput in [[stage_in]], constant TransformMatrix& transform [[buffer(MatrixBuffer)]]) {
+    GradShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    return out;
+}
+
+vertex TxtShaderInOut vert_txt(TxtVertexInput in [[stage_in]], constant TransformMatrix& transform [[buffer(MatrixBuffer)]]) {
+    TxtShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    out.texCoords = in.texCoords;
+    return out;
+}
+
+vertex LCDShaderInOut vert_txt_lcd(TxtVertexInput in [[stage_in]], constant TransformMatrix& transform [[buffer(MatrixBuffer)]]) {
+    LCDShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    out.orig_pos = in.position;
+    out.texCoords = in.texCoords;
+    return out;
+}
+
+vertex TxtShaderInOut vert_txt_tp(TxtVertexInput in [[stage_in]], constant AnchorData& anchorData [[buffer(FrameUniformBuffer)]], constant TransformMatrix& transform [[buffer(MatrixBuffer)]])
+{
+    TxtShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix * pos4;
+
+    // Compute texture coordinates here w.r.t. anchor rect of texture paint
+    out.tpCoords.x = (anchorData.xParams[0] * in.position.x) +
+                      (anchorData.xParams[1] * in.position.y) +
+                      (anchorData.xParams[2] * out.position.w);
+    out.tpCoords.y = (anchorData.yParams[0] * in.position.x) +
+                      (anchorData.yParams[1] * in.position.y) +
+                      (anchorData.yParams[2] * out.position.w);
+    out.texCoords = in.texCoords;
+
+    return out;
+}
+
+vertex GradShaderInOut vert_txt_grad(TxtVertexInput in [[stage_in]],
+                                     constant TransformMatrix& transform [[buffer(MatrixBuffer)]]) {
+    GradShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    out.texCoords = in.texCoords;
+    return out;
+}
+
+fragment half4 frag_col(ColShaderInOut in [[stage_in]]) {
+    return in.color;
+}
+
+fragment half4 frag_col_aa(AAShaderInOut in [[stage_in]]) {
+    float2 oleg1 = dfdx(in.outerTexCoords);
+    float2 oleg2 = dfdy(in.outerTexCoords);
+    // Calculate the bounds of the distorted pixel parallelogram.
+    float2 corner = in.outerTexCoords - (oleg1+oleg2)/2.0;
+    float2 omin = min(corner, corner+oleg1);
+    omin = min(omin, corner+oleg2);
+    omin = min(omin, corner+oleg1+oleg2);
+    float2 omax = max(corner, corner+oleg1);
+    omax = max(omax, corner+oleg2);
+    omax = max(omax, corner+oleg1+oleg2);
+    // Calculate the vectors for the "legs" of the pixel parallelogram
+    // for the inner parallelogram.
+    float2 ileg1 = dfdx(in.innerTexCoords);
+    float2 ileg2 = dfdy(in.innerTexCoords);
+    // Calculate the bounds of the distorted pixel parallelogram.
+    corner = in.innerTexCoords - (ileg1+ileg2)/2.0;
+    float2 imin = min(corner, corner+ileg1);
+    imin = min(imin, corner+ileg2);
+    imin = min(imin, corner+ileg1+ileg2);
+    float2 imax = max(corner, corner+ileg1);
+    imax = max(imax, corner+ileg2);
+    imax = max(imax, corner+ileg1+ileg2);
+    // Clamp the bounds of the parallelograms to the unit square to
+    // estimate the intersection of the pixel parallelogram with
+    // the unit square.  The ratio of the 2 rectangle areas is a
+    // reasonable estimate of the proportion of coverage.
+    float2 o1 = clamp(omin, 0.0, 1.0);
+    float2 o2 = clamp(omax, 0.0, 1.0);
+    float oint = (o2.y-o1.y)*(o2.x-o1.x);
+    float oarea = (omax.y-omin.y)*(omax.x-omin.x);
+    float2 i1 = clamp(imin, 0.0, 1.0);
+    float2 i2 = clamp(imax, 0.0, 1.0);
+    float iint = (i2.y-i1.y)*(i2.x-i1.x);
+    float iarea = (imax.y-imin.y)*(imax.x-imin.x);
+    // Proportion of pixel in outer shape minus the proportion
+    // of pixel in the inner shape == the coverage of the pixel
+    // in the area between the two.
+    float coverage = oint/oarea - iint / iarea;
+    return (in.color * coverage);
+}
+
+fragment unsigned int frag_stencil(StencilShaderInOut in [[stage_in]]) {
+    return in.color;
+}
+
+// NOTE:
+// 1. consider to make shaders without IF-conditions
+// 2. we can pass interpolation mode via uniforms and select corresponding sampler in shader
+//  but it can cause performance problems (something like getTextureSampler(hint) will be invoked
+//  for every pixel)
+
+fragment half4 frag_txt(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> renderTexture [[texture(0)]],
+        constant TxtFrameUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]]
+) {
+    float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
+    float srcA = uniforms.isSrcOpaque ? 1 : pixelColor.a;
+    if (uniforms.mode) {
+        float3 c = mix(pixelColor.rgb, uniforms.color.rgb, srcA);
+        return half4(c.r, c.g, c.b ,
+                     (uniforms.isSrcOpaque) ?
+                      uniforms.color.a : pixelColor.a*uniforms.color.a);
+    }
+
+    return half4(pixelColor.r,
+                 pixelColor.g,
+                 pixelColor.b, srcA)*uniforms.extraAlpha;
+}
+
+fragment half4 frag_text(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> renderTexture [[texture(0)]],
+        constant TxtFrameUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]]
+) {
+    float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
+    return half4(uniforms.color * pixelColor.a);
+}
+
+fragment half4 frag_txt_tp(TxtShaderInOut vert [[stage_in]],
+                       texture2d<float, access::sample> renderTexture [[texture(0)]],
+                       texture2d<float, access::sample> paintTexture [[texture(1)]],
+                       constant TxtFrameUniforms& uniforms [[buffer(1)]],
+                       sampler textureSampler [[sampler(0)]]
+) {
+    float4 renderColor = renderTexture.sample(textureSampler, vert.texCoords);
+    float4 paintColor = paintTexture.sample(textureSampler, vert.tpCoords);
+    const float srcA = uniforms.isSrcOpaque ? 1 : paintColor.a;
+    return half4(paintColor.r*renderColor.a,
+                 paintColor.g*renderColor.a,
+                 paintColor.b*renderColor.a,
+                 srcA*renderColor.a) * uniforms.extraAlpha;
+}
+
+fragment half4 frag_txt_grad(GradShaderInOut in [[stage_in]],
+                         constant GradFrameUniforms& uniforms [[buffer(0)]],
+                         texture2d<float, access::sample> renderTexture [[texture(0)]])
+{
+    constexpr sampler textureSampler (address::repeat, mag_filter::nearest,
+                                      min_filter::nearest);
+
+    float4 renderColor = renderTexture.sample(textureSampler, in.texCoords);
+
+    float3 v = float3(in.position.x-0.5, in.position.y-0.5, 1);
+    float  a = (dot(v,uniforms.params)-0.25)*2.0;
+    return half4(frag_single_grad(a, uniforms)*renderColor.a)*uniforms.extraAlpha;
+}
+
+fragment half4 frag_txt_lin_grad(GradShaderInOut in [[stage_in]],
+                                 constant LinGradFrameUniforms& uniforms [[buffer(0)]],
+                                 texture2d<float, access::sample> renderTexture [[texture(0)]])
+{
+    constexpr sampler textureSampler (address::repeat, mag_filter::nearest,
+                                      min_filter::nearest);
+
+    float4 renderColor = renderTexture.sample(textureSampler, in.texCoords);
+    float3 v = float3(in.position.x, in.position.y, 1);
+    float  a = dot(v,uniforms.params);
+    return half4(frag_multi_grad(a, uniforms)*renderColor.a)*uniforms.extraAlpha;
+}
+
+fragment half4 frag_txt_rad_grad(GradShaderInOut in [[stage_in]],
+                                 constant RadGradFrameUniforms& uniforms [[buffer(0)]],
+                                 texture2d<float, access::sample> renderTexture [[texture(0)]])
+{
+    constexpr sampler textureSampler (address::repeat, mag_filter::nearest,
+                                      min_filter::nearest);
+
+    float4 renderColor = renderTexture.sample(textureSampler, in.texCoords);
+
+    float3 fragCoord = float3(in.position.x-0.5, in.position.y-0.5, 1);
+    float  x = dot(fragCoord, uniforms.m0);
+    float  y = dot(fragCoord, uniforms.m1);
+    float  xfx = x - uniforms.precalc.x;
+    float  a = (uniforms.precalc.x*xfx + sqrt(xfx*xfx + y*y*uniforms.precalc.y))*uniforms.precalc.z;
+    return half4(frag_multi_grad(a, uniforms)*renderColor.a)*uniforms.extraAlpha;
+}
+
+
+fragment half4 aa_frag_txt(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> renderTexture [[texture(0)]],
+        texture2d<float, access::sample> stencilTexture [[texture(1)]],
+        constant TxtFrameUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]]
+) {
+    float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
+    if (!is_null_texture(stencilTexture)) {
+        float4 stencil = stencilTexture.sample(textureSampler, vert.texCoords);
+        if (stencil.r ==  0.0) {
+            discard_fragment();
+        }
+    }
+    return half4(pixelColor.r, pixelColor.g, pixelColor.b, pixelColor.a);
+}
+
+fragment half4 frag_txt_op_rescale(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> srcTex [[texture(0)]],
+        constant TxtFrameOpRescaleUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]]
+) {
+    float4 srcColor = srcTex.sample(textureSampler, vert.texCoords);
+    const float srcA = uniforms.isSrcOpaque ? 1 : srcColor.a;
+
+    // TODO: check uniforms.isNonPremult and pre-multiply if necessary
+    return half4(srcColor.r*uniforms.normScaleFactors.r + uniforms.normOffsets.r,
+                 srcColor.g*uniforms.normScaleFactors.g + uniforms.normOffsets.g,
+                 srcColor.b*uniforms.normScaleFactors.b + uniforms.normOffsets.b, srcA)*uniforms.extraAlpha;
+
+    // NOTE: GL-shader multiplies result with glColor (in order to apply extra alpha), probably it's better to do the
+    // same here.
+    //
+    // GL-shader impl:
+    //"    vec4 srcColor = texture%s(baseImage, gl_TexCoord[0].st);"
+    //"    %s"                                                      // (placeholder for un-premult code: srcColor.rgb /= srcColor.a;)
+    //"    vec4 result = (srcColor * scaleFactors) + offsets;"      // rescale source value
+    //"    %s"                                                      // (placeholder for re-premult code: result.rgb *= result.a;)
+    //"    gl_FragColor = result * gl_Color;"                       // modulate with gl_Color in order to apply extra alpha
+}
+
+fragment half4 frag_txt_op_convolve(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> srcTex [[texture(0)]],
+        constant TxtFrameOpConvolveUniforms& uniforms [[buffer(1)]],
+        const device float * kernelVals [[buffer(2)]],
+        sampler textureSampler [[sampler(0)]]
+) {
+    float4 sum = float4(0, 0, 0, 0);
+    if (vert.texCoords[0] < uniforms.imgEdge[0]
+        || vert.texCoords[1] < uniforms.imgEdge[1]
+        || vert.texCoords[0] > uniforms.imgEdge[2]
+        || vert.texCoords[1] > uniforms.imgEdge[3]
+    ) {
+        if (!uniforms.isEdgeZeroFill) {
+            sum = srcTex.sample(textureSampler, vert.texCoords);
+        }
+    }
+
+    for (int i = 0; i < uniforms.kernelSize; i++) {
+        float3 kern = float3(kernelVals[i*3], kernelVals[i*3 + 1], kernelVals[i*3 + 2]);
+        float2 pos = float2(vert.texCoords.x + kern.x, vert.texCoords.y + kern.y);
+        float4 pixCol = srcTex.sample(textureSampler, pos);
+        sum.r += kern.z * pixCol.r;
+        sum.g += kern.z * pixCol.g;
+        sum.b += kern.z * pixCol.b;
+        sum.a += kern.z * pixCol.a;
+    }
+    const float srcA = uniforms.isSrcOpaque ? 1 : sum.a;
+    return half4(sum.r, sum.g, sum.b, srcA)*uniforms.extraAlpha;
+
+    // NOTE: GL-shader multiplies result with glColor (in order to apply extra alpha), probably it's better to do the
+    // same here.
+    //
+    // GL-shader impl:
+    //"    if (any(lessThan(gl_TexCoord[0].st, imgEdge.xy)) ||"
+    //"        any(greaterThan(gl_TexCoord[0].st, imgEdge.zw)))"
+    //"    {"
+    //"        %s"      // (placeholder for edge condition code)
+    //"    } else {"
+    //"        sum = vec4(0.0);"
+    //"        for (i = 0; i < MAX_KERNEL_SIZE; i++) {"
+    //"            sum +="
+    //"                kernelVals[i].z *"
+    //"                texture%s(baseImage,"
+    //"                          gl_TexCoord[0].st + kernelVals[i].xy);"
+    //"        }"
+    //"    }"
+    //""
+    //"    gl_FragColor = sum * gl_Color;" // modulate with gl_Color in order to apply extra alpha
+}
+
+fragment half4 frag_txt_op_lookup(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> srcTex [[texture(0)]],
+        texture2d<float, access::sample> lookupTex [[texture(1)]],
+        constant TxtFrameOpLookupUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]]
+) {
+    float4 srcColor = srcTex.sample(textureSampler, vert.texCoords);
+    float4 srcIndex = srcColor - uniforms.offset;
+    const float2 posR = float2(srcIndex.r, 0.125);
+    const float2 posG = float2(srcIndex.g, 0.375);
+    const float2 posB = float2(srcIndex.b, 0.625);
+
+    float4 lookupR = lookupTex.sample(textureSampler, posR);
+    float4 lookupG = lookupTex.sample(textureSampler, posG);
+    float4 lookupB = lookupTex.sample(textureSampler, posB);
+    const float srcA = uniforms.isSrcOpaque ? 1 : srcColor.a;
+    const float a = uniforms.isUseSrcAlpha ? srcA : lookupTex.sample(textureSampler, float2(srcIndex.a, 0.875)).a;
+
+    // TODO: check uniforms.isNonPremult and pre-multiply if necessary
+    return half4(lookupR.a, lookupG.a, lookupB.a, a)*uniforms.extraAlpha;
+
+    // NOTE: GL-shader multiplies result with glColor (in order to apply extra alpha), probably it's better to do the
+    // same here.
+    //
+    // GL-shader impl:
+    //"    vec4 srcColor = texture%s(baseImage, gl_TexCoord[0].st);"
+    //"    %s"                                  // (placeholder for un-premult code)
+    //"    vec4 srcIndex = srcColor - offset;"  // subtract offset from original index
+    //
+    //      // use source value as input to lookup table (note that
+    //      // "v" texcoords are hardcoded to hit texel centers of
+    //      // each row/band in texture)
+    //"    vec4 result;"
+    //"    result.r = texture2D(lookupTable, vec2(srcIndex.r, 0.125)).r;"
+    //"    result.g = texture2D(lookupTable, vec2(srcIndex.g, 0.375)).r;"
+    //"    result.b = texture2D(lookupTable, vec2(srcIndex.b, 0.625)).r;"
+    //"    %s"                                  // (placeholder for alpha store code)
+    //"    %s"                                  // (placeholder for re-premult code)
+    //"    gl_FragColor = result * gl_Color;"   // modulate with gl_Color in order to apply extra alpha
+}
+
+fragment half4 frag_grad(GradShaderInOut in [[stage_in]],
+                         constant GradFrameUniforms& uniforms [[buffer(0)]]) {
+    float3 v = float3(in.position.x-0.5, in.position.y-0.5, 1);
+    float  a = (dot(v,uniforms.params)-0.25)*2.0;
+    return half4(frag_single_grad(a, uniforms)) * uniforms.extraAlpha;
+}
+
+// LinGradFrameUniforms
+fragment half4 frag_lin_grad(GradShaderInOut in [[stage_in]],
+                             constant LinGradFrameUniforms& uniforms [[buffer(0)]]) {
+    float3 v = float3(in.position.x-0.5, in.position.y-0.5, 1);
+    float  a = dot(v, uniforms.params);
+    return half4(frag_multi_grad(a, uniforms))*uniforms.extraAlpha;
+}
+
+fragment half4 frag_rad_grad(GradShaderInOut in [[stage_in]],
+                             constant RadGradFrameUniforms& uniforms [[buffer(0)]]) {
+    float3 fragCoord = float3(in.position.x-0.5, in.position.y-0.5, 1);
+    float  x = dot(fragCoord, uniforms.m0);
+    float  y = dot(fragCoord, uniforms.m1);
+    float  xfx = x - uniforms.precalc.x;
+    float  a = (uniforms.precalc.x*xfx + sqrt(xfx*xfx + y*y*uniforms.precalc.y))*uniforms.precalc.z;
+    return half4(frag_multi_grad(a, uniforms))*uniforms.extraAlpha;
+}
+
+vertex TxtShaderInOut vert_tp(VertexInput in [[stage_in]],
+       constant AnchorData& anchorData [[buffer(FrameUniformBuffer)]],
+       constant TransformMatrix& transform [[buffer(MatrixBuffer)]])
+{
+    TxtShaderInOut out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix * pos4;
+
+    // Compute texture coordinates here w.r.t. anchor rect of texture paint
+    out.texCoords.x = (anchorData.xParams[0] * in.position.x) +
+                      (anchorData.xParams[1] * in.position.y) +
+                      (anchorData.xParams[2] * out.position.w);
+    out.texCoords.y = (anchorData.yParams[0] * in.position.x) +
+                      (anchorData.yParams[1] * in.position.y) +
+                      (anchorData.yParams[2] * out.position.w);
+
+    return out;
+}
+
+fragment half4 frag_tp(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> renderTexture [[texture(0)]],
+        constant TxtFrameUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]])
+{
+    float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
+    const float srcA = uniforms.isSrcOpaque ? 1 : pixelColor.a;
+    return half4(pixelColor.r, pixelColor.g, pixelColor.b, srcA) * uniforms.extraAlpha;
+}
+
+
+
+/* The variables involved in the equation can be expressed as follows:
+ *
+ *   Cs = Color component of the source (foreground color) [0.0, 1.0]
+ *   Cd = Color component of the destination (background color) [0.0, 1.0]
+ *   Cr = Color component to be written to the destination [0.0, 1.0]
+ *   Ag = Glyph alpha (aka intensity or coverage) [0.0, 1.0]
+ *   Ga = Gamma adjustment in the range [1.0, 2.5]
+ *   (^ means raised to the power)
+ *
+ * And here is the theoretical equation approximated by this shader:
+ *
+ *            Cr = (Ag*(Cs^Ga) + (1-Ag)*(Cd^Ga)) ^ (1/Ga)
+ */
+fragment float4 lcd_color(
+        LCDShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> glyphTexture [[texture(0)]],
+        texture2d<float, access::sample> dstTexture [[texture(1)]],
+        constant LCDFrameUniforms& uniforms [[buffer(1)]])
+{
+    float3 src_adj = uniforms.src_adj;
+    float3 gamma = uniforms.gamma;
+    float3 invgamma = uniforms.invgamma;
+
+    constexpr sampler glyphTextureSampler (address::repeat,
+                                      mag_filter::nearest,
+                                      min_filter::nearest);
+
+    // load the RGB value from the glyph image at the current texcoord
+    float3 glyph_clr = float3(glyphTexture.sample(glyphTextureSampler, vert.texCoords));
+
+    if (glyph_clr.r == 0.0f && glyph_clr.g == 0.0f && glyph_clr.b == 0.0f) {
+        // zero coverage, so skip this fragment
+        discard_fragment();
+    }
+
+    // load the RGB value from the corresponding destination pixel
+    uint2 texCoord = {(unsigned int)(vert.orig_pos.x), (unsigned int)(vert.orig_pos.y)};
+    float4 dst_clr = dstTexture.read(texCoord);
+
+    // gamma adjust the dest color
+    float3 dst_adj = pow(dst_clr.rgb, gamma);
+
+    // linearly interpolate the three color values
+    float3 result = mix(dst_adj, src_adj, glyph_clr);
+
+    // gamma re-adjust the resulting color (alpha is always set to 1.0)
+    return float4(pow(result.rgb, invgamma), 1.0);
+
+}
+// Compute shader to transfer clipping data to the texture used for manual clipping in
+// aa_frag_txt shader
+kernel void stencil2tex(const device uchar *imageBuffer [[buffer(0)]],
+    device uchar4 *outputBuffer [[buffer(1)]],
+    uint gid [[thread_position_in_grid]])
+{
+    uchar p = imageBuffer[gid];
+    outputBuffer[gid] = uchar4(p, p, p, p);
+}
+
+// work item deals with 4 byte pixel
+// assuming that data is aligned
+kernel void rgb_to_rgba(const device uchar *imageBuffer [[buffer(0)]],
+                        device uchar *outputBuffer [[buffer(1)]],
+                        uint gid [[thread_position_in_grid]])
+{
+    outputBuffer[4 * gid]     = imageBuffer[4 * gid];     // r
+    outputBuffer[4 * gid + 1] = imageBuffer[4 * gid + 1]; // g
+    outputBuffer[4 * gid + 2] = imageBuffer[4 * gid + 2]; // b
+    outputBuffer[4 * gid + 3] = 255;                      // a
+}
+
+kernel void bgr_to_rgba(const device uchar *imageBuffer [[buffer(0)]],
+                        device uchar *outputBuffer [[buffer(1)]],
+                        uint gid [[thread_position_in_grid]])
+{
+    outputBuffer[4 * gid]     = imageBuffer[4 * gid + 2]; // r
+    outputBuffer[4 * gid + 1] = imageBuffer[4 * gid + 1]; // g
+    outputBuffer[4 * gid + 2] = imageBuffer[4 * gid];     // b
+    outputBuffer[4 * gid + 3] = 255;                      // a
+}
+
+kernel void xrgb_to_rgba(const device uchar *imageBuffer [[buffer(0)]],
+                         device uchar *outputBuffer [[buffer(1)]],
+                         uint gid [[thread_position_in_grid]])
+{
+    outputBuffer[4 * gid]     = imageBuffer[4 * gid + 1]; // r
+    outputBuffer[4 * gid + 1] = imageBuffer[4 * gid + 2]; // g
+    outputBuffer[4 * gid + 2] = imageBuffer[4 * gid + 3]; // b
+    outputBuffer[4 * gid + 3] = imageBuffer[4 * gid];     // a
+}
+
+
+kernel void xbgr_to_rgba(const device uchar *imageBuffer [[buffer(0)]],
+                         device uchar *outputBuffer [[buffer(1)]],
+                         uint gid [[thread_position_in_grid]])
+{
+    outputBuffer[4 * gid]     = imageBuffer[4 * gid + 3]; // r
+    outputBuffer[4 * gid + 1] = imageBuffer[4 * gid + 2]; // g
+    outputBuffer[4 * gid + 2] = imageBuffer[4 * gid + 1]; // b
+    outputBuffer[4 * gid + 3] = imageBuffer[4 * gid];     // a
+}
+
+// ----------------------------------------------------------------------------
+// Shaders for rendering in XOR Mode
+// ----------------------------------------------------------------------------
+vertex ColShaderInOut_XOR vert_col_xorMode(VertexInput in [[stage_in]],
+       constant FrameUniforms& uniforms [[buffer(FrameUniformBuffer)]],
+       constant TransformMatrix& transform [[buffer(MatrixBuffer)]])
+{
+    ColShaderInOut_XOR out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    out.orig_pos = in.position;
+    out.color = half4(uniforms.color.r, uniforms.color.g, uniforms.color.b, uniforms.color.a);
+    return out;
+}
+
+fragment half4 frag_col_xorMode(ColShaderInOut_XOR in [[stage_in]],
+        texture2d<float, access::read> renderTexture [[texture(0)]])
+{
+    uint2 texCoord = {(unsigned int)(in.orig_pos.x), (unsigned int)(in.orig_pos.y)};
+
+    float4 pixelColor = renderTexture.read(texCoord);
+    half4 color = in.color;
+
+    half4 c;
+    c.r = float( (unsigned char)(pixelColor.r * 255.0) ^ (unsigned char)(color.r * 255.0)) / 255.0f;
+    c.g = float( (unsigned char)(pixelColor.g * 255.0) ^ (unsigned char)(color.g * 255.0)) / 255.0f;
+    c.b = float( (unsigned char)(pixelColor.b * 255.0) ^ (unsigned char)(color.b * 255.0)) / 255.0f;
+    c.a = 1.0;
+
+    return c;
+}
+
+
+vertex TxtShaderInOut_XOR vert_txt_xorMode(
+        TxtVertexInput in [[stage_in]],
+        constant TransformMatrix& transform [[buffer(MatrixBuffer)]])
+{
+    TxtShaderInOut_XOR out;
+    float4 pos4 = float4(in.position, 0.0, 1.0);
+    out.position = transform.transformMatrix*pos4;
+    out.orig_pos = in.position;
+    out.texCoords = in.texCoords;
+    return out;
+}
+
+fragment half4 frag_txt_xorMode(
+        TxtShaderInOut_XOR vert [[stage_in]],
+        texture2d<float, access::sample> renderTexture [[texture(0)]],
+        texture2d<float, access::read> backgroundTexture [[texture(1)]],
+        constant TxtFrameUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]])
+{
+    uint2 texCoord = {(unsigned int)(vert.orig_pos.x), (unsigned int)(vert.orig_pos.y)};
+    float4 bgColor = backgroundTexture.read(texCoord);
+
+    float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
+    float srcA = uniforms.isSrcOpaque ? 1 : pixelColor.a;
+
+    float4 c;
+    if (uniforms.mode) {
+        c = mix(pixelColor, uniforms.color, srcA);
+    } else {
+        c = float4(pixelColor.r,
+                 pixelColor.g,
+                 pixelColor.b, srcA)*uniforms.extraAlpha;
+    }
+
+    half4 ret;
+    ret.r = half( (unsigned char)(c.r * 255.0) ^ (unsigned char)(bgColor.r * 255.0)) / 255.0f;
+    ret.g = half( (unsigned char)(c.g * 255.0) ^ (unsigned char)(bgColor.g * 255.0)) / 255.0f;
+    ret.b = half( (unsigned char)(c.b * 255.0) ^ (unsigned char)(bgColor.b * 255.0)) / 255.0f;
+    ret.a = c.a + (1.0 - c.a) * bgColor.a;
+
+    return ret;
+}
+
+
+/*
+    // --------------------------------------------------------------------------------------
+    Currently, gradient paint and texture paint XOR mode rendering has been implemented
+    through tile based rendering (similar to OGL) that uses MTLBlitLoops_SurfaceToSwBlit method for
+    getting framebuffer tiles and render using a different render pipe (not MTLRenderer)
+
+    In metal, we can avoid tile based rendering and use below shaders.
+    NOTE: These two shaders are incomplete and need some tweak.
+    // --------------------------------------------------------------------------------------
+
+fragment half4 frag_grad_xorMode(GradShaderInOut_XOR in [[stage_in]],
+                         texture2d<float, access::read> renderTexture [[texture(0)]],
+                         constant GradFrameUniforms& uniforms [[buffer(0)]]) {
+    uint2 texCoord = {(unsigned int)(in.orig_pos.x), (unsigned int)(in.orig_pos.y)};
+    float4 pixelColor = renderTexture.read(texCoord);
+
+    float3 v = float3(in.position.x, in.position.y, 1);
+    float  a = (dot(v,uniforms.params)-0.25)*2.0;
+    float4 c = mix(uniforms.color1, uniforms.color2, a);
+
+    half4 ret;
+    ret.r = float( (unsigned char)(pixelColor.r * 255.0) ^ (unsigned char)(c.r * 255.0)) / 255.0f;
+    ret.g = float( (unsigned char)(pixelColor.g * 255.0) ^ (unsigned char)(c.g * 255.0)) / 255.0f;
+    ret.b = float( (unsigned char)(pixelColor.b * 255.0) ^ (unsigned char)(c.b * 255.0)) / 255.0f;
+
+    return half4(ret);
+}
+
+
+fragment half4 frag_tp_xorMode(
+        TxtShaderInOut vert [[stage_in]],
+        texture2d<float, access::sample> renderTexture [[texture(0)]],
+        texture2d<float, access::read> backgroundTexture [[texture(1)]],
+        constant int& xorColor[[buffer(0)]])
+{
+    uint2 texCoord = {(unsigned int)(vert.orig_pos.x), (unsigned int)(vert.orig_pos.y)};
+    float4 bgColor = backgroundTexture.read(texCoord);
+
+    constexpr sampler textureSampler (address::repeat,
+                                      mag_filter::nearest,
+                                      min_filter::nearest);
+
+    float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
+
+    pixelColor.r = float( (unsigned char)(pixelColor.r * 255.0) ^ ((xorColor >> 16) & 0xFF) ) / 255.0f;
+    pixelColor.g = float( (unsigned char)(pixelColor.g * 255.0) ^ ((xorColor >> 8) & 0xFF)) / 255.0f;
+    pixelColor.b = float( (unsigned char)(pixelColor.b * 255.0) ^ (xorColor & 0xFF)) / 255.0f;
+    pixelColor.a = 1.0;
+
+    half4 ret;
+    ret.r = half( (unsigned char)(pixelColor.r * 255.0) ^ (unsigned char)(bgColor.r * 255.0)) / 255.0f;
+    ret.g = half( (unsigned char)(pixelColor.g * 255.0) ^ (unsigned char)(bgColor.g * 255.0)) / 255.0f;
+    ret.b = half( (unsigned char)(pixelColor.b * 255.0) ^ (unsigned char)(bgColor.b * 255.0)) / 255.0f;
+    ret.a = 1.0;
+
+    return ret;
+
+    // This implementation defaults alpha to 1.0 as if source is opaque
+    //TODO : implement alpha component value if source is transparent
+}
+*/
diff --git a/src/java.desktop/share/native/libawt/java2d/loops/GraphicsPrimitiveMgr.h b/src/java.desktop/share/native/libawt/java2d/loops/GraphicsPrimitiveMgr.h
index b9158ce99f6..2a3353aded8 100644
--- a/src/java.desktop/share/native/libawt/java2d/loops/GraphicsPrimitiveMgr.h
+++ b/src/java.desktop/share/native/libawt/java2d/loops/GraphicsPrimitiveMgr.h
@@ -485,6 +485,9 @@ extern struct _CompositeTypes {
 #define PtrPixelsRow(p, y, scanStride)    PtrAddBytes(p, \
     ((intptr_t) (y)) * (scanStride))
 
+#define PtrPixelsBand(p, y, length, elemSize)    PtrAddBytes(p, \
+    ((intptr_t) (y)) * (length) * (elemSize))
+
 /*
  * The function to call with an array of NativePrimitive structures
  * to register them with the Java GraphicsPrimitiveMgr.
diff --git a/test/jdk/performance/client/RenderPerfTest/Makefile b/test/jdk/performance/client/RenderPerfTest/Makefile
new file mode 100644
index 00000000000..4dbbe9197f2
--- /dev/null
+++ b/test/jdk/performance/client/RenderPerfTest/Makefile
@@ -0,0 +1,79 @@
+#
+# Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+
+SOURCEPATH=src
+CLASSES=build
+DIST=dist
+RESOURCES=resources
+
+RENDERPERF_CLASSES = $(CLASSES)/renderperf/RenderPerfTest.class
+RENDERPERFLCD_CLASSES = $(CLASSES)/renderperf/RenderPerfLCDTest.class
+RENDERPERF_SOURCES = $(SOURCEPATH)/renderperf/RenderPerfTest.java
+RENDERPERFLCD_SOURCES = $(SOURCEPATH)/renderperf/RenderPerfLCDTest.java
+
+RENDERPERF_RESOURCES = $(CLASSES)/renderperf/images/duke.png
+
+all: mkdirs $(DIST)/RenderPerfTest.jar $(DIST)/RenderPerfLCDTest.jar
+
+run: mkdirs $(DIST)/RenderPerfTest.jar
+	java -jar $(DIST)/RenderPerfTest.jar
+
+$(DIST)/RenderPerfTest.jar: \
+	$(RENDERPERF_CLASSES) $(RENDERPERF_RESOURCES) \
+	$(CLASSES)/renderperf.manifest
+	jar cvmf $(CLASSES)/renderperf.manifest $(DIST)/RenderPerfTest.jar -C $(CLASSES) .
+
+$(DIST)/RenderPerfLCDTest.jar: \
+	$(RENDERPERFLCD_CLASSES) $(RENDERPERFLCD_RESOURCES) \
+	$(CLASSES)/renderperflcd.manifest
+	jar cvmf $(CLASSES)/renderperflcd.manifest $(DIST)/RenderPerfLCDTest.jar -C $(CLASSES) .
+
+$(CLASSES)/renderperf/images/%: $(RESOURCES)/renderperf/images/%
+	cp -r $< $@
+
+
+$(CLASSES)/renderperf.manifest:
+	echo "Main-Class: renderperf.RenderPerfTest" > $@
+
+$(CLASSES)/renderperflcd.manifest:
+	echo "Main-Class: renderperf.RenderPerfLCDTest" > $@
+
+$(DIST):
+	mkdir  $(DIST)
+
+$(CLASSES):
+	mkdir $(CLASSES)
+	mkdir -p $(CLASSES)/renderperf/images
+
+mkdirs: $(DIST) $(CLASSES)
+
+$(RENDERPERF_CLASSES): $(RENDERPERF_SOURCES)
+	javac -g:none -d $(CLASSES) -sourcepath $(SOURCEPATH) $<
+
+$(RENDERPERFLCD_CLASSES): $(RENDERPERFLCD_SOURCES)
+	javac -g:none -d $(CLASSES) -sourcepath $(SOURCEPATH) $<
+
+clean:
+	rm -rf $(CLASSES)
+	rm -rf $(DIST)
diff --git a/test/jdk/performance/client/RenderPerfTest/README b/test/jdk/performance/client/RenderPerfTest/README
new file mode 100644
index 00000000000..7cdae172b1a
--- /dev/null
+++ b/test/jdk/performance/client/RenderPerfTest/README
@@ -0,0 +1,34 @@
+-----------------------------------------------------------------------
+Introduction
+-----------------------------------------------------------------------
+
+RenderPerfTest is a set of on-screen rendering microbenchmarks  to
+analyze the  performance of Java2D graphical primitives rendering
+
+-----------------------------------------------------------------------
+How To Compile
+-----------------------------------------------------------------------
+
+#> cd RenderPerfTest
+
+The benchmark can be compiled by using either ant:
+
+#> ant
+
+or gnumake (assuming there's 'javac' in the path):
+
+#> gnumake
+
+The jar files will be generated into RenderPerfTest/dist directory.
+
+-----------------------------------------------------------------------
+How To Run RenderPerfTest
+-----------------------------------------------------------------------
+Run all tests
+#> ant run
+  or
+#> java -jar dist/RenderPerfTest.jar
+
+Run particular test cases
+
+#> java -jar dist/RenderPerfTest.jar WhiteTextGray ...
\ No newline at end of file
diff --git a/test/jdk/performance/client/RenderPerfTest/build.xml b/test/jdk/performance/client/RenderPerfTest/build.xml
new file mode 100644
index 00000000000..0aebb7ca700
--- /dev/null
+++ b/test/jdk/performance/client/RenderPerfTest/build.xml
@@ -0,0 +1,86 @@
+<!--
+ Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+ This code is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License version 2 only, as
+ published by the Free Software Foundation.
+
+ This code is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ version 2 for more details (a copy is included in the LICENSE file that
+ accompanied this code).
+
+ You should have received a copy of the GNU General Public License version
+ 2 along with this work; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ or visit www.oracle.com if you need additional information or have any
+ questions.
+-->
+
+<project name="RenderPerfTest" default="dist" basedir=".">
+    <description>
+        simple example build file
+    </description>
+  <!-- set global properties for this build -->
+  <property name="src" location="src"/>
+  <property name="build" location="build"/>
+  <property name="dist"  location="dist"/>
+  <property name="resources"  location="resources"/>
+
+  <target name="init">
+    <!-- Create the time stamp -->
+    <tstamp/>
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${build}"/>
+  </target>
+
+  <target name="compile" depends="init"
+        description="compile the source " >
+    <!-- Compile the java code from ${src} into ${build} -->
+    <javac includeantruntime="false" debug="off" srcdir="${src}" destdir="${build}"/>
+  </target>
+
+  <target name="run" depends="dist"
+    description="run RenderPerfTest" >
+    <java jar="${dist}/RenderPerfTest.jar"
+       fork="true"
+    >
+    </java>
+  </target>
+
+  <target name="resources" depends="init"
+        description="copy resources into build dir" >
+    <!-- Copy the resource files from ${resources} into ${build}/ -->
+    <mkdir dir="${dist}"/>
+    <mkdir dir="${dist}/renderperf"/>
+    <mkdir dir="${build}/renderperf/images"/>
+    <copy todir="${build}/renderperf/images">
+      <fileset dir="resources/renderperf/images" />
+    </copy>
+  </target>
+
+  <target name="dist" depends="compile, resources"
+        description="generate the distribution" >
+    <!-- Create the distribution directory -->
+    <mkdir dir="${dist}"/>
+
+    <!-- Put everything in ${build} into the RenderPerfTest.jar file -->
+    <jar jarfile="${dist}/RenderPerfTest.jar" basedir="${build}">
+      <manifest>
+        <attribute name="Built-By" value="${user.name}"/>
+	<attribute name="Main-Class" value="renderperf.RenderPerfTest"/>
+      </manifest>
+    </jar>
+  </target>
+
+  <target name="clean"
+        description="clean up" >
+    <!-- Delete the ${build} and ${dist} directory trees -->
+    <delete dir="${build}"/>
+    <delete dir="${dist}"/>
+  </target>
+</project>
diff --git a/test/jdk/performance/client/RenderPerfTest/resources/renderperf/images/duke.png b/test/jdk/performance/client/RenderPerfTest/resources/renderperf/images/duke.png
new file mode 100644
index 00000000000..9a52fa05b41
Binary files /dev/null and b/test/jdk/performance/client/RenderPerfTest/resources/renderperf/images/duke.png differ
diff --git a/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfLCDTest.java b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfLCDTest.java
new file mode 100644
index 00000000000..abe5cc37b6e
--- /dev/null
+++ b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfLCDTest.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package renderperf;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Robot;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.QuadCurve2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.imageio.ImageIO;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+
+public class RenderPerfLCDTest {
+    private static HashSet<String> ignoredTests = new HashSet<>();
+
+    private final static int N = 1000;
+    private final static float WIDTH = 800;
+    private final static float HEIGHT = 800;
+    private final static float R = 25;
+    private final static int BW = 50;
+    private final static int BH = 50;
+    private final static int COUNT = 300;
+    private final static int DELAY = 10;
+    private final static int RESOLUTION = 5;
+    private final static int COLOR_TOLERANCE = 10;
+    private final static int MAX_MEASURE_TIME = 5000;
+
+
+    interface Configurable {
+        void configure(Graphics2D g2d);
+    }
+
+    interface Renderable {
+        void setup(Graphics2D g2d);
+        void render(Graphics2D g2d);
+        void update();
+    }
+
+    static class Particles {
+        private float[] bx;
+        private float[] by;
+        private float[] vx;
+        private float[] vy;
+        private float r;
+        private int n;
+
+        private float x0;
+        private float y0;
+        private float width;
+        private float height;
+
+        Particles(int n, float r, float x0, float y0, float width, float height) {
+            bx = new float[n];
+            by = new float[n];
+            vx = new float[n];
+            vy = new float[n];
+            this.n = n;
+            this.r = r;
+            this.x0 = x0;
+            this.y0 = y0;
+            this.width = width;
+            this.height = height;
+            for (int i = 0; i < n; i++) {
+                bx[i] = (float) (x0 + r + 0.1 + Math.random() * (width - 2 * r - 0.2 - x0));
+                by[i] = (float) (y0 + r + 0.1 + Math.random() * (height - 2 * r - 0.2 - y0));
+                vx[i] = 0.1f * (float) (Math.random() * 2 * r - r);
+                vy[i] = 0.1f * (float) (Math.random() * 2 * r - r);
+            }
+
+        }
+
+        void render(Graphics2D g2d, ParticleRenderer renderer) {
+            for (int i = 0; i < n; i++) {
+                renderer.render(g2d, i, bx, by, vx, vy);
+            }
+        }
+
+        void update() {
+            for (int i = 0; i < n; i++) {
+                bx[i] += vx[i];
+                if (bx[i] + r > width || bx[i] - r < x0) vx[i] = -vx[i];
+                by[i] += vy[i];
+                if (by[i] + r > height || by[i] - r < y0) vy[i] = -vy[i];
+            }
+
+        }
+
+    }
+
+    ParticleRenderable createPR(ParticleRenderer renderer) {
+        return new ParticleRenderable(renderer);
+    }
+
+    static class ParticleRenderable implements Renderable {
+        ParticleRenderer renderer;
+        Configurable configure;
+
+        ParticleRenderable(ParticleRenderer renderer, Configurable configure) {
+            this.renderer = renderer;
+            this.configure = configure;
+        }
+
+        ParticleRenderable(ParticleRenderer renderer) {
+            this(renderer, null);
+        }
+
+        @Override
+        public void setup(Graphics2D g2d) {
+            if (configure != null) configure.configure(g2d);
+        }
+
+        @Override
+        public void render(Graphics2D g2d) {
+            balls.render(g2d, renderer);
+        }
+
+        @Override
+        public void update() {
+            balls.update();
+        }
+
+        public ParticleRenderable configure(Configurable configure) {
+            this.configure = configure;
+            return this;
+        }
+    }
+
+    interface ParticleRenderer {
+        void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy);
+
+    }
+
+    static class WhiteTextParticleRenderer implements ParticleRenderer {
+        float r;
+
+        WhiteTextParticleRenderer(float r) {
+            this.r = r;
+        }
+
+        void setPaint(Graphics2D g2d, int id) {
+            g2d.setColor(Color.WHITE);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            setPaint(g2d, id);
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)(y[id] - r));
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)y[id]);
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)(y[id] + r));
+        }
+    }
+
+    static class TextParticleRenderer extends WhiteTextParticleRenderer {
+        Color[] colors;
+
+        float r;
+
+        TextParticleRenderer(int n, float r) {
+            super(r);
+            colors = new Color[n];
+            this.r = r;
+            for (int i = 0; i < n; i++) {
+                colors[i] = new Color((float) Math.random(),
+                        (float) Math.random(), (float) Math.random());
+            }
+        }
+
+        void setPaint(Graphics2D g2d, int id) {
+            g2d.setColor(colors[id % colors.length]);
+        }
+    }
+
+    static class PerfMeter {
+        private String name;
+        private int frame = 0;
+
+        private JPanel panel;
+
+        private long time;
+        private double execTime = 0;
+        private Color expColor = Color.RED;
+        AtomicBoolean waiting = new AtomicBoolean(false);
+        private double fps;
+
+        PerfMeter(String name) {
+            this.name = name;
+        }
+
+        PerfMeter exec(final Renderable renderable) throws Exception {
+            final CountDownLatch latch = new CountDownLatch(COUNT);
+            final CountDownLatch latchFrame = new CountDownLatch(1);
+            final long endTime = System.currentTimeMillis() + MAX_MEASURE_TIME;
+
+            final Frame f = new Frame();
+            f.addWindowListener(new WindowAdapter() {
+                @Override
+                public void windowClosed(WindowEvent e) {
+                    latchFrame.countDown();
+                }
+            });
+
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+
+                    panel = new JPanel()
+                    {
+                        @Override
+                        protected void paintComponent(Graphics g) {
+
+                            super.paintComponent(g);
+                            time = System.nanoTime();
+                            Graphics2D g2d = (Graphics2D) g.create();
+                            renderable.setup(g2d);
+                            renderable.render(g2d);
+                            g2d.setColor(expColor);
+                            g.fillRect(0, 0, BW, BH);
+                        }
+                    };
+
+                    panel.setPreferredSize(new Dimension((int)(WIDTH + BW), (int)(HEIGHT + BH)));
+                    panel.setBackground(Color.BLACK);
+                    f.add(panel);
+                    //f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+                    f.pack();
+                    f.setVisible(true);
+                }
+            });
+            Robot robot = new Robot();
+
+            Timer timer = new Timer(DELAY, e -> {
+
+                if (waiting.compareAndSet(false, true)) {
+                    Color c = robot.getPixelColor(
+                            panel.getTopLevelAncestor().getX() + panel.getTopLevelAncestor().getInsets().left + BW / 2,
+                            panel.getTopLevelAncestor().getY() + panel.getTopLevelAncestor().getInsets().top + BW / 2);
+                    if (isAlmostEqual(c, Color.BLUE)) {
+                        expColor = Color.RED;
+                    } else {
+                        expColor = Color.BLUE;
+                    }
+                    renderable.update();
+                    panel.getParent().repaint();
+
+                } else {
+                    while (!isAlmostEqual(
+                            robot.getPixelColor(
+                                    panel.getTopLevelAncestor().getX() + panel.getTopLevelAncestor().getInsets().left + BW/2,
+                                    panel.getTopLevelAncestor().getY() + panel.getTopLevelAncestor().getInsets().top + BH/2),
+                            expColor))
+                    {
+                        try {
+                            Thread.sleep(RESOLUTION);
+                        } catch (InterruptedException ex) {
+                            ex.printStackTrace();
+                        }
+                    }
+                    time = System.nanoTime() - time;
+                    execTime += time;
+                    frame++;
+                    waiting.set(false);
+                }
+
+                if (System.currentTimeMillis() < endTime) {
+                    latch.countDown();
+                } else {
+                    while(latch.getCount() > 0) latch.countDown();
+                }
+            });
+            timer.start();
+            latch.await();
+            SwingUtilities.invokeAndWait(() -> {
+                timer.stop();
+                f.setVisible(false);
+                f.dispose();
+            });
+
+            latchFrame.await();
+            if (execTime != 0 && frame != 0) {
+                fps = 1e9 / (execTime / frame);
+            } else {
+                fps = 0;
+            }
+
+            return this;
+        }
+
+        private void report() {
+            System.err.println(name + " : " + String.format("%.2f FPS", fps));
+        }
+
+        private boolean isAlmostEqual(Color c1, Color c2) {
+            return Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE ||
+                    Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE ||
+                    Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE;
+
+        }
+    }
+
+    private static final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT);
+    private static final ParticleRenderer textRenderer = new TextParticleRenderer(N, R);
+
+    private static final Configurable TextLCD = (Graphics2D g2d) ->
+        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
+
+    public void testTextBubblesLCD() throws Exception {
+        (new PerfMeter("TextLCD")).exec(createPR(textRenderer).configure(TextLCD)).report();
+    }
+
+    public static void main(String[] args)
+            throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
+    {
+        RenderPerfLCDTest test = new RenderPerfLCDTest();
+
+        if (args.length > 0) {
+            for (String testCase : args) {
+                Method m = RenderPerfLCDTest.class.getDeclaredMethod(testCase);
+                m.invoke(test);
+            }
+        } else {
+            Method[] methods = RenderPerfLCDTest.class.getDeclaredMethods();
+            for (Method m : methods) {
+                if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) {
+                    m.invoke(test);
+                }
+            }
+        }
+    }
+}
diff --git a/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java
new file mode 100644
index 00000000000..3805c8d5914
--- /dev/null
+++ b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java
@@ -0,0 +1,986 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package renderperf;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.LinearGradientPaint;
+import java.awt.RadialGradientPaint;
+import java.awt.RenderingHints;
+import java.awt.Robot;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.QuadCurve2D;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferShort;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.imageio.ImageIO;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.WindowConstants;
+
+public class RenderPerfTest {
+    private static HashSet<String> ignoredTests = new HashSet<>();
+
+    static {
+        ignoredTests.add("testWiredBoxAA");
+    }
+
+    private final static int N = 1000;
+    private final static float WIDTH = 800;
+    private final static float HEIGHT = 800;
+    private final static float R = 25;
+    private final static int BW = 50;
+    private final static int BH = 50;
+    private final static int COUNT = 300;
+    private final static int DELAY = 10;
+    private final static int RESOLUTION = 5;
+    private final static int COLOR_TOLERANCE = 10;
+    private final static int MAX_MEASURE_TIME = 5000;
+
+
+    interface Configurable {
+        void configure(Graphics2D g2d);
+    }
+
+    interface Renderable {
+        void setup(Graphics2D g2d);
+        void render(Graphics2D g2d);
+        void update();
+    }
+
+    static class Particles {
+        private float[] bx;
+        private float[] by;
+        private float[] vx;
+        private float[] vy;
+        private float r;
+        private int n;
+
+        private float x0;
+        private float y0;
+        private float width;
+        private float height;
+
+        Particles(int n, float r, float x0, float y0, float width, float height) {
+            bx = new float[n];
+            by = new float[n];
+            vx = new float[n];
+            vy = new float[n];
+            this.n = n;
+            this.r = r;
+            this.x0 = x0;
+            this.y0 = y0;
+            this.width = width;
+            this.height = height;
+            for (int i = 0; i < n; i++) {
+                bx[i] = (float) (x0 + r + 0.1 + Math.random() * (width - 2 * r - 0.2 - x0));
+                by[i] = (float) (y0 + r + 0.1 + Math.random() * (height - 2 * r - 0.2 - y0));
+                vx[i] = 0.1f * (float) (Math.random() * 2 * r - r);
+                vy[i] = 0.1f * (float) (Math.random() * 2 * r - r);
+            }
+
+        }
+
+        void render(Graphics2D g2d, ParticleRenderer renderer) {
+            for (int i = 0; i < n; i++) {
+                renderer.render(g2d, i, bx, by, vx, vy);
+            }
+        }
+
+        void update() {
+            for (int i = 0; i < n; i++) {
+                bx[i] += vx[i];
+                if (bx[i] + r > width || bx[i] - r < x0) vx[i] = -vx[i];
+                by[i] += vy[i];
+                if (by[i] + r > height || by[i] - r < y0) vy[i] = -vy[i];
+            }
+
+        }
+
+    }
+
+    ParticleRenderable createPR(ParticleRenderer renderer) {
+        return new ParticleRenderable(renderer);
+    }
+
+    static class ParticleRenderable implements Renderable {
+        ParticleRenderer renderer;
+        Configurable configure;
+
+        ParticleRenderable(ParticleRenderer renderer, Configurable configure) {
+            this.renderer = renderer;
+            this.configure = configure;
+        }
+
+        ParticleRenderable(ParticleRenderer renderer) {
+            this(renderer, null);
+        }
+
+        @Override
+        public void setup(Graphics2D g2d) {
+            if (configure != null) configure.configure(g2d);
+        }
+
+        @Override
+        public void render(Graphics2D g2d) {
+            balls.render(g2d, renderer);
+        }
+
+        @Override
+        public void update() {
+            balls.update();
+        }
+
+        public ParticleRenderable configure(Configurable configure) {
+            this.configure = configure;
+            return this;
+        }
+    }
+
+    interface ParticleRenderer {
+        void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy);
+
+    }
+
+    static class FlatParticleRenderer implements ParticleRenderer {
+        Color[] colors;
+        float r;
+
+        FlatParticleRenderer(int n, float r) {
+            colors = new Color[n];
+            this.r = r;
+            for (int i = 0; i < n; i++) {
+                colors[i] = new Color((float) Math.random(),
+                        (float) Math.random(), (float) Math.random());
+            }
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            g2d.setColor(colors[id % colors.length]);
+            g2d.fillOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
+        }
+
+    }
+
+    static class ClipFlatParticleRenderer extends FlatParticleRenderer {
+
+        ClipFlatParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            if ((id % 10) == 0) {
+                g2d.setColor(colors[id % colors.length]);
+                g2d.setClip(new Ellipse2D.Double((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)));
+                g2d.fillRect((int) (x[id] - 2 * r), (int) (y[id] - 2 * r), (int) (4 * r), (int) (4 * r));
+            }
+        }
+
+    }
+    static class WhiteTextParticleRenderer implements ParticleRenderer {
+        float r;
+
+        WhiteTextParticleRenderer(float r) {
+            this.r = r;
+        }
+
+        void setPaint(Graphics2D g2d, int id) {
+            g2d.setColor(Color.WHITE);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            setPaint(g2d, id);
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)(y[id] - r));
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)y[id]);
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)(y[id] + r));
+        }
+    }
+
+    static class TextParticleRenderer extends WhiteTextParticleRenderer {
+        Color[] colors;
+
+        float r;
+
+        TextParticleRenderer(int n, float r) {
+            super(r);
+            colors = new Color[n];
+            this.r = r;
+            for (int i = 0; i < n; i++) {
+                colors[i] = new Color((float) Math.random(),
+                        (float) Math.random(), (float) Math.random());
+            }
+        }
+
+        void setPaint(Graphics2D g2d, int id) {
+            g2d.setColor(colors[id % colors.length]);
+        }
+    }
+
+    static class LargeTextParticleRenderer extends TextParticleRenderer {
+
+        LargeTextParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            setPaint(g2d, id);
+            Font font = new Font("LucidaGrande", Font.PLAIN, 32);
+            g2d.setFont(font);
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)(y[id] - r));
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)y[id]);
+            g2d.drawString("The quick brown fox jumps over the lazy dog",
+                    (int)(x[id] - r), (int)(y[id] + r));
+        }
+    }
+
+    static class FlatOvalRotParticleRenderer extends FlatParticleRenderer {
+
+
+        FlatOvalRotParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        void setPaint(Graphics2D g2d, int id) {
+            g2d.setColor(colors[id % colors.length]);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            setPaint(g2d, id);
+            if (Math.abs(vx[id] + vy[id]) > 0.001) {
+                AffineTransform t = (AffineTransform) g2d.getTransform().clone();
+                double l = vx[id] / Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]);
+                if (vy[id] < 0) {
+                    l = -l;
+                }
+                g2d.translate(x[id], y[id]);
+                g2d.rotate(Math.acos(l));
+                g2d.fillOval(-(int)r, (int)(-0.5*r), (int) (2 * r), (int)r);
+                g2d.setTransform(t);
+            } else {
+                g2d.fillOval((int)(x[id] - r), (int)(y[id] - 0.5*r),
+                        (int) (2 * r), (int) r);
+            }
+        }
+    }
+
+    static class LinGradOvalRotParticleRenderer extends FlatOvalRotParticleRenderer {
+
+
+        LinGradOvalRotParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        void setPaint(Graphics2D g2d, int id) {
+            Point2D start = new Point2D.Double(- r,  - 0.5*r);
+            Point2D end = new Point2D.Double( 2 * r, r);
+            float[] dist = {0.0f, 1.0f};
+            Color[] cls = {colors[id %colors.length], colors[(colors.length - id) %colors.length]};
+            LinearGradientPaint p =
+                    new LinearGradientPaint(start, end, dist, cls);
+            g2d.setPaint(p);
+        }
+    }
+
+    static class LinGrad3OvalRotParticleRenderer extends FlatOvalRotParticleRenderer {
+
+
+        LinGrad3OvalRotParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        void setPaint(Graphics2D g2d, int id) {
+            Point2D start = new Point2D.Double(- r,  - 0.5*r);
+            Point2D end = new Point2D.Double( 2 * r, r);
+            float[] dist = {0.0f, 0.5f, 1.0f};
+            Color[] cls = {
+                colors[id %colors.length],
+                colors[(colors.length - id) %colors.length],
+                colors[(id*5) %colors.length]};
+            LinearGradientPaint p =
+                new LinearGradientPaint(start, end, dist, cls);
+            g2d.setPaint(p);
+        }
+    }
+
+    static class RadGrad3OvalRotParticleRenderer extends FlatOvalRotParticleRenderer {
+
+
+        RadGrad3OvalRotParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        void setPaint(Graphics2D g2d, int id) {
+            Point2D start = new Point2D.Double();
+            float[] dist = {0.0f, 0.5f, 1.0f};
+            Color[] cls = {
+                colors[id %colors.length],
+                colors[(colors.length - id) %colors.length],
+                colors[(id*5) %colors.length]};
+            RadialGradientPaint p =
+                new RadialGradientPaint(start, r, dist, cls);
+            g2d.setPaint(p);
+        }
+    }
+
+    static class FlatBoxParticleRenderer extends FlatParticleRenderer {
+
+
+        FlatBoxParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            g2d.setColor(colors[id % colors.length]);
+            g2d.fillRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
+
+        }
+
+    }
+
+    static class ClipFlatBoxParticleRenderer extends FlatParticleRenderer {
+
+
+        ClipFlatBoxParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            if ((id % 10) == 0) {
+                g2d.setColor(colors[id % colors.length]);
+                g2d.setClip((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r));
+                g2d.fillRect((int) (x[id] - 2 * r), (int) (y[id] - 2 * r), (int) (4 * r), (int) (4 * r));
+            }
+        }
+    }
+
+    static class ImgParticleRenderer extends FlatParticleRenderer {
+        BufferedImage dukeImg;
+
+        ImgParticleRenderer(int n, float r) {
+            super(n, r);
+            try {
+                dukeImg = ImageIO.read(
+                        Objects.requireNonNull(
+                                RenderPerfTest.class.getClassLoader().getResourceAsStream(
+                                        "renderperf/images/duke.png")));
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            g2d.setColor(colors[id % colors.length]);
+            g2d.drawImage(dukeImg, (int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r), null);
+        }
+
+    }
+
+    static class FlatBoxRotParticleRenderer extends FlatParticleRenderer {
+
+
+        FlatBoxRotParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            g2d.setColor(colors[id % colors.length]);
+            if (Math.abs(vx[id] + vy[id]) > 0.001) {
+                AffineTransform t = (AffineTransform) g2d.getTransform().clone();
+                double l = vx[id] / Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]);
+                if (vy[id] < 0) {
+                    l = -l;
+                }
+                g2d.translate(x[id], y[id]);
+                g2d.rotate(Math.acos(l));
+                g2d.fillRect(-(int)r, -(int)r, (int) (2 * r), (int) (2 * r));
+                g2d.setTransform(t);
+            } else {
+                g2d.fillRect((int)(x[id] - r), (int)(y[id] - r),
+                        (int) (2 * r), (int) (2 * r));
+            }
+        }
+    }
+
+    static class WiredParticleRenderer extends FlatParticleRenderer {
+
+
+        WiredParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            g2d.setColor(colors[id % colors.length]);
+            g2d.drawOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
+        }
+
+    }
+    static class WiredBoxParticleRenderer extends FlatParticleRenderer {
+
+        WiredBoxParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            g2d.setColor(colors[id % colors.length]);
+            g2d.drawRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
+        }
+
+    }
+    static class SegParticleRenderer extends FlatParticleRenderer {
+
+        SegParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            double v = Math.sqrt(vx[id]*vx[id]+vy[id]*vy[id]);
+            float nvx = (float) (vx[id]/v);
+            float nvy = (float) (vy[id]/v);
+            g2d.setColor(colors[id % colors.length]);
+            g2d.drawLine((int)(x[id] - r*nvx), (int)(y[id] - r*nvy),
+                    (int)(x[id] + 2*r*nvx), (int)(y[id] + 2*r*nvy));
+        }
+
+    }
+
+
+    static class WiredQuadParticleRenderer extends FlatParticleRenderer {
+
+        WiredQuadParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            if (id > 2 && (id % 3) == 0) {
+                g2d.setColor(colors[id % colors.length]);
+                g2d.draw(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1]));
+            }
+
+        }
+    }
+
+    static class FlatQuadParticleRenderer extends FlatParticleRenderer {
+
+        FlatQuadParticleRenderer(int n, float r) {
+            super(n, r);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            if (id > 2 && (id % 3) == 0) {
+                g2d.setColor(colors[id % colors.length]);
+                g2d.fill(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1]));
+            }
+
+        }
+    }
+
+    static class BlitImageParticleRenderer extends FlatParticleRenderer {
+        BufferedImage image;
+
+        BlitImageParticleRenderer(int n, float r, BufferedImage img) {
+            super(n, r);
+            image = img;
+            fill(image);
+        }
+
+        @Override
+        public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
+            g2d.drawImage(image, (int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r), null);
+        }
+
+        private static void fill(final Image image) {
+            final Graphics2D graphics = (Graphics2D) image.getGraphics();
+            graphics.setComposite(AlphaComposite.Src);
+            for (int i = 0; i < image.getHeight(null); ++i) {
+                graphics.setColor(new Color(i, 0, 0));
+                graphics.fillRect(0, i, image.getWidth(null), 1);
+            }
+            graphics.dispose();
+        }
+
+    }
+
+    static class SwBlitImageParticleRenderer extends BlitImageParticleRenderer {
+
+        SwBlitImageParticleRenderer(int n, float r, final int type) {
+            super(n, r, makeUnmanagedBI(type));
+        }
+
+        private static BufferedImage makeUnmanagedBI(final int type) {
+            final BufferedImage bi = new BufferedImage(17, 33, type);
+            final DataBuffer db = bi.getRaster().getDataBuffer();
+            if (db instanceof DataBufferInt) {
+                ((DataBufferInt) db).getData();
+            } else if (db instanceof DataBufferShort) {
+                ((DataBufferShort) db).getData();
+            } else if (db instanceof DataBufferByte) {
+                ((DataBufferByte) db).getData();
+            }
+            bi.setAccelerationPriority(0.0f);
+            return bi;
+        }
+    }
+
+    static class SurfaceBlitImageParticleRenderer extends BlitImageParticleRenderer {
+
+        SurfaceBlitImageParticleRenderer(int n, float r, final int type) {
+            super(n, r, makeManagedBI(type));
+        }
+
+        private static BufferedImage makeManagedBI(final int type) {
+            final BufferedImage bi = new BufferedImage(17, 33, type);
+            bi.setAccelerationPriority(1.0f);
+            return bi;
+        }
+    }
+
+    static class PerfMeter {
+        private String name;
+        private int frame = 0;
+
+        private JPanel panel;
+
+        private long time;
+        private double execTime = 0;
+        private Color expColor = Color.RED;
+        AtomicBoolean waiting = new AtomicBoolean(false);
+        private double fps;
+
+        PerfMeter(String name) {
+            this.name = name;
+        }
+
+        PerfMeter exec(final Renderable renderable) throws Exception {
+            final CountDownLatch latch = new CountDownLatch(COUNT);
+            final CountDownLatch latchFrame = new CountDownLatch(1);
+            final long endTime = System.currentTimeMillis() + MAX_MEASURE_TIME;
+
+            final JFrame f = new JFrame();
+            f.addWindowListener(new WindowAdapter() {
+                @Override
+                public void windowClosed(WindowEvent e) {
+                    latchFrame.countDown();
+                }
+            });
+
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+
+                    panel = new JPanel()
+                    {
+                        @Override
+                        protected void paintComponent(Graphics g) {
+
+                            super.paintComponent(g);
+                            time = System.nanoTime();
+                            Graphics2D g2d = (Graphics2D) g.create();
+                            renderable.setup(g2d);
+                            renderable.render(g2d);
+                            g2d.setColor(expColor);
+                            g.fillRect(0, 0, BW, BH);
+                        }
+                    };
+
+                    panel.setPreferredSize(new Dimension((int)(WIDTH + BW), (int)(HEIGHT + BH)));
+                    panel.setBackground(Color.BLACK);
+                    f.add(panel);
+                    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+                    f.pack();
+                    f.setVisible(true);
+                }
+            });
+            Robot robot = new Robot();
+
+            Timer timer = new Timer(DELAY, e -> {
+
+                if (waiting.compareAndSet(false, true)) {
+                    Color c = robot.getPixelColor(
+                            panel.getTopLevelAncestor().getX() + panel.getTopLevelAncestor().getInsets().left + BW / 2,
+                            panel.getTopLevelAncestor().getY() + panel.getTopLevelAncestor().getInsets().top + BW / 2);
+                    if (isAlmostEqual(c, Color.BLUE)) {
+                        expColor = Color.RED;
+                    } else {
+                        expColor = Color.BLUE;
+                    }
+                    renderable.update();
+                    panel.getParent().repaint();
+
+                } else {
+                    while (!isAlmostEqual(
+                            robot.getPixelColor(
+                                    panel.getTopLevelAncestor().getX() + panel.getTopLevelAncestor().getInsets().left + BW/2,
+                                    panel.getTopLevelAncestor().getY() + panel.getTopLevelAncestor().getInsets().top + BH/2),
+                            expColor))
+                    {
+                        try {
+                            Thread.sleep(RESOLUTION);
+                        } catch (InterruptedException ex) {
+                            ex.printStackTrace();
+                        }
+                    }
+                    time = System.nanoTime() - time;
+                    execTime += time;
+                    frame++;
+                    waiting.set(false);
+                }
+
+                if (System.currentTimeMillis() < endTime) {
+                    latch.countDown();
+                } else {
+                    while(latch.getCount() > 0) latch.countDown();
+                }
+            });
+            timer.start();
+            latch.await();
+            SwingUtilities.invokeAndWait(() -> {
+                timer.stop();
+                f.setVisible(false);
+                f.dispose();
+            });
+
+            latchFrame.await();
+            if (execTime != 0 && frame != 0) {
+                fps = 1e9 / (execTime / frame);
+            } else {
+                fps = 0;
+            }
+
+            return this;
+        }
+
+        private void report() {
+            System.err.println(name + " : " + String.format("%.2f FPS", fps));
+        }
+
+        private boolean isAlmostEqual(Color c1, Color c2) {
+            return Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE ||
+                    Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE ||
+                    Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE;
+
+        }
+    }
+
+    private static final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT);
+    private static final ParticleRenderer flatRenderer = new FlatParticleRenderer(N, R);
+    private static final ParticleRenderer clipFlatRenderer = new ClipFlatParticleRenderer(N, R);
+    private static final ParticleRenderer flatOvalRotRenderer = new FlatOvalRotParticleRenderer(N, R);
+    private static final ParticleRenderer flatBoxRenderer = new FlatBoxParticleRenderer(N, R);
+    private static final ParticleRenderer clipFlatBoxParticleRenderer = new ClipFlatBoxParticleRenderer(N, R);
+    private static final ParticleRenderer flatBoxRotRenderer = new FlatBoxRotParticleRenderer(N, R);
+    private static final ParticleRenderer linGradOvalRotRenderer = new LinGradOvalRotParticleRenderer(N, R);
+    private static final ParticleRenderer linGrad3OvalRotRenderer = new LinGrad3OvalRotParticleRenderer(N, R);
+    private static final ParticleRenderer radGrad3OvalRotRenderer = new RadGrad3OvalRotParticleRenderer(N, R);
+    private static final ParticleRenderer wiredRenderer = new WiredParticleRenderer(N, R);
+    private static final ParticleRenderer wiredBoxRenderer = new WiredBoxParticleRenderer(N, R);
+    private static final ParticleRenderer segRenderer = new SegParticleRenderer(N, R);
+    private static final ParticleRenderer flatQuadRenderer = new FlatQuadParticleRenderer(N, R);
+    private static final ParticleRenderer wiredQuadRenderer = new WiredQuadParticleRenderer(N, R);
+    private static final ParticleRenderer imgRenderer = new ImgParticleRenderer(N, R);
+    private static final ParticleRenderer textRenderer = new TextParticleRenderer(N, R);
+    private static final ParticleRenderer largeTextRenderer = new LargeTextParticleRenderer(N, R);
+    private static final ParticleRenderer whiteTextRenderer = new WhiteTextParticleRenderer(R);
+    private static final ParticleRenderer argbSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB);
+    private static final ParticleRenderer bgrSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR);
+    private static final ParticleRenderer argbSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB);
+    private static final ParticleRenderer bgrSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR);
+
+    private static final Configurable AA = (Graphics2D g2d) ->
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+            RenderingHints.VALUE_ANTIALIAS_ON);
+
+    private static final Configurable TextLCD = (Graphics2D g2d) ->
+        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
+
+    private static final Configurable TextAA = (Graphics2D g2d) ->
+        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+    private static final Configurable XORMode = (Graphics2D g2d) ->
+        {g2d.setXORMode(Color.WHITE);};
+
+    private static final Configurable XORModeLCDText = (Graphics2D g2d) ->
+        {g2d.setXORMode(Color.WHITE);
+         g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+         RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);};
+
+
+    public void testFlatOval() throws Exception {
+        (new PerfMeter("FlatOval")).exec(createPR(flatRenderer)).report();
+    }
+
+    public void testFlatOvalAA() throws Exception {
+        (new PerfMeter("FlatOvalAA")).exec(createPR(flatRenderer).configure(AA)).report();
+    }
+
+    public void testClipFlatOval() throws Exception {
+        (new PerfMeter("ClipFlatOval")).exec(createPR(clipFlatRenderer)).report();
+    }
+
+    public void testClipFlatOvalAA() throws Exception {
+        (new PerfMeter("ClipFlatOvalAA")).exec(createPR(clipFlatRenderer).configure(AA)).report();
+    }
+
+    public void testFlatBox() throws Exception {
+        (new PerfMeter("FlatBox")).exec(createPR(flatBoxRenderer)).report();
+    }
+
+    public void testFlatBoxAA() throws Exception {
+        (new PerfMeter("FlatBoxAA")).exec(createPR(flatBoxRenderer).configure(AA)).report();
+    }
+
+    public void testClipFlatBox() throws Exception {
+        (new PerfMeter("ClipFlatBox")).exec(createPR(clipFlatBoxParticleRenderer)).report();
+    }
+
+    public void testClipFlatBoxAA() throws Exception {
+        (new PerfMeter("ClipFlatBoxAA")).exec(createPR(clipFlatBoxParticleRenderer).configure(AA)).report();
+    }
+
+    public void testImage() throws Exception {
+        (new PerfMeter("Image")).exec(createPR(imgRenderer)).report();
+    }
+
+    public void testImageAA() throws Exception {
+        (new PerfMeter("ImageAA")).exec(createPR(imgRenderer).configure(AA)).report();
+    }
+
+    public void testRotatedBox() throws Exception {
+        (new PerfMeter("RotatedBox")).exec(createPR(flatBoxRotRenderer)).report();
+    }
+
+    public void testRotatedBoxAA() throws Exception {
+        (new PerfMeter("RotatedBoxAA")).exec(createPR(flatBoxRotRenderer).configure(AA)).report();
+    }
+
+    public void testRotatedOval() throws Exception {
+        (new PerfMeter("RotatedOval")).exec(createPR(flatOvalRotRenderer)).report();
+    }
+
+    public void testRotatedOvalAA() throws Exception {
+        (new PerfMeter("RotatedOvalAA")).exec(createPR(flatOvalRotRenderer).configure(AA)).report();
+    }
+
+    public void testLinGrad3RotatedOval() throws Exception {
+        (new PerfMeter("LinGrad3RotatedOval")).exec(createPR(linGrad3OvalRotRenderer)).report();
+    }
+
+    public void testLinGrad3RotatedOvalAA() throws Exception {
+        (new PerfMeter("LinGrad3RotatedOvalAA")).exec(createPR(linGrad3OvalRotRenderer).configure(AA)).report();
+    }
+
+    public void testRadGrad3RotatedOval() throws Exception {
+        (new PerfMeter("RadGrad3RotatedOval")).exec(createPR(radGrad3OvalRotRenderer)).report();
+    }
+
+    public void testRadGrad3RotatedOvalAA() throws Exception {
+        (new PerfMeter("RadGrad3RotatedOvalAA")).exec(createPR(radGrad3OvalRotRenderer).configure(AA)).report();
+    }
+
+    public void testLinGradRotatedOval() throws Exception {
+        (new PerfMeter("LinGradRotatedOval")).exec(createPR(linGradOvalRotRenderer)).report();
+    }
+
+    public void testLinGradRotatedOvalAA() throws Exception {
+        (new PerfMeter("LinGradRotatedOvalAA")).exec(createPR(linGradOvalRotRenderer).configure(AA)).report();
+    }
+
+    public void testWiredBubbles() throws Exception {
+        (new PerfMeter("WiredBubbles")).exec(createPR(wiredRenderer)).report();
+    }
+
+    public void testWiredBubblesAA() throws Exception {
+        (new PerfMeter("WiredBubblesAA")).exec(createPR(wiredRenderer).configure(AA)).report();
+    }
+
+    public void testWiredBox() throws Exception {
+        (new PerfMeter("WiredBox")).exec(createPR(wiredBoxRenderer)).report();
+    }
+
+    public void testWiredBoxAA() throws Exception {
+        (new PerfMeter("WiredBoxAA")).exec(createPR(wiredBoxRenderer).configure(AA)).report();
+    }
+
+    public void testLines() throws Exception {
+        (new PerfMeter("Lines")).exec(createPR(segRenderer)).report();
+    }
+
+    public void testLinesAA() throws Exception {
+        (new PerfMeter("LinesAA")).exec(createPR(segRenderer).configure(AA)).report();
+    }
+
+    public void testFlatQuad() throws Exception {
+        (new PerfMeter("FlatQuad")).exec(createPR(flatQuadRenderer)).report();
+    }
+
+    public void testFlatQuadAA() throws Exception {
+        (new PerfMeter("FlatQuadAA")).exec(createPR(flatQuadRenderer).configure(AA)).report();
+    }
+
+    public void testWiredQuad() throws Exception {
+        (new PerfMeter("WiredQuad")).exec(createPR(wiredQuadRenderer)).report();
+    }
+
+    public void testWiredQuadAA() throws Exception {
+        (new PerfMeter("WiredQuadAA")).exec(createPR(wiredQuadRenderer).configure(AA)).report();
+    }
+
+    public void testTextNoAA() throws Exception {
+        (new PerfMeter("TextNoAA")).exec(createPR(textRenderer)).report();
+    }
+
+    public void testTextLCD() throws Exception {
+        (new PerfMeter("TextLCD")).exec(createPR(textRenderer).configure(TextLCD)).report();
+    }
+
+    public void testTextGray() throws Exception {
+        (new PerfMeter("TextGray")).exec(createPR(textRenderer).configure(TextAA)).report();
+    }
+
+    public void testLargeTextNoAA() throws Exception {
+        (new PerfMeter("LargeTextNoAA")).exec(createPR(largeTextRenderer)).report();
+    }
+
+    public void testLargeTextLCD() throws Exception {
+        (new PerfMeter("LargeTextLCD")).exec(createPR(largeTextRenderer).configure(TextLCD)).report();
+    }
+
+    public void testLargeTextGray() throws Exception {
+        (new PerfMeter("LargeTextGray")).exec(createPR(largeTextRenderer).configure(TextAA)).report();
+    }
+    public void testWhiteTextNoAA() throws Exception {
+        (new PerfMeter("WhiteTextNoAA")).exec(createPR(whiteTextRenderer)).report();
+    }
+
+    public void testWhiteTextLCD() throws Exception {
+        (new PerfMeter("WhiteTextLCD")).exec(createPR(whiteTextRenderer).configure(TextLCD)).report();
+    }
+
+    public void testWhiteTextGray() throws Exception {
+        (new PerfMeter("WhiteTextGray")).exec(createPR(whiteTextRenderer).configure(TextAA)).report();
+    }
+
+    public void testArgbSwBlitImage() throws Exception {
+        (new PerfMeter("ArgbSwBlitImage")).exec(createPR(argbSwBlitImageRenderer)).report();
+    }
+
+    public void testBgrSwBlitImage() throws Exception {
+        (new PerfMeter("BgrSwBlitImage")).exec(createPR(bgrSwBlitImageRenderer)).report();
+    }
+
+    public void testArgbSurfaceBlitImage() throws Exception {
+        (new PerfMeter("ArgbSurfaceBlitImageRenderer")).exec(createPR(argbSurfaceBlitImageRenderer)).report();
+    }
+
+    public void testBgrSurfaceBlitImage() throws Exception {
+        (new PerfMeter("BgrSurfaceBlitImage")).exec(createPR(bgrSurfaceBlitImageRenderer)).report();
+    }
+
+    public void testFlatOval_XOR() throws Exception {
+        (new PerfMeter("FlatOval_XOR")).exec(createPR(flatRenderer).configure(XORMode)).report();
+    }
+
+    public void testRotatedBox_XOR() throws Exception {
+        (new PerfMeter("RotatedBox_XOR")).exec(createPR(flatBoxRotRenderer).configure(XORMode)).report();
+    }
+
+    public void testLines_XOR() throws Exception {
+        (new PerfMeter("Lines_XOR")).exec(createPR(segRenderer).configure(XORMode)).report();
+    }
+
+    public void testImage_XOR() throws Exception {
+        (new PerfMeter("Image_XOR")).exec(createPR(imgRenderer).configure(XORMode)).report();
+    }
+
+    public void testTextNoAA_XOR() throws Exception {
+        (new PerfMeter("TextNoAA_XOR")).exec(createPR(textRenderer).configure(XORMode)).report();
+    }
+
+    public void testTextLCD_XOR() throws Exception {
+        (new PerfMeter("TextLCD_XOR")).exec(createPR(textRenderer).configure(XORModeLCDText)).report();
+    }
+
+    public static void main(String[] args)
+            throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
+    {
+        RenderPerfTest test = new RenderPerfTest();
+
+        if (args.length > 0) {
+            for (String testCase : args) {
+                Method m = RenderPerfTest.class.getDeclaredMethod("test" + testCase);
+                m.invoke(test);
+            }
+        } else {
+            Method[] methods = RenderPerfTest.class.getDeclaredMethods();
+            for (Method m : methods) {
+                if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) {
+                    m.invoke(test);
+                }
+            }
+        }
+    }
+}