diff a/src/java.desktop/macosx/native/libawt_lwawt/awt/common.h b/src/java.desktop/macosx/native/libawt_lwawt/awt/common.h
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/common.h
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/common.h
@@ -63,13 +63,19 @@
 struct TxtVertex {
     float position[2];
     float txtpos[2];
 };
 
+#define INTERPOLATION_NEAREST_NEIGHBOR 0
+#define INTERPOLATION_BILINEAR 1
+// 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;
+    int mode; // NOTE: consider to use bit fields
     int isSrcOpaque;
     int isDstOpaque;
     float extraAlpha;
 };
 
diff a/src/java.desktop/macosx/native/libawt_lwawt/awt/shaders.metal b/src/java.desktop/macosx/native/libawt_lwawt/awt/shaders.metal
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/shaders.metal
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/shaders.metal
@@ -127,21 +127,24 @@
 
 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)]]
-        )
-{
-    constexpr sampler textureSampler (mag_filter::linear,
-                                  min_filter::linear);
+        constant TxtFrameUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]]
+) {
     float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
     float srcA = uniforms.isSrcOpaque ? 1 : pixelColor.a;
-    // TODO: consider to make shaders without IF-conditions
     if (uniforms.mode) {
         float4 c = mix(pixelColor, uniforms.color, srcA);
         return half4(c.r, c.g, c.b , c.a);
     }
 
@@ -150,16 +153,13 @@
                  pixelColor.b, srcA*uniforms.extraAlpha);
 }
 
 fragment half4 frag_txt_tp(TxtShaderInOut vert [[stage_in]],
                        texture2d<float, access::sample> renderTexture [[texture(0)]],
-                       texture2d<float, access::sample> paintTexture [[texture(1)]])
-{
-    constexpr sampler textureSampler (address::repeat,
-      mag_filter::nearest,
-      min_filter::nearest);
-
+                       texture2d<float, access::sample> paintTexture [[texture(1)]],
+                       sampler textureSampler [[sampler(0)]]
+) {
     float4 renderColor = renderTexture.sample(textureSampler, vert.texCoords);
     float4 paintColor = paintTexture.sample(textureSampler, vert.tpCoords);
     return half4(paintColor.r*renderColor.a,
                  paintColor.g*renderColor.a,
                  paintColor.b*renderColor.a,
@@ -185,14 +185,13 @@
 }
 
 fragment half4 aa_frag_txt(
         TxtShaderInOut vert [[stage_in]],
         texture2d<float, access::sample> renderTexture [[texture(0)]],
-        constant TxtFrameUniforms& uniforms [[buffer(1)]]
-)
-{
-    constexpr sampler textureSampler (mag_filter::linear, min_filter::linear);
+        constant TxtFrameUniforms& uniforms [[buffer(1)]],
+        sampler textureSampler [[sampler(0)]]
+) {
     float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
     return half4(pixelColor.r, pixelColor.g, pixelColor.b, pixelColor.a);
 }
 
 fragment half4 frag_grad(GradShaderInOut in [[stage_in]],
diff a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.h
--- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.h
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.h
@@ -38,24 +38,31 @@
 // 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)getTextureEncoder:(id<MTLTexture> _Nonnull)dest
                                       isSrcOpaque:(bool)isSrcOpaque
-                                      isDstOpaque:(bool)isDstOpaque;
+                                      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;
 
 // Base method to obtain any MTLRenderCommandEncoder
 - (id<MTLRenderCommandEncoder> _Nonnull)
     getEncoder:(id<MTLTexture> _Nonnull)dest
       isOpaque:(jboolean)isOpaque
      isTexture:(jboolean)isTexture
+ interpolation:(int)interpolation
           isAA:(jboolean)isAA
       srcFlags:(const SurfaceRasterFlags *_Nullable)srcFlags;
 
 - (id<MTLBlitCommandEncoder> _Nonnull)createBlitEncoder;
 
diff a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.m
--- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.m
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/EncoderManager.m
@@ -38,10 +38,12 @@
 
     // Persistent encoder properties
     id<MTLTexture> _destination;
     SurfaceRasterFlags _dstFlags;
 
+    jboolean _isAA;
+
     //
     // Cached 'mutable' states of encoder
     //
 
     // Composite rule and source raster flags (it affects the CAD-multipliers (of pipelineState))
@@ -51,11 +53,11 @@
     // 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;
-    jboolean _isAA;
+    int _interpolationMode;
 
     // Clip rect or stencil
     MTLClip * _clip;
 
     // Transform (affects transformation inside vertex shader)
@@ -100,10 +102,11 @@
 
 - (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
                 paint:(MTLPaint *)paint
             composite:(MTLComposite *)composite
             isTexture:(jboolean)isTexture
+        interpolation:(int)interpolation
                  isAA:(jboolean)isAA
              srcFlags:(const SurfaceRasterFlags * _Nullable)srcFlags
                  clip:(MTLClip *)clip
             transform:(MTLTransform *)transform
           forceUpdate:(jboolean)forceUpdate
@@ -127,10 +130,11 @@
     [self updatePipelineState:encoder
                         paint:paint
                     composite:composite
                 isStencilUsed:[clip isShape]
                     isTexture:isTexture
+                interpolation:interpolation
                          isAA:isAA
                      srcFlags:srcFlags
                   forceUpdate:forceUpdate];
     [self updateTransform:encoder transform:transform forceUpdate:forceUpdate];
     [self updateClip:encoder clip:clip forceUpdate:forceUpdate];
@@ -144,44 +148,48 @@
 - (void)updatePipelineState:(id<MTLRenderCommandEncoder>)encoder
                       paint:(MTLPaint *)paint
                   composite:(MTLComposite *)composite
               isStencilUsed:(jboolean)isStencilUsed
                   isTexture:(jboolean)isTexture
+              interpolation:(int)interpolation
                        isAA:(jboolean)isAA
                    srcFlags:(const SurfaceRasterFlags * _Nullable)srcFlags
                 forceUpdate:(jboolean)forceUpdate
 {
     if (srcFlags == NULL)
         srcFlags = &defaultRasterFlags;
 
     if (!forceUpdate
         && [_paint isEqual:paint]
         && [_composite isEqual:composite]
-        && _isTexture == isTexture
+        && (_isTexture == isTexture && (!isTexture || _interpolationMode == interpolation)) // interpolation is used only in texture mode
         && _isAA == isAA
         && _srcFlags.isOpaque == srcFlags->isOpaque && _srcFlags.isPremultiplied == srcFlags->isPremultiplied)
         return;
 
     [_paint copyFrom:paint];
     [_composite copyFrom:composite];
     _isTexture = isTexture;
+    _interpolationMode = interpolation;
     _isAA = isAA;
     _srcFlags = *srcFlags;
 
     if ((jint)[composite getCompositeState] == sun_java2d_SunGraphics2D_COMP_XOR) {
         [paint setXorModePipelineState:encoder
                       composite:_composite
                   isStencilUsed:isStencilUsed
                       isTexture:_isTexture
+                  interpolation:interpolation
                        srcFlags:&_srcFlags
                        dstFlags:&_dstFlags
            pipelineStateStorage:_pipelineStateStorage];
     } else {
         [paint setPipelineState:encoder
                       composite:_composite
                   isStencilUsed:isStencilUsed
                       isTexture:_isTexture
+                  interpolation:interpolation
                            isAA:isAA
                        srcFlags:&_srcFlags
                        dstFlags:&_dstFlags
            pipelineStateStorage:_pipelineStateStorage];
     }
@@ -265,54 +273,74 @@
 - (id<MTLRenderCommandEncoder> _Nonnull)getAARenderEncoder:(const BMTLSDOps * _Nonnull)dstOps {
   id<MTLTexture> dstTxt = dstOps->pTexture;
   return [self getEncoder:dstTxt
                  isOpaque:dstOps->isOpaque
                 isTexture:JNI_FALSE
+           interpolation:INTERPOLATION_NEAREST_NEIGHBOR
                      isAA:JNI_TRUE
                  srcFlags:NULL];
 }
 
 - (id<MTLRenderCommandEncoder> _Nonnull)getRenderEncoder:(id<MTLTexture> _Nonnull)dest
                                              isDstOpaque:(bool)isOpaque
 {
     return [self getEncoder:dest
                  isOpaque:isOpaque
                 isTexture:JNI_FALSE
+            interpolation:INTERPOLATION_NEAREST_NEIGHBOR
                      isAA:JNI_FALSE
                  srcFlags:NULL];
 }
 
 - (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(const BMTLSDOps * _Nonnull)dstOps
                                       isSrcOpaque:(bool)isSrcOpaque
 {
-    return [self getTextureEncoder:dstOps->pTexture isSrcOpaque:isSrcOpaque isDstOpaque:dstOps->isOpaque];
+    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) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
                                       isSrcOpaque:(bool)isSrcOpaque
                                       isDstOpaque:(bool)isDstOpaque
+                                    interpolation:(int)interpolation
                                              isAA:(jboolean)isAA
 {
     SurfaceRasterFlags srcFlags = { isSrcOpaque, JNI_TRUE };
     return [self getEncoder:dest
                    isOpaque:isDstOpaque
                   isTexture:JNI_TRUE
+              interpolation:interpolation
                        isAA:isAA
                    srcFlags:&srcFlags];
 }
 
 - (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 isAA:JNI_FALSE];
+    return [self getTextureEncoder:dest isSrcOpaque:isSrcOpaque isDstOpaque:isDstOpaque interpolation:interpolation isAA:JNI_FALSE];
 }
 
 - (id<MTLRenderCommandEncoder> _Nonnull)
     getEncoder:(id<MTLTexture> _Nonnull)dest
       isOpaque:(jboolean)isOpaque
      isTexture:(jboolean)isTexture
+ interpolation:(int)interpolation
           isAA:(jboolean)isAA
       srcFlags:(const SurfaceRasterFlags *_Nullable)srcFlags {
   //
   // 1. check whether it's necessary to call endEncoder
   //
@@ -407,10 +435,11 @@
   //
   [_encoderStates updateEncoder:_encoder
                           paint:_mtlc.paint
                       composite:_mtlc.composite
                       isTexture:isTexture
+                  interpolation:interpolation
                            isAA:isAA
                        srcFlags:srcFlags
                            clip:_mtlc.clip
                       transform:_mtlc.transform
                     forceUpdate:forceUpdate];
@@ -434,10 +463,11 @@
           NSUInteger _w = _destination.width;
           NSUInteger _h = _destination.height;
           _encoder = [self getTextureEncoder:_destination
                                  isSrcOpaque:JNI_FALSE
                                  isDstOpaque:JNI_TRUE
+                               interpolation:INTERPOLATION_NEAREST_NEIGHBOR
                                         isAA:JNI_TRUE];
 
           struct TxtVertex quadTxVerticesBuffer[] = {
               {{0, 0}, {0, 0}},
               {{0,_h}, {0, 1}},
diff a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.h
--- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.h
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.h
@@ -69,10 +69,10 @@
 
 void MTLBlitTex2Tex(MTLContext *mtlc, id<MTLTexture> src, id<MTLTexture> dest);
 
 void drawTex2Tex(MTLContext *mtlc,
                         id<MTLTexture> src, id<MTLTexture> dst,
-                        jboolean isSrcOpaque, jboolean isDstOpaque,
+                        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 a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.m
--- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.m
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLBlitLoops.m
@@ -140,11 +140,11 @@
 
 //#define TRACE_drawTex2Tex
 
 void drawTex2Tex(MTLContext *mtlc,
                         id<MTLTexture> src, id<MTLTexture> dst,
-                        jboolean isSrcOpaque, jboolean isDstOpaque,
+                        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);
@@ -153,11 +153,13 @@
     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];
+                                                                     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];
@@ -247,11 +249,11 @@
  */
 
 static void
 MTLBlitSwToTextureViaPooledTexture(
         MTLContext *mtlc, SurfaceDataRasInfo *srcInfo, BMTLSDOps * bmtlsdOps,
-        MTLRasterFormatInfo * rfi, jboolean useBlitEncoder,
+        MTLRasterFormatInfo * rfi, jboolean useBlitEncoder, jint hint,
         jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2)
 {
     const int sw = srcInfo->bounds.x2 - srcInfo->bounds.x1;
     const int sh = srcInfo->bounds.y2 - srcInfo->bounds.y1;
     id<MTLTexture> dest = bmtlsdOps->pTexture;
@@ -277,11 +279,11 @@
                     destinationSlice:0
                     destinationLevel:0
                    destinationOrigin:MTLOriginMake(dx1, dy1, 0)];
         [blitEncoder endEncoding];
     } else {
-        drawTex2Tex(mtlc, swizzledTexture != nil ? swizzledTexture : texBuff, dest, !rfi->hasAlpha, bmtlsdOps->isOpaque,
+        drawTex2Tex(mtlc, swizzledTexture != nil ? swizzledTexture : texBuff, dest, !rfi->hasAlpha, bmtlsdOps->isOpaque, hint,
                     0, 0, sw, sh, dx1, dy1, dx2, dy2);
     }
 
     if (swizzledTexture != nil) {
         [swizzledTexture release];
@@ -483,11 +485,11 @@
     }
 
 #ifdef TRACE_ISOBLIT
     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via sampling]");
 #endif //TRACE_ISOBLIT
-    drawTex2Tex(mtlc, srcTex, dstTex, srcOps->isOpaque, dstOps->isOpaque, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
+    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.
@@ -611,17 +613,17 @@
                     replaceTextureRegion(dest, &srcInfo, &rfi, (int) dx1, (int) dy1, (int) dx2, (int) dy2);
                 } else {
 #ifdef TRACE_BLIT
                     J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via pooled + blit]");
 #endif //TRACE_BLIT
-                    MTLBlitSwToTextureViaPooledTexture(mtlc, &srcInfo, dstOps, &rfi, true, dx1, dy1, dx2, dy2);
+                    MTLBlitSwToTextureViaPooledTexture(mtlc, &srcInfo, dstOps, &rfi, true, hint, dx1, dy1, dx2, dy2);
                 }
             } else { // !useReplaceRegion
 #ifdef TRACE_BLIT
                 J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE," [via pooled texture]");
 #endif //TRACE_BLIT
-                MTLBlitSwToTextureViaPooledTexture(mtlc, &srcInfo, dstOps, &rfi, false, dx1, dy1, dx2, dy2);
+                MTLBlitSwToTextureViaPooledTexture(mtlc, &srcInfo, dstOps, &rfi, false, hint, dx1, dy1, dx2, dy2);
             }
         }
         SurfaceData_InvokeRelease(env, srcOps, &srcInfo);
     }
     SurfaceData_InvokeUnlock(env, srcOps, &srcInfo);
diff a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.m
--- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.m
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLContext.m
@@ -97,10 +97,12 @@
 @synthesize textureFunction,
             vertexCacheEnabled, aaEnabled, device, library, pipelineStateStorage,
             commandQueue, vertexBuffer,
             texturePool;
 
+extern void initSamplers(id<MTLDevice> device);
+
 - (id)initWithDevice:(id<MTLDevice>)d shadersLib:(NSString*)shadersLib {
     self = [super init];
     if (self) {
         // Initialization code here.
         device = d;
@@ -129,10 +131,12 @@
 
         _commandBufferWrapper = nil;
 
         // Create command queue
         commandQueue = [device newCommandQueue];
+
+        initSamplers(device);
     }
     return self;
 }
 
 - (void)dealloc {
diff a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.h b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.h
--- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.h
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.h
@@ -96,18 +96,20 @@
 // 2. Prepares corresponding buffers of vertex and fragment shaders
 - (void)setPipelineState:(id<MTLRenderCommandEncoder>)encoder
                composite:(MTLComposite *)composite
            isStencilUsed:(jboolean)isStencilUsed
                isTexture:(jboolean)isTexture
+           interpolation:(int)interpolation
                 srcFlags:(const SurfaceRasterFlags *)srcFlags
                 dstFlags:(const SurfaceRasterFlags *)dstFlags
     pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage;
 
 - (void)setXorModePipelineState:(id<MTLRenderCommandEncoder>)encoder
                composite:(MTLComposite *)composite
            isStencilUsed:(jboolean)isStencilUsed
                isTexture:(jboolean)isTexture
+           interpolation:(int)interpolation
                 srcFlags:(const SurfaceRasterFlags *)srcFlags
                 dstFlags:(const SurfaceRasterFlags *)dstFlags
     pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage;
 @end
 
diff a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.m
--- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.m
+++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLPaints.m
@@ -281,17 +281,74 @@
     _anchor.yParams[0] = yp0;
     _anchor.yParams[1] = yp1;
     _anchor.yParams[2] = yp3;
 }
 
+static id<MTLSamplerState> samplerNearestClamp = nil;
+static id<MTLSamplerState> samplerLinearClamp = nil;
+static id<MTLSamplerState> samplerNearestRepeat = nil;
+static id<MTLSamplerState> samplerLinearRepeat = nil;
+
+void initSamplers(id<MTLDevice> device) {
+    // TODO: move this code into SamplerManager (need implement)
+
+    if (samplerNearestClamp != nil)
+        return;
+
+    MTLSamplerDescriptor *samplerDescriptor = [MTLSamplerDescriptor new];
+
+    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];
+}
+
+static void setTxtUniforms(
+        id<MTLRenderCommandEncoder> encoder, int color, int mode, int interpolation, bool repeat, jfloat extraAlpha,
+        const SurfaceRasterFlags * srcFlags, const SurfaceRasterFlags * dstFlags
+) {
+    struct TxtFrameUniforms uf = {RGBA_TO_V4(color), mode, srcFlags->isOpaque, dstFlags->isOpaque, interpolation};
+    [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
+
+    id<MTLSamplerState> sampler;
+    if (repeat) {
+        sampler = interpolation == INTERPOLATION_BILINEAR ? samplerLinearRepeat : samplerNearestRepeat;
+    } else {
+        sampler = interpolation == INTERPOLATION_BILINEAR ? samplerLinearClamp : samplerNearestClamp;
+    }
+    [encoder setFragmentSamplerState:sampler atIndex:0];
+}
+
+// TODO: need support hints for all shaders
+
 // For the current paint mode:
 // 1. Selects vertex+fragment shaders (and corresponding pipelineDesc) and set pipelineState
 // 2. Set vertex and fragment buffers
 - (void)setPipelineState:(id<MTLRenderCommandEncoder>)encoder
                composite:(MTLComposite *)composite
            isStencilUsed:(jboolean)isStencilUsed
                isTexture:(jboolean)isTexture
+           interpolation:(int)interpolation
                     isAA:(jboolean)isAA
                 srcFlags:(const SurfaceRasterFlags *)srcFlags
                 dstFlags:(const SurfaceRasterFlags *)dstFlags
     pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage
 {
@@ -312,15 +369,11 @@
                                                       dstFlags:dstFlags
                                                  stencilNeeded:stencil];
         [encoder setVertexBytes:&_anchor length:sizeof(_anchor) atIndex:FrameUniformBuffer];
         [encoder setFragmentTexture:_paintTexture atIndex: 1];
 
-        struct TxtFrameUniforms uf = {RGBA_TO_V4(0), 0, srcFlags->isOpaque,
-                                      dstFlags->isOpaque, [composite getExtraAlpha]};
-        [encoder setFragmentBytes:&uf length:sizeof(uf)
-                          atIndex:FrameUniformBuffer];
-
+        setTxtUniforms(encoder, 0, 0, interpolation, YES, [composite getExtraAlpha], srcFlags, dstFlags);
       } else if (_paintState == sun_java2d_SunGraphics2D_PAINT_GRADIENT) {
         pipelineState = [pipelineStateStorage getPipelineState:templateTexturePipelineDesc
                                                 vertexShaderId:@"vert_txt_grad"
                                               fragmentShaderId:@"frag_txt_grad"
                                                  compositeRule:[composite getRule]
@@ -357,19 +410,11 @@
                                             srcFlags:srcFlags
                                             dstFlags:dstFlags
                                        stencilNeeded:stencil];
         }
 
-        if (_paintState == sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR) {
-          struct TxtFrameUniforms uf = {RGBA_TO_V4(_color), 1,
-                  srcFlags->isOpaque, dstFlags->isOpaque, [composite getExtraAlpha]};
-          [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
-        } else {
-          struct TxtFrameUniforms uf = {RGBA_TO_V4(0), 0,
-                  srcFlags->isOpaque, dstFlags->isOpaque, [composite getExtraAlpha]};
-          [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
-        }
+        setTxtUniforms(encoder, _color, _paintState == sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR ? 1 : 0, interpolation, NO, [composite getExtraAlpha], srcFlags, dstFlags);
       }
     } else {
         if (_paintState == sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR) {
             pipelineState = [pipelineStateStorage getPipelineState:templateRenderPipelineDesc
                                                     vertexShaderId:@"vert_col"
@@ -421,10 +466,11 @@
 // 2. Set vertex and fragment buffers
 - (void)setXorModePipelineState:(id<MTLRenderCommandEncoder>)encoder
                composite:(MTLComposite *)composite
            isStencilUsed:(jboolean)isStencilUsed
                isTexture:(jboolean)isTexture
+           interpolation:(int)interpolation
                 srcFlags:(const SurfaceRasterFlags *)srcFlags
                 dstFlags:(const SurfaceRasterFlags *)dstFlags
     pipelineStateStorage:(MTLPipelineStatesStorage *)pipelineStateStorage {
     initTemplatePipelineDescriptors();
 
@@ -437,20 +483,12 @@
                                           vertexShaderId:@"vert_txt"
                                         fragmentShaderId:@"frag_txt"
                                                 srcFlags:srcFlags
                                                 dstFlags:dstFlags
                                            stencilNeeded:stencil];
-
-        if (_paintState == sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR) {
-            struct TxtFrameUniforms uf = {RGBA_TO_V4(_color ^ xorColor), 1,
-                    srcFlags->isOpaque, dstFlags->isOpaque, [composite getExtraAlpha]};
-            [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
-        } else {
-            struct TxtFrameUniforms uf = {RGBA_TO_V4(0 ^ xorColor), 0,
-                    srcFlags->isOpaque, dstFlags->isOpaque, [composite getExtraAlpha]};
-            [encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
-        }
+        const int col = _paintState == sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR ? _color ^ xorColor : 0 ^ xorColor;
+        setTxtUniforms(encoder, col, _paintState == sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR ? 1 : 0, interpolation, NO, [composite getExtraAlpha], srcFlags, dstFlags);
         [encoder setFragmentBytes:&xorColor length:sizeof(xorColor) atIndex: 0];
     } else {
         if (_paintState == sun_java2d_SunGraphics2D_PAINT_ALPHACOLOR) {
 
             pipelineState = [pipelineStateStorage getXorModePipelineState:templateRenderPipelineDesc
