diff --git a/doc/ExtensionSupport.md b/doc/ExtensionSupport.md
index e85bc81fa207a895bb4275be5b5c76a9ee900b24..4870f35a36857371c6cd0d750801eb36a48e23b9 100644
--- a/doc/ExtensionSupport.md
+++ b/doc/ExtensionSupport.md
@@ -230,6 +230,7 @@ using data from registry_xml.py and gl.xml.
 | [GL_CHROMIUM_copy_compressed_texture](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/CHROMIUM_copy_compressed_texture.txt) | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
 | [GL_CHROMIUM_copy_texture](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/CHROMIUM_copy_texture.txt) | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
 | [GL_ANGLE_copy_texture_3d](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_copy_texture_3d.txt) | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
+| [GL_WEBKIT_explicit_resolve_target](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/WEBKIT_explicit_resolve_target.txt) |  |  |  |  |  |  |  |
 | [GL_CHROMIUM_framebuffer_mixed_samples](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/CHROMIUM_framebuffer_mixed_samples.txt) |  |  |  |  |  |  |  |
 | [GL_ANGLE_framebuffer_multisample](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_framebuffer_multisample.txt) | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
 | [GL_ANGLE_get_image](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_get_image.txt) | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
@@ -267,6 +268,7 @@ using data from registry_xml.py and gl.xml.
 | [GL_ANGLE_texture_external_update](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_texture_external_update.txt) |  |  |  |  |  |  |  |
 | [GL_ANGLE_texture_multisample](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_texture_multisample.txt) | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
 | [GL_ANGLE_texture_rectangle](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_texture_rectangle.txt) |  |  |  |  |  |  |  |
+| [GL_ANGLE_variable_rasterization_rate_metal](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_variable_rasterization_rate_metal.txt) |  |  |  |  |  |  |  |
 | [GL_ANGLE_vulkan_image](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_vulkan_image.txt) | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
 | [GL_ANGLE_webgl_compatibility](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_webgl_compatibility.txt) |  |  |  |  |  |  |  |
 | [GL_ANGLE_yuv_internal_format](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_yuv_internal_format.txt) | &#x2714; |  | &#x2714; | &#x2714; | &#x2714; | &#x2714; | &#x2714; |
diff --git a/include/GLES2/gl2ext_angle.h b/include/GLES2/gl2ext_angle.h
index e86dfd066c0950bad804c50439a93f4dbe62489d..e612624f0317edbe06183e55d278eddac31a46c4 100644
--- a/include/GLES2/gl2ext_angle.h
+++ b/include/GLES2/gl2ext_angle.h
@@ -747,5 +747,23 @@ GL_APICALL void GL_APIENTRY glGetPointervANGLE (GLenum pname, void **params);
 #endif
 #endif /* GL_ANGLE_blob_cache */
 
+#ifndef GL_WEBKIT_explicit_resolve_target
+typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC) (GLenum, GLenum, GLenum, GLuint);
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glFramebufferResolveRenderbufferWEBKIT(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
+#endif
+#endif /* GL_WEBKIT_explicit_resolve_target */
+
+#ifndef GL_ANGLE_variable_rasterization_rate_metal
+#define GL_ANGLE_variable_rasterization_rate_metal 1
+
+#define GL_VARIABLE_RASTERIZATION_RATE_ANGLE            0x96BC
+#define GL_METAL_RASTERIZATION_RATE_MAP_BINDING_ANGLE   0x96BD
+typedef void *GLMTLRasterizationRateMapANGLE;
+typedef void (GL_APIENTRYP PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC) (GLuint, GLMTLRasterizationRateMapANGLE);
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glBindMetalRasterizationRateMapANGLE(GLuint framebuffer, GLMTLRasterizationRateMapANGLE map);
+#endif
+#endif /* GL_ANGLE_variable_rasterization_rate_metal */
 
 #endif  // INCLUDE_GLES2_GL2EXT_ANGLE_H_
diff --git a/include/platform/autogen/FeaturesMtl_autogen.h b/include/platform/autogen/FeaturesMtl_autogen.h
index 3eaee0ad0744cc9b01c235d16d29e7f55416f9c8..b6d521015840f78d626a0134af3375989c3bf28b 100644
--- a/include/platform/autogen/FeaturesMtl_autogen.h
+++ b/include/platform/autogen/FeaturesMtl_autogen.h
@@ -74,6 +74,12 @@ struct FeaturesMtl : FeatureSetBase
         &members,
     };
 
+    FeatureInfo hasVariableRasterizationRate = {
+        "hasVariableRasterizationRate",
+        FeatureCategory::MetalFeatures,
+        &members,
+    };
+
     FeatureInfo allowInlineConstVertexData = {
         "allowInlineConstVertexData",
         FeatureCategory::MetalFeatures,
diff --git a/include/platform/mtl_features.json b/include/platform/mtl_features.json
index 6d0934476badfbfe9964536ec5d60b66be3cc2b8..a30d0107f0bc0f1f581c87441bc9899e8c9855f8 100644
--- a/include/platform/mtl_features.json
+++ b/include/platform/mtl_features.json
@@ -70,6 +70,13 @@
                 "The renderer supports MTL(Shared)Event"
             ]
         },
+        {
+            "name": "has_variable_rasterization_rate",
+            "category": "Features",
+            "description": [
+                "The renderer supports variable rasterization rate"
+            ]
+        },
         {
             "name": "allow_inline_const_vertex_data",
             "category": "Features",
diff --git a/scripts/entry_point_packed_gl_enums.json b/scripts/entry_point_packed_gl_enums.json
index b258660ba4367032e592ab8d6397ee5edca0a661..fe961d3125259b015d3d005a6d45443ede3131bc 100644
--- a/scripts/entry_point_packed_gl_enums.json
+++ b/scripts/entry_point_packed_gl_enums.json
@@ -440,6 +440,9 @@
     "glFramebufferFoveationParameters": {
         "framebuffer": "FramebufferID"
     },
+    "glFramebufferResolveRenderbufferWEBKIT": {
+        "renderbuffer": "RenderbufferID"
+    },
     "glFramebufferRenderbuffer": {
         "renderbuffer": "RenderbufferID"
     },
diff --git a/scripts/generate_entry_points.py b/scripts/generate_entry_points.py
index ed1c845bcbad7f137d025084a79a09f4ad5f3357..214ac27f14b7879cba758902d55627bf5111edeb 100755
--- a/scripts/generate_entry_points.py
+++ b/scripts/generate_entry_points.py
@@ -1053,6 +1053,7 @@ FORMAT_DICT = {
     "GLint": "%d",
     "GLintptr": UNSIGNED_LONG_LONG_FORMAT,
     "GLSETBLOBPROCANGLE": POINTER_FORMAT,
+    "GLMTLRasterizationRateMapANGLE": POINTER_FORMAT,
     "GLshort": "%d",
     "GLsizei": "%d",
     "GLsizeiptr": UNSIGNED_LONG_LONG_FORMAT,
diff --git a/scripts/gl_angle_ext.xml b/scripts/gl_angle_ext.xml
index 44c6dea45fb939b843aa889720413c51c137a030..5ea6342532fcf698fc8d6300515abbe7017599f6 100644
--- a/scripts/gl_angle_ext.xml
+++ b/scripts/gl_angle_ext.xml
@@ -13,6 +13,7 @@
     <types>
         <type>typedef GLsizeiptr (<apientry/> *<name>GLGETBLOBPROCANGLE</name>)(const void *key, GLsizeiptr keySize, void *value, GLsizeiptr valueSize, const void *userParam);</type>
         <type>typedef void (<apientry/> *<name>GLSETBLOBPROCANGLE</name>)(const void *key, GLsizeiptr keySize, const void *value, GLsizeiptr valueSize, const void *userParam);</type>
+        <type>typedef void *<name>GLMTLRasterizationRateMapANGLE</name>;</type>
     </types>
 
     <!-- SECTION: GL parameter class type definitions. -->
@@ -1066,6 +1067,18 @@
             <param len="1">void **<name>params</name></param>
             <alias name="glGetPointerv"/>
         </command>
+        <command>
+            <proto>void <name>glBindMetalRasterizationRateMapANGLE</name></proto>
+            <param><ptype>GLuint</ptype> <name>framebuffer</name></param>
+            <param><ptype>GLMTLRasterizationRateMapANGLE</ptype> <name>map</name></param>
+        </command>
+        <command>
+            <proto>void <name>glFramebufferResolveRenderbufferWEBKIT</name></proto>
+            <param><ptype>GLenum</ptype> <name>target</name></param>
+            <param><ptype>GLenum</ptype> <name>attachment</name></param>
+            <param><ptype>GLenum</ptype> <name>renderbuffertarget</name></param>
+            <param><ptype>GLuint</ptype> <name>renderbuffer</name></param>
+        </command>
     </commands>
 
     <!-- SECTION: ANGLE extension interface definitions -->
@@ -1465,6 +1478,16 @@
         <extension name="GL_ANGLE_shader_binary" supported='gles2'>
             <require>
                 <enum name="GL_SHADER_BINARY_ANGLE"/>
+        <extension name="GL_ANGLE_variable_rasterization_rate_metal" supported="gles2">
+            <require>
+                <enum name="GL_VARIABLE_RASTERIZATION_RATE_ANGLE"/>
+                <enum name="GL_METAL_RASTERIZATION_RATE_MAP_BINDING_ANGLE"/>
+                <command name="glBindMetalRasterizationRateMapANGLE"/>
+            </require>
+        </extension>
+        <extension name="GL_WEBKIT_explicit_resolve_target" supported='gles2'>
+            <require>
+                <command name="glFramebufferResolveRenderbufferWEBKIT"/>
             </require>
         </extension>
         <extension name="GL_ANGLE_blob_cache" supported="gles2">
@@ -1657,4 +1680,8 @@
         <enum value="0x96EF" name="GL_BLOB_CACHE_SET_FUNCTION_ANGLE"/>
         <enum value="0x972D" name="GL_BLOB_CACHE_USER_PARAM_ANGLE"/>
     </enums>
+    <enums namespace="GL" start="0x96BC" end="0x96BD" vendor="ANGLE">
+        <enum value="0x96BC" name="GL_VARIABLE_RASTERIZATION_RATE_ANGLE"/>
+        <enum value="0x96BD" name="GL_METAL_RASTERIZATION_RATE_MAP_BINDING_ANGLE"/>
+    </enums>
 </registry>
diff --git a/scripts/registry_xml.py b/scripts/registry_xml.py
index 3f2deac8b890ed285099d32b70b1db5070b5d18d..2871d8e3f3ec0a46da03281343186738575221e1 100644
--- a/scripts/registry_xml.py
+++ b/scripts/registry_xml.py
@@ -74,12 +74,14 @@ angle_requestable_extensions = [
     "GL_ANGLE_texture_compression_dxt5",
     "GL_ANGLE_texture_external_update",
     "GL_ANGLE_texture_multisample",
+    "GL_ANGLE_variable_rasterization_rate_metal",
     "GL_ANGLE_vulkan_image",
     "GL_ANGLE_yuv_internal_format",
     "GL_CHROMIUM_color_buffer_float_rgb",
     "GL_CHROMIUM_color_buffer_float_rgba",
     "GL_CHROMIUM_lose_context",
     "GL_CHROMIUM_sync_query",
+    "GL_WEBKIT_explicit_resolve_target",
 ]
 
 gles_requestable_extensions = [
diff --git a/src/common/debug.cpp b/src/common/debug.cpp
index 706852242b5579629b6156f27d330209b6701a1a..77f70f55532d461556fc105213424e5e4c3146a7 100644
--- a/src/common/debug.cpp
+++ b/src/common/debug.cpp
@@ -36,6 +36,13 @@
 #include "common/entry_points_enum_autogen.h"
 #include "common/system_utils.h"
 
+#if defined(ANGLE_ENABLE_ASSERTS)
+bool AreAssertionsEnabled() {
+    static bool enabled = [] { return angle::GetEnvironmentVar("ANGLE_DISABLE_ASSERTS") != "1"; }();
+    return enabled;
+}
+#endif  // defined(ANGLE_ENABLE_ASSERTS)
+
 namespace gl
 {
 
diff --git a/src/common/entry_points_enum_autogen.cpp b/src/common/entry_points_enum_autogen.cpp
index 7d3beb4fe34e3eae82f48b15bfdc9376e7841eda..8479f0719dbd27b4fca7c2f19fcaedb68b82e471 100644
--- a/src/common/entry_points_enum_autogen.cpp
+++ b/src/common/entry_points_enum_autogen.cpp
@@ -524,6 +524,8 @@ const char *GetEntryPointName(EntryPoint ep)
             return "glBindFramebufferOES";
         case EntryPoint::GLBindImageTexture:
             return "glBindImageTexture";
+        case EntryPoint::GLBindMetalRasterizationRateMapANGLE:
+            return "glBindMetalRasterizationRateMapANGLE";
         case EntryPoint::GLBindProgramPipeline:
             return "glBindProgramPipeline";
         case EntryPoint::GLBindProgramPipelineEXT:
@@ -954,6 +956,8 @@ const char *GetEntryPointName(EntryPoint ep)
             return "glFramebufferRenderbuffer";
         case EntryPoint::GLFramebufferRenderbufferOES:
             return "glFramebufferRenderbufferOES";
+        case EntryPoint::GLFramebufferResolveRenderbufferWEBKIT:
+            return "glFramebufferResolveRenderbufferWEBKIT";
         case EntryPoint::GLFramebufferTexture:
             return "glFramebufferTexture";
         case EntryPoint::GLFramebufferTexture2D:
diff --git a/src/common/entry_points_enum_autogen.h b/src/common/entry_points_enum_autogen.h
index e09617166d054a05a00c908f55aac4b214cd25ab..476e2c06b02d9a217ef82499cf8994454832dd7a 100644
--- a/src/common/entry_points_enum_autogen.h
+++ b/src/common/entry_points_enum_autogen.h
@@ -268,6 +268,7 @@ enum class EntryPoint
     GLBindFramebuffer,
     GLBindFramebufferOES,
     GLBindImageTexture,
+    GLBindMetalRasterizationRateMapANGLE,
     GLBindProgramPipeline,
     GLBindProgramPipelineEXT,
     GLBindRenderbuffer,
@@ -483,6 +484,7 @@ enum class EntryPoint
     GLFramebufferPixelLocalStorageRestoreANGLE,
     GLFramebufferRenderbuffer,
     GLFramebufferRenderbufferOES,
+    GLFramebufferResolveRenderbufferWEBKIT,
     GLFramebufferTexture,
     GLFramebufferTexture2D,
     GLFramebufferTexture2DMultisampleEXT,
diff --git a/src/common/frame_capture_utils_autogen.cpp b/src/common/frame_capture_utils_autogen.cpp
index 48e319050981b8421a492630349b3d4b5935d4b0..94b071c78bb3a811a090e72743eefe57177a00eb 100644
--- a/src/common/frame_capture_utils_autogen.cpp
+++ b/src/common/frame_capture_utils_autogen.cpp
@@ -229,6 +229,9 @@ void WriteParamCaptureReplay(std::ostream &os, const CallCapture &call, const Pa
         case ParamType::TGLSETBLOBPROCANGLE:
             WriteParamValueReplay<ParamType::TGLSETBLOBPROCANGLE>(
                 os, call, param.value.GLSETBLOBPROCANGLEVal);
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            WriteParamValueReplay<ParamType::TGLMTLRasterizationRateMapANGLE>(
+                os, call, param.value.GLMTLRasterizationRateMapANGLEVal);
             break;
         case ParamType::TGLbitfield:
             WriteParamValueReplay<ParamType::TGLbitfield>(os, call, param.value.GLbitfieldVal);
@@ -739,6 +742,8 @@ const char *ParamTypeToString(ParamType paramType)
             return "GLGETBLOBPROCANGLE";
         case ParamType::TGLSETBLOBPROCANGLE:
             return "GLSETBLOBPROCANGLE";
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            return "GLMTLRasterizationRateMapANGLE";
         case ParamType::TGLbitfield:
             return "GLbitfield";
         case ParamType::TGLboolean:
diff --git a/src/common/frame_capture_utils_autogen.h b/src/common/frame_capture_utils_autogen.h
index 34dcf881b22b8d3c02ede3737d927edf50d17d57..e5ffb4730c8719b94be3657306fd2d88e1ae747a 100644
--- a/src/common/frame_capture_utils_autogen.h
+++ b/src/common/frame_capture_utils_autogen.h
@@ -76,6 +76,7 @@ enum class ParamType
     TGLDEBUGPROCKHR,
     TGLGETBLOBPROCANGLE,
     TGLSETBLOBPROCANGLE,
+    TGLMTLRasterizationRateMapANGLE,
     TGLbitfield,
     TGLboolean,
     TGLbooleanPointer,
@@ -182,7 +183,7 @@ enum class ParamType
     TvoidPointerPointer,
 };
 
-constexpr uint32_t kParamTypeCount = 163;
+constexpr uint32_t kParamTypeCount = 164;
 
 union ParamValue
 {
@@ -245,6 +246,7 @@ union ParamValue
     GLDEBUGPROCKHR GLDEBUGPROCKHRVal;
     GLGETBLOBPROCANGLE GLGETBLOBPROCANGLEVal;
     GLSETBLOBPROCANGLE GLSETBLOBPROCANGLEVal;
+    GLMTLRasterizationRateMapANGLE GLMTLRasterizationRateMapANGLEVal;
     GLbitfield GLbitfieldVal;
     GLboolean GLbooleanVal;
     GLboolean *GLbooleanPointerVal;
@@ -741,6 +743,14 @@ inline GLSETBLOBPROCANGLE GetParamVal<ParamType::TGLSETBLOBPROCANGLE, GLSETBLOBP
     return value.GLSETBLOBPROCANGLEVal;
 }
 
+template <>
+inline GLMTLRasterizationRateMapANGLE
+GetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE, GLMTLRasterizationRateMapANGLE>(
+    const ParamValue &value)
+{
+    return value.GLMTLRasterizationRateMapANGLEVal;
+}
+
 template <>
 inline GLbitfield GetParamVal<ParamType::TGLbitfield, GLbitfield>(const ParamValue &value)
 {
@@ -1557,6 +1567,8 @@ T AccessParamValue(ParamType paramType, const ParamValue &value)
             return GetParamVal<ParamType::TGLGETBLOBPROCANGLE, T>(value);
         case ParamType::TGLSETBLOBPROCANGLE:
             return GetParamVal<ParamType::TGLSETBLOBPROCANGLE, T>(value);
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            return GetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE, T>(value);
         case ParamType::TGLbitfield:
             return GetParamVal<ParamType::TGLbitfield, T>(value);
         case ParamType::TGLboolean:
@@ -2147,6 +2159,14 @@ inline void SetParamVal<ParamType::TGLSETBLOBPROCANGLE>(GLSETBLOBPROCANGLE value
     valueOut->GLSETBLOBPROCANGLEVal = valueIn;
 }
 
+template <>
+inline void SetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE>(
+    GLMTLRasterizationRateMapANGLE valueIn,
+    ParamValue *valueOut)
+{
+    valueOut->GLMTLRasterizationRateMapANGLEVal = valueIn;
+}
+
 template <>
 inline void SetParamVal<ParamType::TGLbitfield>(GLbitfield valueIn, ParamValue *valueOut)
 {
@@ -3004,6 +3024,9 @@ void InitParamValue(ParamType paramType, T valueIn, ParamValue *valueOut)
         case ParamType::TGLSETBLOBPROCANGLE:
             SetParamVal<ParamType::TGLSETBLOBPROCANGLE>(valueIn, valueOut);
             break;
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            SetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE>(valueIn, valueOut);
+            break;
         case ParamType::TGLbitfield:
             SetParamVal<ParamType::TGLbitfield>(valueIn, valueOut);
             break;
diff --git a/src/common/gl_enum_utils_autogen.cpp b/src/common/gl_enum_utils_autogen.cpp
index 416b30b2909f6cf35f2da0885847a1c8de1079b3..ad25872b3e2c9d5bfdfe178975a4f918f1d4a33f 100644
--- a/src/common/gl_enum_utils_autogen.cpp
+++ b/src/common/gl_enum_utils_autogen.cpp
@@ -2818,6 +2818,10 @@ const char *GLenumToString(GLESEnum enumGroup, unsigned int value)
                     return "GL_RGBX8_ANGLE";
                 case 0x96BB:
                     return "GL_SHADER_BINARY_ANGLE";
+                case 0x96BC:
+                    return "GL_VARIABLE_RASTERIZATION_RATE_ANGLE";
+                case 0x96BD:
+                    return "GL_METAL_RASTERIZATION_RATE_MAP_BINDING_ANGLE";
                 case 0x96BE:
                     return "GL_PROGRAM_BINARY_READY_ANGLE";
                 case 0x96C0:
@@ -22274,6 +22278,7 @@ static StringEnumEntry g_stringEnumTable[] = {
     {"GL_MESH_SUBROUTINE_UNIFORM_NV", 0x957E},
     {"GL_MESH_VERTICES_OUT_NV", 0x9579},
     {"GL_MESH_WORK_GROUP_SIZE_NV", 0x953E},
+    {"GL_METAL_RASTERIZATION_RATE_MAP_BINDING_ANGLE", 0x96BD},
     {"GL_MIN", 0x8007},
     {"GL_MINMAX", 0x802E},
     {"GL_MINMAX_EXT", 0x802E},
@@ -24925,6 +24930,7 @@ static StringEnumEntry g_stringEnumTable[] = {
     {"GL_VARIABLE_E_NV", 0x8527},
     {"GL_VARIABLE_F_NV", 0x8528},
     {"GL_VARIABLE_G_NV", 0x8529},
+    {"GL_VARIABLE_RASTERIZATION_RATE_ANGLE", 0x96BC},
     {"GL_VARIANT_ARRAY_EXT", 0x87E8},
     {"GL_VARIANT_ARRAY_POINTER_EXT", 0x87E9},
     {"GL_VARIANT_ARRAY_STRIDE_EXT", 0x87E6},
diff --git a/src/common/log_utils.h b/src/common/log_utils.h
index 347d740526aaf8f4c1c84d056ab8b1eb99f0395f..b745db2e934a0319b4a52c6291a8ea2946e86dae 100644
--- a/src/common/log_utils.h
+++ b/src/common/log_utils.h
@@ -262,10 +262,13 @@ std::ostream &FmtHex(std::ostream &os, T value)
 
 // A macro asserting a condition and outputting failures to the debug log
 #if defined(ANGLE_ENABLE_ASSERTS)
+bool AreAssertionsEnabled();
 #    define ASSERT(expression)                                                               \
         (expression ? static_cast<void>(0)                                                   \
+            : (!AreAssertionsEnabled()                                                       \
+                ? static_cast<void>(0)                                                       \
                 : (FATAL() << "\t! Assert failed in " << __FUNCTION__ << " (" << __FILE__    \
-                               << ":" << __LINE__ << "): " << #expression))
+                    << ":" << __LINE__ << "): " << #expression)))
 #else
 #    define ASSERT(condition) ANGLE_EAT_STREAM_PARAMETERS << !(condition)
 #endif  // defined(ANGLE_ENABLE_ASSERTS)
diff --git a/src/common/tls.cpp b/src/common/tls.cpp
index 9600bae1a49fc9b6c409dfe311439a630b3b29f2..b66a225f407cc74f50e0be4015692a8cd94dffd0 100644
--- a/src/common/tls.cpp
+++ b/src/common/tls.cpp
@@ -20,7 +20,6 @@
 #    include <wrl/async.h>
 #    include <wrl/client.h>
 
-using namespace std;
 using namespace Windows::Foundation;
 using namespace ABI::Windows::System::Threading;
 
diff --git a/src/common/utilities.cpp b/src/common/utilities.cpp
index 35085108ad1000ca52727fa6247027014579e7c4..d01deccaa4d02cfdc38c8f2dadfdd56426fe7d2a 100644
--- a/src/common/utilities.cpp
+++ b/src/common/utilities.cpp
@@ -6,6 +6,12 @@
 
 // utilities.cpp: Conversion functions and other utility routines.
 
+// Older clang versions have a false positive on this warning here.
+// TODO(dino): Is this still necessary?
+#if defined(__clang__)
+#    pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+
 #include "common/utilities.h"
 #include "GLES3/gl3.h"
 #include "common/mathutil.h"
diff --git a/src/compiler/fuzz/translator_fuzzer.cpp b/src/compiler/fuzz/translator_fuzzer.cpp
index 5ace655c9b730ed73cb4488940435d31e7ef7937..5ce717bc8ba251bf600f779ebcf83c0e40935a30 100644
--- a/src/compiler/fuzz/translator_fuzzer.cpp
+++ b/src/compiler/fuzz/translator_fuzzer.cpp
@@ -140,6 +140,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
         static_cast<uint32_t>(ShFragmentSynchronizationType::InvalidEnum));
 
     std::vector<uint32_t> validOutputs;
+#ifndef ANGLE_TRANSLATOR_FUZZER_METAL_ONLY
     validOutputs.push_back(SH_ESSL_OUTPUT);
     validOutputs.push_back(SH_GLSL_COMPATIBILITY_OUTPUT);
     validOutputs.push_back(SH_GLSL_130_OUTPUT);
@@ -155,6 +156,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
     validOutputs.push_back(SH_SPIRV_VULKAN_OUTPUT);
     validOutputs.push_back(SH_HLSL_3_0_OUTPUT);
     validOutputs.push_back(SH_HLSL_4_1_OUTPUT);
+#endif
+#ifdef ANGLE_ENABLE_METAL
+    validOutputs.push_back(SH_MSL_METAL_OUTPUT);
+#endif
     bool found = false;
     for (auto valid : validOutputs)
     {
@@ -186,6 +191,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 
         if (translator == nullptr)
         {
+            sh::Finalize();
             return 0;
         }
 
@@ -220,6 +226,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 
         if (!translator->Init(resources))
         {
+            sh::Finalize();
             return 0;
         }
 
@@ -232,5 +239,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
     const char *shaderStrings[]       = {reinterpret_cast<const char *>(data)};
     translator->compile(shaderStrings, 1, options);
 
+    sh::Finalize();
     return 0;
 }
diff --git a/src/compiler/preprocessor/preprocessor_tab_autogen.cpp b/src/compiler/preprocessor/preprocessor_tab_autogen.cpp
index d03039602a42956a7d60903f36bcc194012c4900..1059f9c6ae6090d8a8657b61077f07ed6af9e269 100644
--- a/src/compiler/preprocessor/preprocessor_tab_autogen.cpp
+++ b/src/compiler/preprocessor/preprocessor_tab_autogen.cpp
@@ -1,5 +1,8 @@
 /* A Bison parser, made by GNU Bison 3.8.2.  */
 
+/* Apple Note: For the avoidance of doubt, Apple elects to distribute this file under the terms of
+ * the BSD license. */
+
 /* Bison implementation for Yacc-like parsers in C
 
    Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
diff --git a/src/compiler/translator/Types.h b/src/compiler/translator/Types.h
index c312bcbb09583a15577186b0ec592fdf3282e722..2b889ac55672bb9eb658f83bdbb41860ef666515 100644
--- a/src/compiler/translator/Types.h
+++ b/src/compiler/translator/Types.h
@@ -254,6 +254,7 @@ class TType
     }
     bool isScalarFloat() const { return isScalar() && type == EbtFloat; }
     bool isScalarInt() const { return isScalar() && (type == EbtInt || type == EbtUInt); }
+    bool isSignedInt() const { return (isScalar() || isVector()) && type == EbtInt; }
 
     bool canBeConstructed() const;
 
diff --git a/src/compiler/translator/glslang_tab_autogen.cpp b/src/compiler/translator/glslang_tab_autogen.cpp
index 67bf1134ef04cf5e23eba2ac5521f0c2913d36d2..340a8b123f7980deff57d3c4c4c30a1b12608727 100644
--- a/src/compiler/translator/glslang_tab_autogen.cpp
+++ b/src/compiler/translator/glslang_tab_autogen.cpp
@@ -1,5 +1,8 @@
 /* A Bison parser, made by GNU Bison 3.8.2.  */
 
+/* Apple Note: For the avoidance of doubt, Apple elects to distribute this file under the terms of
+ * the BSD license. */
+
 /* Bison implementation for Yacc-like parsers in C
 
    Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
diff --git a/src/compiler/translator/glslang_tab_autogen.h b/src/compiler/translator/glslang_tab_autogen.h
index d5a48edd40f382359db76acf1a1ce75b6a6dc6d5..907c62e0bb659292b3912707904981863429478a 100644
--- a/src/compiler/translator/glslang_tab_autogen.h
+++ b/src/compiler/translator/glslang_tab_autogen.h
@@ -1,5 +1,8 @@
 /* A Bison parser, made by GNU Bison 3.8.2.  */
 
+/* Apple Note: For the avoidance of doubt, Apple elects to distribute this file under the terms of
+ * the BSD license. */
+
 /* Bison interface for Yacc-like parsers in C
 
    Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
diff --git a/src/compiler/translator/msl/EmitMetal.cpp b/src/compiler/translator/msl/EmitMetal.cpp
index adef17611b2a32b3aeb0db799bd1da4002ce2639..2770e1e8fc30157e08fb1b4c90e54c44014c77d3 100644
--- a/src/compiler/translator/msl/EmitMetal.cpp
+++ b/src/compiler/translator/msl/EmitMetal.cpp
@@ -181,12 +181,17 @@ class GenMetalTraverser : public TIntermTraverser
 
     void emitSingleConstant(const TConstantUnion *const constUnion);
 
+    void emitForwardProgressStore();
+    void emitForwardProgressSignal();
+    bool shouldEnsureForwardProgress() const { return mForwardProgressStoreNestingCount >= 0; }
+
   private:
     Sink &mOut;
     const TCompiler &mCompiler;
     const PipelineStructs &mPipelineStructs;
     SymbolEnv &mSymbolEnv;
     IdGen &mIdGen;
+    int mForwardProgressStoreNestingCount  = -1; // Negative means forward progress is not ensured.
     int mIndentLevel                  = -1;
     int mLastIndentationPos           = -1;
     int mOpenPointerParenCount        = 0;
@@ -201,8 +206,44 @@ class GenMetalTraverser : public TIntermTraverser
     size_t mDriverUniformsBindingIndex     = 0;
     size_t mUBOArgumentBufferBindingIndex  = 0;
     bool mRasterOrderGroupsSupported       = false;
-    bool mInjectAsmStatementIntoLoopBodies = false;
+    friend class ScopedForwardProgressStore;
+};
+
+class ScopedForwardProgressStore
+{
+public:
+    ScopedForwardProgressStore(GenMetalTraverser& traverser);
+    ~ScopedForwardProgressStore();
+private:
+    GenMetalTraverser& mTraverser;
 };
+
+ScopedForwardProgressStore::ScopedForwardProgressStore(GenMetalTraverser& traverser)
+    : mTraverser(traverser)
+{
+    if (mTraverser.shouldEnsureForwardProgress())
+    {
+        if (mTraverser.mForwardProgressStoreNestingCount == 0)
+        {
+            mTraverser.emitOpenBrace();
+            mTraverser.emitForwardProgressStore();
+        }
+        ++mTraverser.mForwardProgressStoreNestingCount;
+    }
+}
+
+ScopedForwardProgressStore::~ScopedForwardProgressStore()
+{
+    if (mTraverser.shouldEnsureForwardProgress())
+    {
+        --mTraverser.mForwardProgressStoreNestingCount;
+        if (mTraverser.mForwardProgressStoreNestingCount == 0)
+        {
+            mTraverser.emitCloseBrace();
+        }
+    }
+}
+
 }  // anonymous namespace
 
 GenMetalTraverser::~GenMetalTraverser()
@@ -228,9 +269,11 @@ GenMetalTraverser::GenMetalTraverser(const TCompiler &compiler,
       mDriverUniformsBindingIndex(compileOptions.metal.driverUniformsBindingIndex),
       mUBOArgumentBufferBindingIndex(compileOptions.metal.UBOArgumentBufferBindingIndex),
       mRasterOrderGroupsSupported(compileOptions.pls.fragmentSyncType ==
-                                  ShFragmentSynchronizationType::RasterOrderGroups_Metal),
-      mInjectAsmStatementIntoLoopBodies(compileOptions.metal.injectAsmStatementIntoLoopBodies)
-{}
+                                  ShFragmentSynchronizationType::RasterOrderGroups_Metal)
+{
+    if (compileOptions.metal.injectAsmStatementIntoLoopBodies)
+        mForwardProgressStoreNestingCount = 0;
+}
 
 void GenMetalTraverser::emitIndentation()
 {
@@ -278,40 +321,12 @@ static const char *GetOperatorString(TOperator op,
             return "=";
         case TOperator::EOpInitialize:
             return "=";
-        case TOperator::EOpAddAssign:
-            return "+=";
-        case TOperator::EOpSubAssign:
-            return "-=";
-        case TOperator::EOpMulAssign:
-            return "*=";
-        case TOperator::EOpDivAssign:
-            return "/=";
-        case TOperator::EOpIModAssign:
-            return "%=";
-        case TOperator::EOpBitShiftLeftAssign:
-            return "<<=";  // TODO: Check logical vs arithmetic shifting.
-        case TOperator::EOpBitShiftRightAssign:
-            return ">>=";  // TODO: Check logical vs arithmetic shifting.
         case TOperator::EOpBitwiseAndAssign:
             return "&=";
         case TOperator::EOpBitwiseXorAssign:
             return "^=";
         case TOperator::EOpBitwiseOrAssign:
             return "|=";
-        case TOperator::EOpAdd:
-            return "+";
-        case TOperator::EOpSub:
-            return "-";
-        case TOperator::EOpMul:
-            return "*";
-        case TOperator::EOpDiv:
-            return "/";
-        case TOperator::EOpIMod:
-            return "%";
-        case TOperator::EOpBitShiftLeft:
-            return "<<";  // TODO: Check logical vs arithmetic shifting.
-        case TOperator::EOpBitShiftRight:
-            return ">>";  // TODO: Check logical vs arithmetic shifting.
         case TOperator::EOpBitwiseAnd:
             return "&";
         case TOperator::EOpBitwiseXor:
@@ -363,16 +378,12 @@ static const char *GetOperatorString(TOperator op,
             return "++";
         case TOperator::EOpPreDecrement:
             return "--";
-        case TOperator::EOpVectorTimesScalarAssign:
-            return "*=";
         case TOperator::EOpVectorTimesMatrixAssign:
             return "*=";
         case TOperator::EOpMatrixTimesScalarAssign:
             return "*=";
         case TOperator::EOpMatrixTimesMatrixAssign:
             return "*=";
-        case TOperator::EOpVectorTimesScalar:
-            return "*";
         case TOperator::EOpVectorTimesMatrix:
             return "*";
         case TOperator::EOpMatrixTimesVector:
@@ -386,6 +397,38 @@ static const char *GetOperatorString(TOperator op,
         case TOperator::EOpNotEqualComponentWise:
             return "!=";
 
+        case TOperator::EOpBitShiftRight:
+        case TOperator::EOpBitShiftRightAssign:
+            // TODO: Check logical vs arithmetic shifting.
+            return "ANGLE_rshift";
+
+        case TOperator::EOpBitShiftLeft:
+        case TOperator::EOpBitShiftLeftAssign:
+            // TODO: Check logical vs arithmetic shifting.
+            return resultType.isSignedInt() ? "ANGLE_ilshift" : "ANGLE_ulshift";
+
+        case TOperator::EOpAddAssign:
+        case TOperator::EOpAdd:
+            return resultType.isSignedInt() ? "ANGLE_iadd" : "+";
+
+        case TOperator::EOpSubAssign:
+        case TOperator::EOpSub:
+            return resultType.isSignedInt() ? "ANGLE_isub" : "-";
+
+        case TOperator::EOpMulAssign:
+        case TOperator::EOpMul:
+        case TOperator::EOpVectorTimesScalarAssign:
+        case TOperator::EOpVectorTimesScalar:
+            return resultType.isSignedInt() ? "ANGLE_imul" : "*";
+
+        case TOperator::EOpDiv:
+        case TOperator::EOpDivAssign:
+            return resultType.isSignedInt() ? "ANGLE_div" : "/";
+
+        case TOperator::EOpIMod:
+        case TOperator::EOpIModAssign:
+            return resultType.isSignedInt() ? "ANGLE_imod" : "%";
+
         case TOperator::EOpEqual:
             if ((argType0->getStruct() && argType1->getStruct()) &&
                 (argType0->isArray() && argType1->isArray()))
@@ -882,17 +925,14 @@ void GenMetalTraverser::emitPostQualifier(const EmitVariableDeclarationConfig &e
 
 void GenMetalTraverser::emitLoopBody(TIntermBlock *bodyNode)
 {
-    if (mInjectAsmStatementIntoLoopBodies)
+    const bool emitForwardProgress = shouldEnsureForwardProgress();
+    if (emitForwardProgress)
     {
         emitOpenBrace();
-
-        emitIndentation();
-        mOut << "__asm__(\"\");\n";
+        emitForwardProgressSignal();
     }
-
     bodyNode->traverse(this);
-
-    if (mInjectAsmStatementIntoLoopBodies)
+    if (emitForwardProgress)
     {
         emitCloseBrace();
     }
@@ -1813,6 +1853,18 @@ bool GenMetalTraverser::visitBinary(Visit, TIntermBinary *binaryNode)
         }
         break;
 
+        case TOperator::EOpDivAssign:
+        case TOperator::EOpIModAssign:
+        case TOperator::EOpBitShiftRightAssign:
+        case TOperator::EOpBitShiftLeftAssign:
+        case TOperator::EOpAddAssign:
+        case TOperator::EOpSubAssign:
+        case TOperator::EOpMulAssign:
+        case TOperator::EOpVectorTimesScalarAssign:
+            leftNode.traverse(this);
+            mOut << " = ";
+            [[fallthrough]];
+
         default:
         {
             const TType &resultType = binaryNode->getType();
@@ -2249,7 +2301,27 @@ bool GenMetalTraverser::visitAggregate(Visit, TIntermAggregate *aggregateNode)
         }
         else
         {
+            bool isFtoi = [&]() {
+                if ((!retType.isScalar() && !retType.isVector()) ||
+                    !IsInteger(retType.getBasicType()))
+                    return false;
+                if (aggregateNode->getChildCount() != 1)
+                    return false;
+                auto &argType = aggregateNode->getChildNode(0)->getAsTyped()->getType();
+                return (argType.isScalar() || argType.isVector()) &&
+                       argType.getBasicType() == EbtFloat;
+            }();
+
+            if (isFtoi)
+            {
+                mOut << "ANGLE_ftoi<";
                 emitType(retType, etConfig);
+                mOut << ">";
+            }
+            else
+            {
+                emitType(retType, etConfig);
+            }
             emitArgList("(", ")");
         }
 
@@ -2374,6 +2446,26 @@ void GenMetalTraverser::emitCloseBrace()
     mOut << "}";
 }
 
+void GenMetalTraverser::emitForwardProgressStore()
+{
+    // https://eel.is/c++draft/intro.progress
+    // "The implementation may assume that any thread will eventually do one of the following:""
+    //  - ...
+    //  - "perform an access through a volatile glvalue"
+    // Emit a volatile variable which all loops in the stack will access.
+    emitIndentation();
+    mOut << "volatile bool ANGLE_p;\n";
+}
+
+void GenMetalTraverser::emitForwardProgressSignal()
+{
+    // Emit a read though the volatile variable. This marks the loop as making forward progress even
+    // if the compiler can otherwise analyze it to be infinite. This ensures that the loop
+    // has defined behavior.
+    emitIndentation();
+    mOut << "(void) ANGLE_p;\n";
+}
+
 static bool RequiresSemicolonTerminator(TIntermNode &node)
 {
     if (node.getAsBlock())
@@ -2575,6 +2667,8 @@ bool GenMetalTraverser::visitForLoop(TIntermLoop *loopNode)
     TIntermTyped *condNode = loopNode->getCondition();
     TIntermTyped *exprNode = loopNode->getExpression();
 
+    ScopedForwardProgressStore scopedProgress(*this);
+
     mOut << "for (";
 
     if (initNode)
@@ -2617,6 +2711,7 @@ bool GenMetalTraverser::visitWhileLoop(TIntermLoop *loopNode)
     ASSERT(condNode);
     ASSERT(!initNode && !exprNode);
 
+    ScopedForwardProgressStore scopedProgress(*this);
     emitIndentation();
     mOut << "while (";
     condNode->traverse(this);
@@ -2636,6 +2731,7 @@ bool GenMetalTraverser::visitDoWhileLoop(TIntermLoop *loopNode)
     ASSERT(condNode);
     ASSERT(!initNode && !exprNode);
 
+    ScopedForwardProgressStore scopedProgress(*this);
     emitIndentation();
     mOut << "do\n";
     emitLoopBody(loopNode->getBody());
diff --git a/src/compiler/translator/msl/ProgramPrelude.cpp b/src/compiler/translator/msl/ProgramPrelude.cpp
index c8fdeafe2dc28b1c5e2a8922b63736f6748c84bf..ea9cb63ab990601cae35ecff71ae1a1d5b594c93 100644
--- a/src/compiler/translator/msl/ProgramPrelude.cpp
+++ b/src/compiler/translator/msl/ProgramPrelude.cpp
@@ -113,6 +113,15 @@ class ProgramPrelude : public TIntermTraverser
     void degrees();
     void radians();
     void mod();
+    void div();
+    void imod();
+    void imul();
+    void iadd();
+    void isub();
+    void ilshift();
+    void ulshift();
+    void rshift();
+    void ftoi();
     void mixBool();
     void postIncrementMatrix();
     void preIncrementMatrix();
@@ -433,6 +442,136 @@ ANGLE_ALWAYS_INLINE X ANGLE_mod(X x, Y y)
 }
 )")
 
+// Avoid undefined behavior when:
+// - the divisor is 0
+// - the dividend is INT_MIN and the divisor is -1 (integer overflow)
+// When the behavior would be undefined the result is `x`.
+// FIXME: This function should also handle INT_MIN / -1, but currently this hits a bug in the metal
+// compiler
+PROGRAM_PRELUDE_DECLARE(div,
+                        R"(
+template<typename X, typename Y, typename Z = metal::conditional_t<metal::is_scalar_v<Y>, X, Y>>
+ANGLE_ALWAYS_INLINE Z ANGLE_div(X x, Y y)
+{
+    Z zx = Z(x);
+    Z zy = Z(y);
+    auto predicate = zy == Z(0);
+    return zx / metal::select(zy, Z(1), predicate);
+}
+)")
+
+// Avoid undefined behavior when:
+// - the divisor is 0
+// - the dividend is INT_MIN and the divisor is -1 (integer overflow)
+// - either of the operands is negative (undefined behavior in Metal)
+// When the behavior would be undefined the result is 0.
+// FIXME: This function should also handle INT_MIN % -1, but currently this hits a bug in the metal
+// compiler
+PROGRAM_PRELUDE_DECLARE(imod,
+                        R"(
+template<typename X, typename Y, typename Z = metal::conditional_t<metal::is_scalar_v<Y>, X, Y>>
+ANGLE_ALWAYS_INLINE Z ANGLE_imod(X x, Y y)
+{
+    if constexpr (metal::is_signed_v<Z>) {
+        Z y_or_one = metal::select(Z(y), Z(1), Z(y) == Z(0));
+        if (metal::any(((Z(x) | y_or_one) & Z(2147483648u)) != Z(0u)))
+        {
+            return as_type<Z>(
+                metal::make_unsigned_t<Z>(x) - metal::make_unsigned_t<Z>(x / y_or_one) * metal::make_unsigned_t<Z>(y_or_one)
+            );
+        }
+        else
+        {
+            return x % y_or_one;
+        }
+    }
+    else
+    {
+        return x % metal::select(Z(y), Z(1u), Z(y) == Z(0u));
+    }
+}
+)")
+
+// Avoid undefined behavior when the operand is outside the range of values that can be represented.
+// When the behavior would be undefined the value is clamped to fit the target type.
+PROGRAM_PRELUDE_DECLARE(ftoi,
+                        R"(
+template <typename X, typename Y>
+ANGLE_ALWAYS_INLINE X ANGLE_ftoi(Y y)
+{
+    auto min = metal::numeric_limits<X>::min();
+    auto max = metal::numeric_limits<X>::max();
+    return X(metal::clamp(y, Y(min), Y(max)));
+}
+)")
+
+// Avoid undefined behavior due to integer overflow
+PROGRAM_PRELUDE_DECLARE(imul,
+                        R"(
+template<typename X, typename Y, typename Z = metal::conditional_t<metal::is_scalar_v<Y>, X, Y>>
+ANGLE_ALWAYS_INLINE Z ANGLE_imul(X x, Y y)
+{
+    return as_type<Z>(metal::make_unsigned_t<Z>(x) * metal::make_unsigned_t<Z>(y));
+}
+)")
+
+// Avoid undefined behavior due to integer overflow
+PROGRAM_PRELUDE_DECLARE(iadd,
+                        R"(
+template<typename X, typename Y, typename Z = metal::conditional_t<metal::is_scalar_v<Y>, X, Y>>
+ANGLE_ALWAYS_INLINE Z ANGLE_iadd(X x, Y y)
+{
+    return as_type<Z>(metal::make_unsigned_t<Z>(x) + metal::make_unsigned_t<Z>(y));
+}
+)")
+
+// Avoid undefined behavior due to integer underflow
+PROGRAM_PRELUDE_DECLARE(isub,
+                        R"(
+template<typename X, typename Y, typename Z = metal::conditional_t<metal::is_scalar_v<Y>, X, Y>>
+ANGLE_ALWAYS_INLINE Z ANGLE_isub(X x, Y y)
+{
+    return as_type<Z>(metal::make_unsigned_t<Z>(x) - metal::make_unsigned_t<Z>(y));
+}
+)")
+
+// Avoid undefined behavior in e1 << e2 when:
+// 1) e2 is larger than the bit width of the type
+// 2) e2 is a negative value.
+// 3) e1 is a negative value
+PROGRAM_PRELUDE_DECLARE(ilshift,
+                        R"(
+template <typename X, typename Y>
+ANGLE_ALWAYS_INLINE X ANGLE_ilshift(X x, Y y)
+{
+    return as_type<X>(metal::select(metal::make_unsigned_t<X>(0), metal::make_unsigned_t<X>(x) << (y & Y(31)), metal::make_unsigned_t<Y>(y) < metal::make_unsigned_t<Y>(32)));
+}
+)")
+
+// Avoid undefined behavior in e1 << e2 when:
+// 1) e2 is larger than the bit width of the type
+// 2) e2 is a negative value.
+PROGRAM_PRELUDE_DECLARE(ulshift,
+                        R"(
+template <typename X, typename Y>
+ANGLE_ALWAYS_INLINE X ANGLE_ulshift(X x, Y y)
+{
+    return metal::select(X(0), x << (y & Y(31)), metal::make_unsigned_t<Y>(y) < metal::make_unsigned_t<Y>(32));
+}
+)")
+
+// Avoid undefined behavior in e1 >> e2 when:
+// 1) e2 is larger than the bit width of the type
+// 2) e2 is a negative value.
+PROGRAM_PRELUDE_DECLARE(rshift,
+                        R"(
+template <typename X, typename Y>
+ANGLE_ALWAYS_INLINE X ANGLE_rshift(X x, Y y)
+{
+    return metal::select(X(0), x >> (y & Y(31)), metal::make_unsigned_t<Y>(y) < metal::make_unsigned_t<Y>(32));
+}
+)")
+
 PROGRAM_PRELUDE_DECLARE(mixBool,
                         R"(
 template <typename T, int N>
@@ -3307,6 +3446,10 @@ void ProgramPrelude::visitOperator(TOperator op,
         case TOperator::EOpMod:
             mod();
             break;
+        case TOperator::EOpIModAssign:
+        case TOperator::EOpIMod:
+            imod();
+            break;
         case TOperator::EOpRefract:
             if (argType0->isScalar())
             {
@@ -3450,6 +3593,7 @@ void ProgramPrelude::visitOperator(TOperator op,
             break;
 
         case TOperator::EOpAdd:
+        case TOperator::EOpAddAssign:
             if (argType0->isMatrix() && argType1->isScalar())
             {
                 addMatrixScalar();
@@ -3458,16 +3602,14 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 addScalarMatrix();
             }
-            break;
-
-        case TOperator::EOpAddAssign:
-            if (argType0->isMatrix() && argType1->isScalar())
+            if (argType0->isSignedInt())
             {
-                addMatrixScalarAssign();
+                iadd();
             }
             break;
 
         case TOperator::EOpSub:
+        case TOperator::EOpSubAssign:
             if (argType0->isMatrix() && argType1->isScalar())
             {
                 subMatrixScalar();
@@ -3476,16 +3618,24 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 subScalarMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                isub();
+            }
             break;
 
-        case TOperator::EOpSubAssign:
-            if (argType0->isMatrix() && argType1->isScalar())
+        case TOperator::EOpMul:
+        case TOperator::EOpMulAssign:
+        case TOperator::EOpVectorTimesScalar:
+        case TOperator::EOpVectorTimesScalarAssign:
+            if (argType0->isSignedInt())
             {
-                subMatrixScalarAssign();
+                imul();
             }
             break;
 
         case TOperator::EOpDiv:
+        case TOperator::EOpDivAssign:
             if (argType0->isMatrix())
             {
                 if (argType1->isMatrix())
@@ -3497,23 +3647,13 @@ void ProgramPrelude::visitOperator(TOperator op,
                     divMatrixScalar();
                 }
             }
-            if (argType0->isScalar() && argType1->isMatrix())
+            else if (op == TOperator::EOpDiv && argType0->isScalar() && argType1->isMatrix())
             {
                 divScalarMatrix();
             }
-            break;
-
-        case TOperator::EOpDivAssign:
-            if (argType0->isMatrix())
-            {
-                if (argType1->isMatrix())
-                {
-                    componentWiseDivideAssign();
-                }
-                else if (argType1->isScalar())
+            else
             {
-                    divMatrixScalarAssign();
-                }
+                div();
             }
             break;
 
@@ -3584,20 +3724,31 @@ void ProgramPrelude::visitOperator(TOperator op,
             }
             break;
 
+        case TOperator::EOpBitShiftLeft:
+        case TOperator::EOpBitShiftLeftAssign:
+        {
+            if (argType0->isSignedInt())
+            {
+                ilshift();
+            }
+            else
+            {
+                ulshift();
+            }
+            break;
+        }
+
+        case TOperator::EOpBitShiftRight:
+        case TOperator::EOpBitShiftRightAssign:
+            rshift();
+            break;
+
         case TOperator::EOpComma:
         case TOperator::EOpAssign:
         case TOperator::EOpInitialize:
-        case TOperator::EOpMulAssign:
-        case TOperator::EOpIModAssign:
-        case TOperator::EOpBitShiftLeftAssign:
-        case TOperator::EOpBitShiftRightAssign:
         case TOperator::EOpBitwiseAndAssign:
         case TOperator::EOpBitwiseXorAssign:
         case TOperator::EOpBitwiseOrAssign:
-        case TOperator::EOpMul:
-        case TOperator::EOpIMod:
-        case TOperator::EOpBitShiftLeft:
-        case TOperator::EOpBitShiftRight:
         case TOperator::EOpBitwiseAnd:
         case TOperator::EOpBitwiseXor:
         case TOperator::EOpBitwiseOr:
@@ -3616,10 +3767,8 @@ void ProgramPrelude::visitOperator(TOperator op,
         case TOperator::EOpLogicalNot:
         case TOperator::EOpNotComponentWise:
         case TOperator::EOpBitwiseNot:
-        case TOperator::EOpVectorTimesScalarAssign:
         case TOperator::EOpVectorTimesMatrixAssign:
         case TOperator::EOpMatrixTimesScalarAssign:
-        case TOperator::EOpVectorTimesScalar:
         case TOperator::EOpVectorTimesMatrix:
         case TOperator::EOpMatrixTimesVector:
         case TOperator::EOpMatrixTimesScalar:
@@ -3787,6 +3936,17 @@ bool ProgramPrelude::visitAggregate(Visit visit, TIntermAggregate *node)
 
     const TFunction *func = node->getFunction();
 
+    if (node->isConstructor() && argCount == 1)
+    {
+        const TType &retType = node->getType();
+        const TType &argType = getArgType(0);
+        if (((retType.isScalar() || retType.isVector()) && IsInteger(retType.getBasicType())) &&
+            ((argType.isScalar() || argType.isVector()) && argType.getBasicType() == EbtFloat))
+        {
+            ftoi();
+        }
+    }
+
     switch (node->getChildCount())
     {
         case 0:
diff --git a/src/gpu_info_util/SystemInfo_internal.h b/src/gpu_info_util/SystemInfo_internal.h
index 5cef5d49eea1b1f1e6c22543446b2e5391ee88f2..5bfff7a4587126abada2d0a2560fb32cce79bd37 100644
--- a/src/gpu_info_util/SystemInfo_internal.h
+++ b/src/gpu_info_util/SystemInfo_internal.h
@@ -41,11 +41,10 @@ uint64_t GetGpuIDFromDisplayID(uint32_t displayID);
 uint64_t GetGpuIDFromOpenGLDisplayMask(uint32_t displayMask);
 #    endif
 
-#endif
-
-#if defined(ANGLE_PLATFORM_MACOS) && ANGLE_ENABLE_METAL
+#    if ANGLE_ENABLE_METAL
 // Get VendorID from metal device's registry ID
 VendorID GetVendorIDFromMetalDeviceRegistryID(uint64_t registryID);
+#    endif
 #endif
 
 
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 5125ccb76e68d89a0563e9fdc823947a520ace27..8fd55ae797675d7f35321c555e100530dbd50584 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -10010,6 +10010,33 @@ void Context::textureFoveationParameters(TextureID texturePacked,
     texture->setFocalPoint(layer, focalPoint, focalX, focalY, gainX, gainY, foveaArea);
 }
 
+void Context::framebufferResolveRenderbufferWEBKIT(GLenum target,
+                                                   GLenum attachment,
+                                                   GLenum renderbuffertarget,
+                                                   RenderbufferID renderbuffer)
+{
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    Framebuffer *framebuffer = mState.getTargetFramebuffer(target);
+    ASSERT(framebuffer);
+
+    if (renderbuffer.value != 0)
+    {
+        Renderbuffer *renderbufferObject = getRenderbuffer(renderbuffer);
+
+        framebuffer->setAttachmentResolve(this, GL_RENDERBUFFER, attachment, gl::ImageIndex(),
+                                          renderbufferObject);
+    }
+    else
+    {
+        framebuffer->resetAttachmentResolve(this, attachment);
+    }
+
+    mState.setObjectDirty(target);
+#else
+    UNIMPLEMENTED();
+#endif
+}
+
 void Context::endTiling(GLbitfield preserveMask)
 {
     ANGLE_CONTEXT_TRY(mImplementation->endTiling(this, preserveMask));
@@ -10084,6 +10111,14 @@ void Context::blobCacheCallbacks(GLSETBLOBPROCANGLE set,
     mState.getBlobCacheCallbacks() = {set, get, userParam};
 }
 
+void Context::bindMetalRasterizationRateMap(GLuint renderbufferHandle, GLMTLRasterizationRateMapANGLE map)
+{
+    Renderbuffer *renderbuffer = getRenderbuffer({renderbufferHandle});
+    rx::RenderbufferImpl *renderbufferImpl = renderbuffer ? renderbuffer->getImplementation() : nullptr;
+    ANGLE_CONTEXT_TRY(mImplementation->bindMetalRasterizationRateMap(this, renderbufferImpl, map));
+    getMutablePrivateState()->setVariableRasterizationRateMap(map);
+}
+
 void Context::texStorageAttribs2D(GLenum target,
                                   GLsizei levels,
                                   GLenum internalFormat,
diff --git a/src/libANGLE/Context_gles_ext_autogen.h b/src/libANGLE/Context_gles_ext_autogen.h
index a2293df8f55585ea7c46d7e1a7d153a2a5b26b33..3f2aa94500f5c58dda772a211c68e5135c4edf45 100644
--- a/src/libANGLE/Context_gles_ext_autogen.h
+++ b/src/libANGLE/Context_gles_ext_autogen.h
@@ -648,6 +648,8 @@
     void invalidateTexture(TextureType targetPacked);                                              \
     /* GL_ANGLE_texture_multisample */                                                             \
     /* GL_ANGLE_texture_rectangle */                                                               \
+    /* GL_ANGLE_variable_rasterization_rate_metal */                                               \
+    void bindMetalRasterizationRateMap(GLuint renderbuffer, GLMTLRasterizationRateMapANGLE map);   \
     /* GL_ANGLE_vulkan_image */                                                                    \
     void acquireTextures(GLuint numTextures, const TextureID *texturesPacked,                      \
                          const GLenum *layouts);                                                   \
@@ -675,6 +677,10 @@
     /* GL_CHROMIUM_framebuffer_mixed_samples */                                                    \
     /* GL_CHROMIUM_lose_context */                                                                 \
     void loseContext(GraphicsResetStatus currentPacked, GraphicsResetStatus otherPacked);          \
-    /* GL_CHROMIUM_sync_query */
+    /* GL_CHROMIUM_sync_query */                                                                   \
+    /* GL_WEBKIT_explicit_resolve_target */                                                        \
+    void framebufferResolveRenderbufferWEBKIT(GLenum target, GLenum attachment,                    \
+                                              GLenum renderbuffertarget,                           \
+                                              RenderbufferID renderbufferPacked);
 
 #endif  // ANGLE_CONTEXT_API_EXT_AUTOGEN_H_
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index 1b01eac27a09a2889ce6b36e3049b4ea44169da6..78bf77003eb171dba29d63ca721ed66cf9dd82ac 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -164,6 +164,47 @@ FramebufferStatus CheckAttachmentCompleteness(const Context *context,
     return FramebufferStatus::Complete();
 }
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+// Check the |checkAttachment| in reference to |firstAttachment| for the sake of explicit resolve
+// target framebuffer completeness.
+FramebufferStatus CheckResolveTargetMatchesForCompleteness(
+    const Context *context,
+    const FramebufferAttachment &firstAttachment,
+    const FramebufferAttachment &checkAttachment)
+{
+    ASSERT(firstAttachment.isAttached() && checkAttachment.isAttached());
+
+    FramebufferStatus attachmentCompleteness =
+        CheckAttachmentCompleteness(context, checkAttachment);
+    if (!attachmentCompleteness.isComplete())
+    {
+        return attachmentCompleteness;
+    }
+
+    if (checkAttachment.getSamples() != 0)
+    {
+        return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNSUPPORTED,
+            "Framebuffer is incomplete: Resolve attachments have multiple samples.");
+    }
+
+    if (firstAttachment.getSize() != checkAttachment.getSize())
+    {
+        return gl::FramebufferStatus::Incomplete(
+            GL_FRAMEBUFFER_UNSUPPORTED,
+            gl::err::kFramebufferIncompleteUnsupportedMissmatchedDimensions);
+    }
+
+    if (!Format::EquivalentForBlit(firstAttachment.getFormat(), checkAttachment.getFormat()))
+    {
+        return gl::FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNSUPPORTED,
+                                                 "Framebuffer is incomplete: Attempting to resolve "
+                                                 "to target with non-equivalent format for blit");
+    }
+
+    return FramebufferStatus::Complete();
+}
+#endif
+
 FramebufferStatus CheckAttachmentSampleCounts(const Context *context,
                                               GLsizei currAttachmentSamples,
                                               GLsizei samples,
@@ -371,6 +412,9 @@ FramebufferState::FramebufferState(rx::UniqueSerial serial)
       mFramebufferSerial(serial),
       mLabel(),
       mColorAttachments(1),
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+      mColorResolveAttachments(1),
+#endif
       mColorAttachmentsMask(0),
       mDrawBufferStates(1, GL_BACK),
       mReadBufferState(GL_BACK),
@@ -394,6 +438,9 @@ FramebufferState::FramebufferState(const Caps &caps, FramebufferID id, rx::Uniqu
       mFramebufferSerial(serial),
       mLabel(),
       mColorAttachments(caps.maxColorAttachments),
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+      mColorResolveAttachments(caps.maxColorAttachments),
+#endif
       mColorAttachmentsMask(0),
       mDrawBufferStates(caps.maxDrawBuffers, GL_NONE),
       mReadBufferState(GL_COLOR_ATTACHMENT0_EXT),
@@ -600,6 +647,27 @@ const FramebufferAttachment *FramebufferState::getDepthStencilAttachment() const
     return nullptr;
 }
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+const FramebufferAttachment *FramebufferState::getColorResolveAttachment(
+    size_t colorAttachment) const
+{
+    ASSERT(colorAttachment < mColorResolveAttachments.size());
+    return mColorResolveAttachments[colorAttachment].isAttached()
+               ? &mColorResolveAttachments[colorAttachment]
+               : nullptr;
+}
+
+const FramebufferAttachment *FramebufferState::getDepthResolveAttachment() const
+{
+    return mDepthResolveAttachment.isAttached() ? &mDepthResolveAttachment : nullptr;
+}
+
+const FramebufferAttachment *FramebufferState::getStencilResolveAttachment() const
+{
+    return mStencilResolveAttachment.isAttached() ? &mStencilResolveAttachment : nullptr;
+}
+#endif
+
 const Extents FramebufferState::getAttachmentExtentsIntersection() const
 {
     int32_t width  = std::numeric_limits<int32_t>::max();
@@ -830,6 +898,10 @@ Framebuffer::Framebuffer(const Context *context, rx::GLImplFactory *factory, Fra
     ASSERT(mImpl != nullptr);
     ASSERT(mState.mColorAttachments.size() ==
            static_cast<size_t>(context->getCaps().maxColorAttachments));
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    ASSERT(mState.mColorResolveAttachments.size() ==
+           static_cast<size_t>(context->getCaps().maxColorAttachments));
+#endif
 
     for (uint32_t colorIndex = 0;
          colorIndex < static_cast<uint32_t>(mState.mColorAttachments.size()); ++colorIndex)
@@ -864,6 +936,15 @@ void Framebuffer::onDestroy(const Context *context)
     mState.mWebGLStencilAttachment.detach(context, mState.mFramebufferSerial);
     mState.mWebGLDepthStencilAttachment.detach(context, mState.mFramebufferSerial);
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    for (auto &attachment : mState.mColorResolveAttachments)
+    {
+        attachment.detach(context, mState.mFramebufferSerial);
+    }
+    mState.mDepthResolveAttachment.detach(context, mState.mFramebufferSerial);
+    mState.mStencilResolveAttachment.detach(context, mState.mFramebufferSerial);
+#endif
+
     if (mPixelLocalStorage)
     {
         mPixelLocalStorage->onFramebufferDestroyed(context);
@@ -1153,6 +1234,23 @@ size_t Framebuffer::getDrawbufferStateCount() const
     return mState.mDrawBufferStates.size();
 }
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+const FramebufferAttachment *Framebuffer::getColorResolveAttachment(size_t colorAttachment) const
+{
+    return mState.getColorResolveAttachment(colorAttachment);
+}
+
+const FramebufferAttachment *Framebuffer::getDepthResolveAttachment() const
+{
+    return mState.getDepthResolveAttachment();
+}
+
+const FramebufferAttachment *Framebuffer::getStencilResolveAttachment() const
+{
+    return mState.getStencilResolveAttachment();
+}
+#endif
+
 GLenum Framebuffer::getDrawBufferState(size_t drawBuffer) const
 {
     ASSERT(drawBuffer < mState.mDrawBufferStates.size());
@@ -1404,6 +1502,25 @@ FramebufferStatus Framebuffer::checkStatusWithGLFrontEnd(const Context *context)
         }
     }
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    for (size_t index = 0; index < mState.mColorAttachments.size(); ++index)
+    {
+        const FramebufferAttachment &colorAttachment = mState.mColorAttachments[index];
+        const FramebufferAttachment &colorResolveAttachment =
+            mState.mColorResolveAttachments[index];
+        if (colorResolveAttachment.isAttached() && colorAttachment.isAttached())
+        {
+            FramebufferStatus resolveAttachmentCompleteness =
+                CheckResolveTargetMatchesForCompleteness(context, colorAttachment,
+                                                         colorResolveAttachment);
+            if (!resolveAttachmentCompleteness.isComplete())
+            {
+                return resolveAttachmentCompleteness;
+            }
+        }
+    }
+#endif
+
     const FramebufferAttachment &depthAttachment = mState.mDepthAttachment;
     if (depthAttachment.isAttached())
     {
@@ -1457,6 +1574,20 @@ FramebufferStatus Framebuffer::checkStatusWithGLFrontEnd(const Context *context)
                     err::kFramebufferIncompleteMismatchedLayeredAttachments);
             }
         }
+
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+        const FramebufferAttachment &depthResolveAttachment = mState.mDepthResolveAttachment;
+        if (depthResolveAttachment.isAttached())
+        {
+            FramebufferStatus resolveAttachmentCompleteness =
+                CheckResolveTargetMatchesForCompleteness(context, depthAttachment,
+                                                         depthResolveAttachment);
+            if (!resolveAttachmentCompleteness.isComplete())
+            {
+                return resolveAttachmentCompleteness;
+            }
+        }
+#endif
     }
 
     const FramebufferAttachment &stencilAttachment = mState.mStencilAttachment;
@@ -1512,6 +1643,20 @@ FramebufferStatus Framebuffer::checkStatusWithGLFrontEnd(const Context *context)
                     err::kFramebufferIncompleteMismatchedLayeredAttachments);
             }
         }
+
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+        const FramebufferAttachment &stencilResolveAttachment = mState.mStencilResolveAttachment;
+        if (stencilResolveAttachment.isAttached())
+        {
+            FramebufferStatus resolveAttachmentCompleteness =
+                CheckResolveTargetMatchesForCompleteness(context, stencilAttachment,
+                                                         stencilResolveAttachment);
+            if (!resolveAttachmentCompleteness.isComplete())
+            {
+                return resolveAttachmentCompleteness;
+            }
+        }
+#endif
     }
 
     // Starting from ES 3.0 stencil and depth, if present, should be the same image
@@ -1982,6 +2127,66 @@ void Framebuffer::setAttachmentMultiview(const Context *context,
                   FramebufferAttachment::kDefaultRenderToTextureSamples);
 }
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+void Framebuffer::setAttachmentResolve(const Context *context,
+                                       GLenum type,
+                                       GLenum binding,
+                                       const ImageIndex &textureIndex,
+                                       FramebufferAttachmentObject *resource)
+{
+    switch (binding)
+    {
+        case GL_DEPTH_STENCIL:
+        case GL_DEPTH_STENCIL_ATTACHMENT:
+            updateAttachmentResolve(context, &mState.mDepthResolveAttachment,
+                                    DIRTY_BIT_DEPTH_ATTACHMENT, &mDirtyDepthAttachmentBinding, type,
+                                    binding, textureIndex, resource);
+            updateAttachmentResolve(context, &mState.mStencilResolveAttachment,
+                                    DIRTY_BIT_STENCIL_ATTACHMENT, &mDirtyStencilAttachmentBinding,
+                                    type, binding, textureIndex, resource);
+            break;
+        case GL_DEPTH:
+        case GL_DEPTH_ATTACHMENT:
+            updateAttachmentResolve(context, &mState.mDepthResolveAttachment,
+                                    DIRTY_BIT_DEPTH_ATTACHMENT, &mDirtyDepthAttachmentBinding, type,
+                                    binding, textureIndex, resource);
+            if (context->isWebGL1())
+            {
+                updateAttachmentResolve(context, &mState.mStencilResolveAttachment,
+                                        DIRTY_BIT_STENCIL_ATTACHMENT,
+                                        &mDirtyStencilAttachmentBinding, GL_NONE,
+                                        GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr);
+            }
+            break;
+
+        case GL_STENCIL:
+        case GL_STENCIL_ATTACHMENT:
+            updateAttachmentResolve(context, &mState.mStencilResolveAttachment,
+                                    DIRTY_BIT_STENCIL_ATTACHMENT, &mDirtyStencilAttachmentBinding,
+                                    type, binding, textureIndex, resource);
+            if (context->isWebGL1())
+            {
+                updateAttachmentResolve(context, &mState.mDepthResolveAttachment,
+                                        DIRTY_BIT_DEPTH_ATTACHMENT, &mDirtyDepthAttachmentBinding,
+                                        GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr);
+            }
+            break;
+
+        default:
+        {
+            const size_t colorIndex = binding - GL_COLOR_ATTACHMENT0;
+            ASSERT(colorIndex < mState.mColorResolveAttachments.size());
+
+            const size_t dirtyBit = DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex;
+            updateAttachmentResolve(context, &mState.mColorResolveAttachments[colorIndex], dirtyBit,
+                                    &mDirtyColorAttachmentBindings[colorIndex], type, binding,
+                                    textureIndex, resource);
+        }
+        break;
+    }
+}
+#endif
+
 void Framebuffer::commitWebGL1DepthStencilIfConsistent(const Context *context,
                                                        GLsizei numViews,
                                                        GLuint baseViewIndex,
@@ -2159,11 +2364,40 @@ void Framebuffer::updateAttachment(const Context *context,
     invalidateCompletenessCache();
 }
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+void Framebuffer::updateAttachmentResolve(const Context *context,
+                                          FramebufferAttachment *attachment,
+                                          size_t dirtyBit,
+                                          angle::ObserverBinding *onDirtyBinding,
+                                          GLenum type,
+                                          GLenum binding,
+                                          const ImageIndex &textureIndex,
+                                          FramebufferAttachmentObject *resource)
+{
+    attachment->attach(
+        context, type, binding, textureIndex, resource, FramebufferAttachment::kDefaultNumViews,
+        FramebufferAttachment::kDefaultBaseViewIndex, false,
+        FramebufferAttachment::kDefaultRenderToTextureSamples, mState.mFramebufferSerial);
+    mDirtyBits.set(dirtyBit);
+    onDirtyBinding->bind(resource);
+    mAttachmentChangedAfterEnablingFoveation = isFoveationEnabled();
+
+    invalidateCompletenessCache();
+}
+#endif
+
 void Framebuffer::resetAttachment(const Context *context, GLenum binding)
 {
     setAttachment(context, GL_NONE, binding, ImageIndex(), nullptr);
 }
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+void Framebuffer::resetAttachmentResolve(const Context *context, GLenum binding)
+{
+    setAttachmentResolve(context, GL_NONE, binding, ImageIndex(), nullptr);
+}
+#endif
+
 void Framebuffer::setWriteControlMode(SrgbWriteControlMode srgbWriteControlMode)
 {
     if (srgbWriteControlMode != mState.getWriteControlMode())
@@ -2806,4 +3040,5 @@ angle::Result Framebuffer::syncAttachmentState(const Context *context,
 
     return angle::Result::Continue;
 }
+
 }  // namespace gl
diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h
index 3d565721c0bfabfea20ee57b933971ae7231069f..03ac251016f4880cbbb2531746fd3cc9c5ee32a0 100644
--- a/src/libANGLE/Framebuffer.h
+++ b/src/libANGLE/Framebuffer.h
@@ -82,6 +82,13 @@ class FramebufferState final : angle::NonCopyable
     const FramebufferAttachment *getDepthStencilAttachment() const;
     const FramebufferAttachment *getReadPixelsAttachment(GLenum readFormat) const;
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    // GL_WEBKIT_explicit_resolve_target
+    const FramebufferAttachment *getColorResolveAttachment(size_t colorAttachment) const;
+    const FramebufferAttachment *getDepthResolveAttachment() const;
+    const FramebufferAttachment *getStencilResolveAttachment() const;
+#endif
+
     const DrawBuffersVector<GLenum> &getDrawBufferStates() const { return mDrawBufferStates; }
     DrawBufferMask getEnabledDrawBuffers() const { return mEnabledDrawBuffers; }
     GLenum getReadBufferState() const { return mReadBufferState; }
@@ -163,6 +170,13 @@ class FramebufferState final : angle::NonCopyable
     FramebufferAttachment mDepthAttachment;
     FramebufferAttachment mStencilAttachment;
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    // GL_WEBKIT_explicit_resolve_target;
+    DrawBuffersVector<FramebufferAttachment> mColorResolveAttachments;
+    FramebufferAttachment mDepthResolveAttachment;
+    FramebufferAttachment mStencilResolveAttachment;
+#endif
+
     // Tracks all the color buffers attached to this FramebufferDesc
     DrawBufferMask mColorAttachmentsMask;
 
@@ -245,6 +259,16 @@ class Framebuffer final : public angle::ObserverInterface,
                                 GLint baseViewIndex);
     void resetAttachment(const Context *context, GLenum binding);
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    // GL_WEBKIT_explicit_resolve_target
+    void setAttachmentResolve(const Context *context,
+                              GLenum type,
+                              GLenum binding,
+                              const ImageIndex &textureIndex,
+                              FramebufferAttachmentObject *resource);
+    void resetAttachmentResolve(const Context *context, GLenum binding);
+#endif
+
     bool detachTexture(Context *context, TextureID texture);
     bool detachRenderbuffer(Context *context, RenderbufferID renderbuffer);
 
@@ -259,9 +283,16 @@ class Framebuffer final : public angle::ObserverInterface,
     const FramebufferAttachment *getFirstColorAttachment() const;
     const FramebufferAttachment *getFirstNonNullAttachment() const;
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    // GL_WEBKIT_explicit_resolve_target
+    const FramebufferAttachment *getColorResolveAttachment(size_t colorAttachment) const;
+    const FramebufferAttachment *getDepthResolveAttachment() const;
+    const FramebufferAttachment *getStencilResolveAttachment() const;
+#endif
+
     const DrawBuffersVector<FramebufferAttachment> &getColorAttachments() const
     {
-        return mState.mColorAttachments;
+        return mState.getColorAttachments();
     }
 
     const FramebufferState &getState() const { return mState; }
@@ -524,6 +555,18 @@ class Framebuffer final : public angle::ObserverInterface,
                           bool isMultiview,
                           GLsizei samples);
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    // GL_WEBKIT_explicit_resolve_target
+    void updateAttachmentResolve(const Context *context,
+                                 FramebufferAttachment *attachment,
+                                 size_t dirtyBit,
+                                 angle::ObserverBinding *onDirtyBinding,
+                                 GLenum type,
+                                 GLenum binding,
+                                 const ImageIndex &textureIndex,
+                                 FramebufferAttachmentObject *resource);
+#endif
+
     void markAttachmentsInitialized(const DrawBufferMask &color, bool depth, bool stencil);
 
     // Checks that we have a partially masked clear:
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index 6be85b5742e89333f3b9c8f101ddb07900f18248..ff3c420a05bf99ae0f6cdccb52edf9cc8efdb93b 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -6,6 +6,9 @@
 
 // State.cpp: Implements the State class, encapsulating raw GL state.
 
+// Older clang versions have a false positive on this warning here.
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+
 #include "libANGLE/State.h"
 
 #include <string.h>
@@ -367,6 +370,8 @@ PrivateState::PrivateState(const Version &clientVersion,
       mLogicOp(LogicalOperation::Copy),
       mPatchVertices(3),
       mPixelLocalStorageActivePlanes(0),
+      mVariableRasterizationRateEnabled(false),
+      mVariableRasterizationRateMap(nullptr),
       mNoSimultaneousConstantColorAndAlphaBlendFunc(false),
       mSetBlendIndexedInvoked(false),
       mSetBlendFactorsIndexedInvoked(false),
@@ -1302,6 +1307,26 @@ void PrivateState::setLogicOp(LogicalOperation opcode)
     }
 }
 
+void PrivateState::setVariableRasterizationRateEnabled(bool enabled)
+{
+    if (mVariableRasterizationRateEnabled != enabled)
+    {
+        mVariableRasterizationRateEnabled = enabled;
+        mDirtyBits.set(state::DIRTY_BIT_EXTENDED);
+        mExtendedDirtyBits.set(state::EXTENDED_DIRTY_BIT_VARIABLE_RASTERIZATION_RATE);
+    }
+}
+
+void PrivateState::setVariableRasterizationRateMap(GLMTLRasterizationRateMapANGLE map)
+{
+    if (mVariableRasterizationRateMap != map)
+    {
+        mVariableRasterizationRateMap = map;
+        mDirtyBits.set(state::DIRTY_BIT_EXTENDED);
+        mExtendedDirtyBits.set(state::EXTENDED_DIRTY_BIT_VARIABLE_RASTERIZATION_RATE);
+    }
+}
+
 void PrivateState::setVertexAttribf(GLuint index, const GLfloat values[4])
 {
     ASSERT(static_cast<size_t>(index) < mVertexAttribCurrentValues.size());
@@ -1434,6 +1459,9 @@ void PrivateState::setEnableFeature(GLenum feature, bool enabled)
         case GL_FETCH_PER_SAMPLE_ARM:
             mFetchPerSample = enabled;
             return;
+        case GL_VARIABLE_RASTERIZATION_RATE_ANGLE:
+            setVariableRasterizationRateEnabled(enabled);
+            return;
         default:
             break;
     }
@@ -1600,6 +1628,8 @@ bool PrivateState::getEnableFeature(GLenum feature) const
             return mShadingRatePreserveAspectRatio;
         case GL_FETCH_PER_SAMPLE_ARM:
             return mFetchPerSample;
+        case GL_VARIABLE_RASTERIZATION_RATE_ANGLE:
+            return mVariableRasterizationRateEnabled;
     }
 
     ASSERT(mClientVersion.major == 1);
@@ -3485,6 +3515,9 @@ void State::getPointerv(const Context *context, GLenum pname, void **params) con
         case GL_BLOB_CACHE_USER_PARAM_ANGLE:
             *params = const_cast<void *>(getBlobCacheCallbacks().userParam);
             break;
+        case GL_METAL_RASTERIZATION_RATE_MAP_BINDING_ANGLE:
+            *params = privateState().getVariableRasterizationRateMap();
+            break;
         default:
             UNREACHABLE();
             break;
diff --git a/src/libANGLE/State.h b/src/libANGLE/State.h
index 4f360a49d53488ddddc9283dc0b2bd336beddfcb..c5ec62d999cd7687544b78cf2ab470facba42b6c 100644
--- a/src/libANGLE/State.h
+++ b/src/libANGLE/State.h
@@ -180,6 +180,7 @@ enum ExtendedDirtyBitType
     EXTENDED_DIRTY_BIT_LOGIC_OP_ENABLED,              // ANGLE_logic_op
     EXTENDED_DIRTY_BIT_LOGIC_OP,                      // ANGLE_logic_op
     EXTENDED_DIRTY_BIT_BLEND_ADVANCED_COHERENT,       // KHR_blend_operation_advanced_coherent
+    EXTENDED_DIRTY_BIT_VARIABLE_RASTERIZATION_RATE,   // ANGLE_variable_rasterization_rate_metal
 
     EXTENDED_DIRTY_BIT_INVALID,
     EXTENDED_DIRTY_BIT_MAX = EXTENDED_DIRTY_BIT_INVALID,
@@ -480,6 +481,15 @@ class PrivateState : angle::NonCopyable
     bool hasActivelyOverriddenPLSDrawBuffers(GLint *firstActivePLSDrawBuffer) const;
     bool isActivelyOverriddenPLSDrawBuffer(GLint drawbuffer) const;
 
+    // GL_ANGLE_variable_rasterization_rate_metal
+    void setVariableRasterizationRateEnabled(bool enabled);
+    bool isVariableRasterizationRateEnabled() const { return mVariableRasterizationRateEnabled; }
+    void setVariableRasterizationRateMap(GLMTLRasterizationRateMapANGLE map);
+    GLMTLRasterizationRateMapANGLE getVariableRasterizationRateMap() const
+    {
+        return mVariableRasterizationRateMap;
+    }
+
     // Line width state setter
     void setLineWidth(GLfloat width);
     float getLineWidth() const { return mLineWidth; }
@@ -710,6 +720,10 @@ class PrivateState : angle::NonCopyable
     // GL_ANGLE_shader_pixel_local_storage
     GLsizei mPixelLocalStorageActivePlanes;
 
+    // GL_ANGLE_variable_rasterization_rate_metal
+    bool mVariableRasterizationRateEnabled;
+    GLMTLRasterizationRateMapANGLE mVariableRasterizationRateMap;
+
     // GLES1 emulation: state specific to GLES1
     GLES1State mGLES1State;
 
diff --git a/src/libANGLE/capture/capture_gles_ext_autogen.cpp b/src/libANGLE/capture/capture_gles_ext_autogen.cpp
index e510fed316199f4e253bbac97ca8cff764909b6e..dfcf1327f754a50e3bf0e3fa31266feb332d244d 100644
--- a/src/libANGLE/capture/capture_gles_ext_autogen.cpp
+++ b/src/libANGLE/capture/capture_gles_ext_autogen.cpp
@@ -5221,6 +5221,18 @@ CallCapture CaptureGetTranslatedShaderSourceANGLE(const State &glState,
     return CallCapture(angle::EntryPoint::GLGetTranslatedShaderSourceANGLE, std::move(paramBuffer));
 }
 
+CallCapture CaptureBindMetalRasterizationRateMapANGLE(const State &glState,
+                                                      bool isCallValid,
+                                                      GLMTLRasterizationRateMapANGLE map)
+{
+    ParamBuffer paramBuffer;
+
+    paramBuffer.addValueParam("map", ParamType::TGLMTLRasterizationRateMapANGLE, map);
+
+    return CallCapture(angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE,
+                       std::move(paramBuffer));
+}
+
 CallCapture CaptureAcquireTexturesANGLE(const State &glState,
                                         bool isCallValid,
                                         GLuint numTextures,
@@ -12380,4 +12392,23 @@ CallCapture CaptureStartTilingQCOM(const State &glState,
     return CallCapture(angle::EntryPoint::GLStartTilingQCOM, std::move(paramBuffer));
 }
 
+CallCapture CaptureFramebufferResolveRenderbufferWEBKIT(const State &glState,
+                                                        bool isCallValid,
+                                                        GLenum target,
+                                                        GLenum attachment,
+                                                        GLenum renderbuffertarget,
+                                                        RenderbufferID renderbufferPacked)
+{
+    ParamBuffer paramBuffer;
+
+    paramBuffer.addEnumParam("target", GLESEnum::AllEnums, ParamType::TGLenum, target);
+    paramBuffer.addEnumParam("attachment", GLESEnum::AllEnums, ParamType::TGLenum, attachment);
+    paramBuffer.addEnumParam("renderbuffertarget", GLESEnum::AllEnums, ParamType::TGLenum,
+                             renderbuffertarget);
+    paramBuffer.addValueParam("renderbufferPacked", ParamType::TRenderbufferID, renderbufferPacked);
+
+    return CallCapture(angle::EntryPoint::GLFramebufferResolveRenderbufferWEBKIT,
+                       std::move(paramBuffer));
+}
+
 }  // namespace gl
diff --git a/src/libANGLE/capture/capture_gles_ext_autogen.h b/src/libANGLE/capture/capture_gles_ext_autogen.h
index 2677e454a623342e515464d1713a547bc0aa248d..86ecf56bf05b104ba946533eaf6fa7c629a33702 100644
--- a/src/libANGLE/capture/capture_gles_ext_autogen.h
+++ b/src/libANGLE/capture/capture_gles_ext_autogen.h
@@ -1004,6 +1004,11 @@ angle::CallCapture CaptureGetTranslatedShaderSourceANGLE(const State &glState,
                                                          GLsizei *length,
                                                          GLchar *source);
 
+// GL_ANGLE_variable_rasterization_rate_metal
+angle::CallCapture CaptureBindMetalRasterizationRateMapANGLE(const State &glState,
+                                                             bool isCallValid,
+                                                             GLMTLRasterizationRateMapANGLE map);
+
 // GL_ANGLE_vulkan_image
 angle::CallCapture CaptureAcquireTexturesANGLE(const State &glState,
                                                bool isCallValid,
@@ -2930,6 +2935,14 @@ angle::CallCapture CaptureStartTilingQCOM(const State &glState,
                                           GLuint height,
                                           GLbitfield preserveMask);
 
+// GL_WEBKIT_explicit_resolve_target
+angle::CallCapture CaptureFramebufferResolveRenderbufferWEBKIT(const State &glState,
+                                                               bool isCallValid,
+                                                               GLenum target,
+                                                               GLenum attachment,
+                                                               GLenum renderbuffertarget,
+                                                               RenderbufferID renderbufferPacked);
+
 // Parameter Captures
 
 void CaptureDeletePerfMonitorsAMD_monitors(const State &glState,
diff --git a/src/libANGLE/features.h b/src/libANGLE/features.h
index a6fe076c72d043dba3fc347bf020ebf8ec895f51..add46e99c91d19bbd790d2de0d8768b8953d633e 100644
--- a/src/libANGLE/features.h
+++ b/src/libANGLE/features.h
@@ -44,4 +44,11 @@
 #    define ANGLE_PROGRAM_LINK_VALIDATE_UNIFORM_PRECISION 1
 #endif
 
+// Lose context on Metal command queue error
+// ENABLED check Metal command buffer status on completion for error and lose context on error.
+// DISABLED Metal backed contexts are never lost.
+#if !defined(ANGLE_METAL_LOSE_CONTEXT_ON_ERROR)
+#    define ANGLE_METAL_LOSE_CONTEXT_ON_ERROR ANGLE_ENABLED
+#endif
+
 #endif  // LIBANGLE_FEATURES_H_
diff --git a/src/libANGLE/formatutils.cpp b/src/libANGLE/formatutils.cpp
index c2a180b1d83a8e0a654f015329c1032cd04ffe5d..f5df2e2809da989ab15a8e495d08f909e7dea527 100644
--- a/src/libANGLE/formatutils.cpp
+++ b/src/libANGLE/formatutils.cpp
@@ -587,7 +587,8 @@ static GLenum EquivalentBlitInternalFormat(GLenum internalformat)
     // sized, even if there is a swizzle (for example, blitting from a
     // multisampled RGBA8 renderbuffer to a BGRA8 texture). This could
     // be expanded and/or autogenerated if that is found necessary.
-    if (internalformat == GL_BGRA8_EXT || internalformat == GL_BGRA8_SRGB_ANGLEX)
+    if (internalformat == GL_SRGB8_ALPHA8_EXT || internalformat == GL_BGRA8_EXT ||
+        internalformat == GL_BGRA8_SRGB_ANGLEX)
     {
         return GL_RGBA8;
     }
diff --git a/src/libANGLE/gles_extensions_autogen.cpp b/src/libANGLE/gles_extensions_autogen.cpp
index dab7ecf2b7f878d0db1b2d5c34243ad6bf8bb734..de5b3024acc6ebb94477cd5cfc04bf7b084af067 100644
--- a/src/libANGLE/gles_extensions_autogen.cpp
+++ b/src/libANGLE/gles_extensions_autogen.cpp
@@ -242,6 +242,7 @@ const ExtensionInfoMap &GetExtensionInfoMap()
         map["GL_CHROMIUM_copy_compressed_texture"] = esOnlyExtension(&Extensions::copyCompressedTextureCHROMIUM);
         map["GL_CHROMIUM_copy_texture"] = esOnlyExtension(&Extensions::copyTextureCHROMIUM);
         map["GL_ANGLE_copy_texture_3d"] = enableableExtension(&Extensions::copyTexture3dANGLE);
+        map["GL_WEBKIT_explicit_resolve_target"] = enableableExtension(&Extensions::explicitResolveTargetWEBKIT);
         map["GL_CHROMIUM_framebuffer_mixed_samples"] = esOnlyExtension(&Extensions::framebufferMixedSamplesCHROMIUM);
         map["GL_ANGLE_framebuffer_multisample"] = enableableExtension(&Extensions::framebufferMultisampleANGLE);
         map["GL_ANGLE_get_image"] = enableableExtension(&Extensions::getImageANGLE);
@@ -279,6 +280,7 @@ const ExtensionInfoMap &GetExtensionInfoMap()
         map["GL_ANGLE_texture_external_update"] = enableableExtension(&Extensions::textureExternalUpdateANGLE);
         map["GL_ANGLE_texture_multisample"] = enableableExtension(&Extensions::textureMultisampleANGLE);
         map["GL_ANGLE_texture_rectangle"] = enableableDisablableExtension(&Extensions::textureRectangleANGLE);
+        map["GL_ANGLE_variable_rasterization_rate_metal"] = enableableExtension(&Extensions::variableRasterizationRateMetalANGLE);
         map["GL_ANGLE_vulkan_image"] = enableableExtension(&Extensions::vulkanImageANGLE);
         map["GL_ANGLE_webgl_compatibility"] = esOnlyExtension(&Extensions::webglCompatibilityANGLE);
         map["GL_ANGLE_yuv_internal_format"] = enableableExtension(&Extensions::yuvInternalFormatANGLE);
diff --git a/src/libANGLE/gles_extensions_autogen.h b/src/libANGLE/gles_extensions_autogen.h
index fb1a993e026a836c4f9c885f36ffa571cbf120db..40baf05acaa3358a8469699a084c84615138b746 100644
--- a/src/libANGLE/gles_extensions_autogen.h
+++ b/src/libANGLE/gles_extensions_autogen.h
@@ -686,6 +686,9 @@ struct Extensions
     // GL_ANGLE_copy_texture_3d
     bool copyTexture3dANGLE = false;
 
+    // GL_WEBKIT_explicit_resolve_target
+    bool explicitResolveTargetWEBKIT = false;
+
     // GL_CHROMIUM_framebuffer_mixed_samples
     bool framebufferMixedSamplesCHROMIUM = false;
 
@@ -797,6 +800,9 @@ struct Extensions
     // GL_ANGLE_texture_rectangle
     bool textureRectangleANGLE = false;
 
+    // GL_ANGLE_variable_rasterization_rate_metal
+    bool variableRasterizationRateMetalANGLE = false;
+
     // GL_ANGLE_vulkan_image
     bool vulkanImageANGLE = false;
 
diff --git a/src/libANGLE/renderer/ContextImpl.cpp b/src/libANGLE/renderer/ContextImpl.cpp
index 122dd5364a8428a7177a43c8948dc9e50e2483bc..5ec7a87d2e9902362a4423eb29897effdd93b883 100644
--- a/src/libANGLE/renderer/ContextImpl.cpp
+++ b/src/libANGLE/renderer/ContextImpl.cpp
@@ -103,4 +103,13 @@ const angle::PerfMonitorCounterGroups &ContextImpl::getPerfMonitorCounters()
     static angle::base::NoDestructor<angle::PerfMonitorCounterGroups> sCounters;
     return *sCounters;
 }
+
+angle::Result ContextImpl::bindMetalRasterizationRateMap(gl::Context *,
+                                                         RenderbufferImpl *renderbuffer,
+                                                         GLMTLRasterizationRateMapANGLE map)
+{
+    UNREACHABLE();
+    return angle::Result::Stop;
+}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/ContextImpl.h b/src/libANGLE/renderer/ContextImpl.h
index 1cd8e4f39642d14616e98f9628bf50d92ea9e197..c8c20081e1b1802187b0eabbd5c096873c31a354 100644
--- a/src/libANGLE/renderer/ContextImpl.h
+++ b/src/libANGLE/renderer/ContextImpl.h
@@ -281,6 +281,11 @@ class ContextImpl : public GLImplFactory
     // AMD_performance_monitor
     virtual const angle::PerfMonitorCounterGroups &getPerfMonitorCounters();
 
+    // GL_ANGLE_variable_rasterization_rate_metal
+    virtual angle::Result bindMetalRasterizationRateMap(gl::Context *,
+                                                        RenderbufferImpl *renderbuffer,
+                                                        GLMTLRasterizationRateMapANGLE map);
+
   protected:
     const gl::State &mState;
     gl::MemoryProgramCache *mMemoryProgramCache;
diff --git a/src/libANGLE/renderer/gl/FunctionsGL.cpp b/src/libANGLE/renderer/gl/FunctionsGL.cpp
index 9d8c7a4f4be3f1afd54163e2aacb12b9a832664e..9c3661be5fb3f51c15404bad23a1942a9eb2a6a4 100644
--- a/src/libANGLE/renderer/gl/FunctionsGL.cpp
+++ b/src/libANGLE/renderer/gl/FunctionsGL.cpp
@@ -53,7 +53,8 @@ static std::vector<std::string> GetIndexedExtensions(PFNGLGETINTEGERVPROC getInt
 
     for (GLint i = 0; i < numExtensions; i++)
     {
-        result.push_back(reinterpret_cast<const char *>(getStringIFunction(GL_EXTENSIONS, i)));
+        if (const char* extensionString = reinterpret_cast<const char *>(getStringIFunction(GL_EXTENSIONS, i)))
+            result.push_back(extensionString);
     }
 
     return result;
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.cpp b/src/libANGLE/renderer/gl/StateManagerGL.cpp
index 9d9f67b0cb23aa6c2f679fc46e68cef753ef3e8b..7d1b3287e15974f417b09682a33b9a146fa03339 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -2517,6 +2517,8 @@ angle::Result StateManagerGL::syncState(const gl::Context *context,
                             break;
                         case gl::state::EXTENDED_DIRTY_BIT_BLEND_ADVANCED_COHERENT:
                             setBlendAdvancedCoherent(state.isBlendAdvancedCoherentEnabled());
+                        case gl::state::EXTENDED_DIRTY_BIT_VARIABLE_RASTERIZATION_RATE:
+                            // Unimplemented extensions.
                             break;
                         default:
                             UNREACHABLE();
diff --git a/src/libANGLE/renderer/metal/BUILD.gn b/src/libANGLE/renderer/metal/BUILD.gn
index 96e9ee8420810f6a3ca9a0c290d4a654200eb7b9..8725219587ee8afc25e803bfd84f0fb117176372 100644
--- a/src/libANGLE/renderer/metal/BUILD.gn
+++ b/src/libANGLE/renderer/metal/BUILD.gn
@@ -15,7 +15,10 @@ assert(is_mac || is_ios)
 assert(angle_enable_metal)
 
 config("angle_metal_backend_config") {
-  defines = [ "ANGLE_ENABLE_METAL" ]
+  defines = [
+    "ANGLE_ENABLE_METAL",
+    "ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED=1",
+  ]
   ldflags = [
     "-weak_framework",
     "Metal",
diff --git a/src/libANGLE/renderer/metal/BufferMtl.mm b/src/libANGLE/renderer/metal/BufferMtl.mm
index 4ac920f02e6b380fe5cb26bf5eb05f86efd72d0c..6f001e829ad8beca5cf4ad8af0557c9b9c0de328 100644
--- a/src/libANGLE/renderer/metal/BufferMtl.mm
+++ b/src/libANGLE/renderer/metal/BufferMtl.mm
@@ -391,7 +391,7 @@ ConversionBufferMtl *BufferMtl::getUniformConversionBuffer(ContextMtl *context,
         {
             if (buffer.offset.second <= offset.second &&
                 (offset.second - buffer.offset.second) % buffer.uniformBufferBlockSize == 0)
-                return &buffer;
+                return static_cast<ConversionBufferMtl *>(&buffer);
         }
     }
 
diff --git a/src/libANGLE/renderer/metal/ContextMtl.h b/src/libANGLE/renderer/metal/ContextMtl.h
index 91da728d1a85fbd444c0d89146a32241bf4f8e65..b2fead9a10e8ea7408edd99e59fec4ddc5ca238b 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.h
+++ b/src/libANGLE/renderer/metal/ContextMtl.h
@@ -272,6 +272,10 @@ class ContextMtl : public ContextImpl, public mtl::Context
     angle::Result memoryBarrier(const gl::Context *context, GLbitfield barriers) override;
     angle::Result memoryBarrierByRegion(const gl::Context *context, GLbitfield barriers) override;
 
+    angle::Result bindMetalRasterizationRateMap(gl::Context *context,
+                                                RenderbufferImpl *renderbuffer,
+                                                GLMTLRasterizationRateMapANGLE map) override;
+
     // override mtl::ErrorHandler
     void handleError(GLenum error,
                      const char *message,
@@ -550,6 +554,7 @@ class ContextMtl : public ContextImpl, public mtl::Context
         DIRTY_BIT_RENDER_PIPELINE,
         DIRTY_BIT_UNIFORM_BUFFERS_BINDING,
         DIRTY_BIT_RASTERIZER_DISCARD,
+        DIRTY_BIT_VARIABLE_RASTERIZATION_RATE,
 
         DIRTY_BIT_INVALID,
         DIRTY_BIT_MAX = DIRTY_BIT_INVALID,
@@ -643,6 +648,9 @@ class ContextMtl : public ContextImpl, public mtl::Context
     IncompleteTextureSet mIncompleteTextures;
     ProvokingVertexHelper mProvokingVertexHelper;
 
+    angle::ObjCPtr<id<MTLRasterizationRateMap>> mRasterizationRateMap;
+    id<MTLTexture> mRasterizationRateMapTexture;
+
     mtl::ContextDevice mContextDevice;
 };
 
diff --git a/src/libANGLE/renderer/metal/ContextMtl.mm b/src/libANGLE/renderer/metal/ContextMtl.mm
index 95e0c28b41bfa32872e5635bc7d4bd52d1cb591b..056d10f5f1eca1a3b2593ab2d2ab0603b0b65f1a 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.mm
+++ b/src/libANGLE/renderer/metal/ContextMtl.mm
@@ -260,6 +260,8 @@ void ContextMtl::onDestroy(const gl::Context *context)
     mIncompleteTextures.onDestroy(context);
     mProvokingVertexHelper.onDestroy(this);
     mDummyXFBRenderTexture = nullptr;
+    mRasterizationRateMap.reset();
+    mRasterizationRateMapTexture = nil;
 
     mContextDevice.reset();
 }
@@ -1071,6 +1073,13 @@ angle::Result ContextMtl::multiDrawElementsInstancedBaseVertexBaseInstance(
 // Device loss
 gl::GraphicsResetStatus ContextMtl::getResetStatus()
 {
+#if ANGLE_METAL_LOSE_CONTEXT_ON_ERROR == ANGLE_ENABLED
+    if (cmdQueue().isDeviceLost())
+    {
+        return gl::GraphicsResetStatus::UnknownContextReset;
+    }
+#endif
+
     return gl::GraphicsResetStatus::NoError;
 }
 
@@ -1408,6 +1417,9 @@ void ContextMtl::updateExtendedState(const gl::State &glState,
             case gl::state::EXTENDED_DIRTY_BIT_POLYGON_OFFSET_LINE_ENABLED:
                 mDirtyBits.set(DIRTY_BIT_DEPTH_BIAS);
                 break;
+            case gl::state::EXTENDED_DIRTY_BIT_VARIABLE_RASTERIZATION_RATE:
+                mDirtyBits.set(DIRTY_BIT_VARIABLE_RASTERIZATION_RATE);
+                break;
             default:
                 break;
         }
@@ -1662,6 +1674,36 @@ angle::Result ContextMtl::memoryBarrierByRegion(const gl::Context *context, GLbi
     return angle::Result::Stop;
 }
 
+angle::Result ContextMtl::bindMetalRasterizationRateMap(gl::Context *context,
+                                                        RenderbufferImpl *renderbuffer,
+                                                        GLMTLRasterizationRateMapANGLE map)
+{
+    id<MTLRasterizationRateMap> rateMap = (__bridge id<MTLRasterizationRateMap>)(map);
+    if (rateMap && rateMap.device != mContextDevice.get())
+    {
+        return angle::Result::Stop;
+    }
+
+    if (auto *metalRenderbuffer = static_cast<RenderbufferMtl*>(renderbuffer))
+    {
+        FramebufferAttachmentRenderTarget *rtOut = nullptr;
+        gl::ImageIndex index;
+        GLenum binding = 0;
+        if (angle::Result::Continue == metalRenderbuffer->getAttachmentRenderTarget(context, binding, index, 1, &rtOut))
+        {
+            if (auto *renderTargetMetal = static_cast<RenderTargetMtl*>(rtOut))
+            {
+                mtl::RenderPassAttachmentDesc desc;
+                renderTargetMetal->toRenderPassAttachmentDesc(&desc);
+                mRasterizationRateMapTexture = desc.texture.get()->get();
+            }
+        }
+    }
+
+    mRasterizationRateMap = std::move(rateMap);
+    return angle::Result::Continue;
+}
+
 // override mtl::ErrorHandler
 void ContextMtl::handleError(GLenum glErrorCode,
                              const char *message,
@@ -2495,8 +2537,22 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
             return checkCommandBufferError();
         }
         // Setup with flushed state should either produce a working encoder or fail with an error
-        // result.
-        ASSERT(mRenderEncoder.valid());
+        // result. if that's not the case, something's gone seriously wrong. Try to
+        // recover from the error by bailing out of the draw call, and finishing the command buffer.
+        // This will result in an unfinished / corrupted draw, but will avoid a browser/GPU process
+        // crash.
+        if (ANGLE_UNLIKELY(!mRenderEncoder.valid() || !mRenderEncoder.hasPipelineState()))
+        {
+            // Completely flush the command buffer, waiting synchronously.
+            flushCommandBuffer(mtl::WaitUntilFinished);
+            // Invalidate all state
+            invalidateState(context);
+            // Return fail, and drop the draw call. This is
+            // a worst case scenario. If in a debug roots situation,
+            // we should try to catch the call stack.
+            ERR() << "Draw call is unusable - please report a bug on bugs.webkit.org";
+            return angle::Result::Stop;
+        }
     }
     return angle::Result::Continue;
 }
@@ -2605,10 +2661,10 @@ angle::Result ContextMtl::setupDrawImpl(const gl::Context *context,
                     mState.getBlendColor().blue, mState.getBlendColor().alpha);
                 break;
             case DIRTY_BIT_VIEWPORT:
-                mRenderEncoder.setViewport(mViewport);
+                mRenderEncoder.setViewport(mViewport, mRenderEncoder.rasterizationRateMapForPass(mRasterizationRateMap, mRasterizationRateMapTexture));
                 break;
             case DIRTY_BIT_SCISSOR:
-                mRenderEncoder.setScissorRect(mScissorRect);
+                mRenderEncoder.setScissorRect(mScissorRect, mRenderEncoder.rasterizationRateMapForPass(mRasterizationRateMap, mRasterizationRateMapTexture));
                 break;
             case DIRTY_BIT_DRAW_FRAMEBUFFER:
                 // Already handled.
@@ -2633,6 +2689,12 @@ angle::Result ContextMtl::setupDrawImpl(const gl::Context *context,
             case DIRTY_BIT_RASTERIZER_DISCARD:
                 // Already handled.
                 break;
+            case DIRTY_BIT_VARIABLE_RASTERIZATION_RATE:
+                if (getState().privateState().isVariableRasterizationRateEnabled() && mRasterizationRateMap)
+                {
+                    mRenderEncoder.setRasterizationRateMap(mRenderEncoder.rasterizationRateMapForPass(mRasterizationRateMap, mRasterizationRateMapTexture));
+                }
+                break;
             default:
                 UNREACHABLE();
                 break;
diff --git a/src/libANGLE/renderer/metal/DisplayMtl.h b/src/libANGLE/renderer/metal/DisplayMtl.h
index c8b8ac3a69962eca7b567814167a86a5e8871a25..f601b886205b02975a9240fa8b8c8a3c23d836ea 100644
--- a/src/libANGLE/renderer/metal/DisplayMtl.h
+++ b/src/libANGLE/renderer/metal/DisplayMtl.h
@@ -140,6 +140,7 @@ class DisplayMtl : public DisplayImpl
     bool supportsDepth24Stencil8PixelFormat() const;
     bool supports32BitFloatFiltering() const;
     bool supportsBCTextureCompression() const;
+    bool supportsVariableRasterizationRate() const;
     bool isAMD() const;
     bool isAMDBronzeDriver() const;
     bool isAMDFireProDevice() const;
diff --git a/src/libANGLE/renderer/metal/DisplayMtl.mm b/src/libANGLE/renderer/metal/DisplayMtl.mm
index 3770c0b41f65617f630cb91bbf30b4674c8cabf2..79f43d4a6f44e716ccfd5f3fbfc2bd2ab56dbebd 100644
--- a/src/libANGLE/renderer/metal/DisplayMtl.mm
+++ b/src/libANGLE/renderer/metal/DisplayMtl.mm
@@ -176,12 +176,22 @@ void DisplayMtl::terminate()
 
 bool DisplayMtl::testDeviceLost()
 {
+#if ANGLE_METAL_LOSE_CONTEXT_ON_ERROR == ANGLE_ENABLED
+    return mCmdQueue.isDeviceLost();
+#else
      return false;
+#endif
 }
 
 egl::Error DisplayMtl::restoreLostDevice(const egl::Display *display)
 {
+#if ANGLE_METAL_LOSE_CONTEXT_ON_ERROR == ANGLE_ENABLED
+    // A Metal device cannot be restored, the entire context would have to be
+    // re-created along with any other EGL objects that reference it.
+    return egl::EglBadDisplay();
+#else
     return egl::NoError();
+#endif
 }
 
 std::string DisplayMtl::getRendererDescription()
@@ -1136,6 +1146,14 @@ void DisplayMtl::initializeExtensions() const
             mNativeCaps.maxImageUnits = gl::IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES;
         }
     }
+
+    // GL_ANGLE_variable_rasterization_rate_metal
+    mNativeExtensions.variableRasterizationRateMetalANGLE = mFeatures.hasVariableRasterizationRate.enabled;
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    // GL_WEBKIT_explicit_resolve_target
+    mNativeExtensions.explicitResolveTargetWEBKIT = true;
+#endif
+
     // "The GPUs in Apple3 through Apple8 families only support memory barriers for compute command
     // encoders, and for vertex-to-vertex and vertex-to-fragment stages of render command encoders."
     mHasFragmentMemoryBarriers = !supportsAppleGPUFamily(3);
@@ -1207,6 +1225,8 @@ void DisplayMtl::initializeFeatures()
     ANGLE_FEATURE_CONDITION((&mFeatures), hasExplicitMemBarrier, (isOSX || isCatalyst) && !isARM);
     ANGLE_FEATURE_CONDITION((&mFeatures), hasDepthAutoResolve, supportsEitherGPUFamily(3, 2));
     ANGLE_FEATURE_CONDITION((&mFeatures), hasStencilAutoResolve, supportsEitherGPUFamily(5, 2));
+    ANGLE_FEATURE_CONDITION((&mFeatures), hasVariableRasterizationRate,
+                            supportsVariableRasterizationRate());
     ANGLE_FEATURE_CONDITION((&mFeatures), allowMultisampleStoreAndResolve,
                             supportsEitherGPUFamily(3, 1));
 
@@ -1397,6 +1417,17 @@ bool DisplayMtl::supportsDepth24Stencil8PixelFormat() const
     return false;
 #endif
 }
+
+bool DisplayMtl::supportsVariableRasterizationRate() const
+{
+    if (@available(ios 13.0, macOS 10.15.4, macCatalyst 13.4, tvOS 16.0, *))
+    {
+        return [mMetalDevice supportsRasterizationRateMapWithLayerCount:1];
+    }
+
+    return false;
+}
+
 bool DisplayMtl::isAMD() const
 {
     return angle::IsAMD(mMetalDeviceVendorId);
diff --git a/src/libANGLE/renderer/metal/FrameBufferMtl.h b/src/libANGLE/renderer/metal/FrameBufferMtl.h
index 84e5326b7270f29630d2c353a6ebadb5d10b6a21..0afffbbb5ad7f3961d49addf1f74d44bd1312e95 100644
--- a/src/libANGLE/renderer/metal/FrameBufferMtl.h
+++ b/src/libANGLE/renderer/metal/FrameBufferMtl.h
@@ -211,6 +211,14 @@ class FramebufferMtl : public FramebufferImpl
     angle::FixedVector<RenderTargetMtl *, mtl::kMaxRenderTargets> mColorRenderTargets;
     RenderTargetMtl *mDepthRenderTarget   = nullptr;
     RenderTargetMtl *mStencilRenderTarget = nullptr;
+
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    // GL_WEBKIT_explicit_resolve_target
+    angle::FixedVector<RenderTargetMtl *, mtl::kMaxRenderTargets> mColorResolveRenderTargets;
+    RenderTargetMtl *mDepthResolveRenderTarget   = nullptr;
+    RenderTargetMtl *mStencilResolveRenderTarget = nullptr;
+#endif
+
     mtl::RenderPassDesc mRenderPassDesc;
 
     const mtl::Format *mRenderPassFirstColorAttachmentFormat = nullptr;
diff --git a/src/libANGLE/renderer/metal/FrameBufferMtl.mm b/src/libANGLE/renderer/metal/FrameBufferMtl.mm
index d0c19c6e25c302705a76b6074f6990452d62b5f4..467e8f882147641018a561ea88ee35122ca3cee0 100644
--- a/src/libANGLE/renderer/metal/FrameBufferMtl.mm
+++ b/src/libANGLE/renderer/metal/FrameBufferMtl.mm
@@ -128,6 +128,9 @@ angle::Result Copy2DTextureSlice0Level0ToTempTexture(const gl::Context *context,
 FramebufferMtl::FramebufferMtl(const gl::FramebufferState &state, ContextMtl *context, bool flipY)
     : FramebufferImpl(state),
       mColorRenderTargets(context->getNativeCaps().maxColorAttachments, nullptr),
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+      mColorResolveRenderTargets(context->getNativeCaps().maxColorAttachments, nullptr),
+#endif
       mBackbuffer(nullptr),
       mFlipY(flipY)
 {
@@ -144,6 +147,14 @@ void FramebufferMtl::reset()
     }
     mDepthRenderTarget = mStencilRenderTarget = nullptr;
 
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    for (auto &rt : mColorResolveRenderTargets)
+    {
+        rt = nullptr;
+    }
+    mDepthResolveRenderTarget = mStencilResolveRenderTarget = nullptr;
+#endif
+
     mRenderPassFirstColorAttachmentFormat = nullptr;
 
     mReadPixelBuffer = nullptr;
@@ -943,7 +954,7 @@ void FramebufferMtl::setLoadStoreActionOnRenderPassFirstStart(
         attachment.loadAction = MTLLoadActionLoad;
     }
 
-    if (attachment.hasImplicitMSTexture())
+    if (attachment.hasResolveTexture())
     {
         attachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
     }
@@ -1010,22 +1021,47 @@ angle::Result FramebufferMtl::updateColorRenderTarget(const gl::Context *context
     ASSERT(colorIndexGL < mColorRenderTargets.size());
     // Reset load store action
     mRenderPassDesc.colorAttachments[colorIndexGL].reset();
-    return updateCachedRenderTarget(context, mState.getColorAttachment(colorIndexGL),
-                                    &mColorRenderTargets[colorIndexGL]);
+    ANGLE_TRY(updateCachedRenderTarget(context, mState.getColorAttachment(colorIndexGL),
+                                       &mColorRenderTargets[colorIndexGL]));
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    if (mState.getColorResolveAttachment(colorIndexGL))
+    {
+        ANGLE_TRY(updateCachedRenderTarget(context, mState.getColorResolveAttachment(colorIndexGL),
+                                           &mColorResolveRenderTargets[colorIndexGL]));
+    }
+#endif
+    return angle::Result::Continue;
 }
 
 angle::Result FramebufferMtl::updateDepthRenderTarget(const gl::Context *context)
 {
     // Reset load store action
     mRenderPassDesc.depthAttachment.reset();
-    return updateCachedRenderTarget(context, mState.getDepthAttachment(), &mDepthRenderTarget);
+    ANGLE_TRY(updateCachedRenderTarget(context, mState.getDepthAttachment(), &mDepthRenderTarget));
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    if (mState.getDepthResolveAttachment())
+    {
+        ANGLE_TRY(updateCachedRenderTarget(context, mState.getDepthResolveAttachment(),
+                                           &mDepthResolveRenderTarget));
+    }
+#endif
+    return angle::Result::Continue;
 }
 
 angle::Result FramebufferMtl::updateStencilRenderTarget(const gl::Context *context)
 {
     // Reset load store action
     mRenderPassDesc.stencilAttachment.reset();
-    return updateCachedRenderTarget(context, mState.getStencilAttachment(), &mStencilRenderTarget);
+    ANGLE_TRY(
+        updateCachedRenderTarget(context, mState.getStencilAttachment(), &mStencilRenderTarget));
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    if (mState.getStencilResolveAttachment())
+    {
+        ANGLE_TRY(updateCachedRenderTarget(context, mState.getStencilResolveAttachment(),
+                                           &mStencilResolveRenderTarget));
+    }
+#endif
+    return angle::Result::Continue;
 }
 
 angle::Result FramebufferMtl::updateCachedRenderTarget(const gl::Context *context,
@@ -1067,6 +1103,9 @@ angle::Result FramebufferMtl::prepareRenderPass(const gl::Context *context,
 
         mtl::RenderPassColorAttachmentDesc &colorAttachment = desc.colorAttachments[colorIndexGL];
         const RenderTargetMtl *colorRenderTarget            = mColorRenderTargets[colorIndexGL];
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+        const RenderTargetMtl *colorResolveRenderTarget = mColorResolveRenderTargets[colorIndexGL];
+#endif
 
         // GL allows data types of fragment shader color outputs to be incompatible with disabled
         // color attachments. To prevent various Metal validation issues, assign textures only to
@@ -1074,6 +1113,12 @@ angle::Result FramebufferMtl::prepareRenderPass(const gl::Context *context,
         if (colorRenderTarget && enabledDrawBuffers.test(colorIndexGL))
         {
             colorRenderTarget->toRenderPassAttachmentDesc(&colorAttachment);
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+            if (colorResolveRenderTarget)
+            {
+                colorResolveRenderTarget->toRenderPassResolveAttachmentDesc(&colorAttachment);
+            }
+#endif
 
             desc.numColorAttachments = std::max(desc.numColorAttachments, colorIndexGL + 1);
             desc.rasterSampleCount =
@@ -1103,6 +1148,12 @@ angle::Result FramebufferMtl::prepareRenderPass(const gl::Context *context,
     if (mDepthRenderTarget)
     {
         mDepthRenderTarget->toRenderPassAttachmentDesc(&desc.depthAttachment);
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+        if (mDepthResolveRenderTarget)
+        {
+            mDepthResolveRenderTarget->toRenderPassResolveAttachmentDesc(&desc.depthAttachment);
+        }
+#endif
         desc.rasterSampleCount =
             std::max(desc.rasterSampleCount, mDepthRenderTarget->getRenderSamples());
     }
@@ -1114,6 +1165,12 @@ angle::Result FramebufferMtl::prepareRenderPass(const gl::Context *context,
     if (mStencilRenderTarget)
     {
         mStencilRenderTarget->toRenderPassAttachmentDesc(&desc.stencilAttachment);
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+        if (mStencilResolveRenderTarget)
+        {
+            mStencilResolveRenderTarget->toRenderPassResolveAttachmentDesc(&desc.stencilAttachment);
+        }
+#endif
         desc.rasterSampleCount =
             std::max(desc.rasterSampleCount, mStencilRenderTarget->getRenderSamples());
     }
@@ -1188,7 +1245,7 @@ angle::Result FramebufferMtl::clearWithLoadOpRenderPassNotStarted(
             colorAttachment.loadAction = MTLLoadActionLoad;
         }
 
-        if (colorAttachment.hasImplicitMSTexture())
+        if (colorAttachment.hasResolveTexture())
         {
             colorAttachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
         }
@@ -1208,7 +1265,7 @@ angle::Result FramebufferMtl::clearWithLoadOpRenderPassNotStarted(
         tempDesc.depthAttachment.loadAction = MTLLoadActionLoad;
     }
 
-    if (tempDesc.depthAttachment.hasImplicitMSTexture())
+    if (tempDesc.depthAttachment.hasResolveTexture())
     {
         tempDesc.depthAttachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
     }
@@ -1227,7 +1284,7 @@ angle::Result FramebufferMtl::clearWithLoadOpRenderPassNotStarted(
         tempDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
     }
 
-    if (tempDesc.stencilAttachment.hasImplicitMSTexture())
+    if (tempDesc.stencilAttachment.hasResolveTexture())
     {
         tempDesc.stencilAttachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
     }
@@ -1465,31 +1522,54 @@ angle::Result FramebufferMtl::invalidateImpl(const gl::Context *context,
                 }
             }
 
-            mtl::RenderPassColorAttachmentDesc &colorAttachment =
-                mRenderPassDesc.colorAttachments[i];
+            // If the invalidated color buffer has an associated resolve target
+            // then resolve the MSAA samples, otherwise discard the data.
+            auto &colorAttachment = mRenderPassDesc.colorAttachments[i];
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+            auto *resolveRenderTarget = mColorResolveRenderTargets[i];
+            colorAttachment.storeAction =
+                resolveRenderTarget ? MTLStoreActionMultisampleResolve : MTLStoreActionDontCare;
+#else
             colorAttachment.storeAction = MTLStoreActionDontCare;
+#endif
             if (renderPassStarted)
             {
-                encoder->setColorStoreAction(MTLStoreActionDontCare, i);
+                encoder->setColorStoreAction(colorAttachment.storeAction, i);
             }
         }
     }
 
     if (invalidateDepthBuffer && mDepthRenderTarget)
     {
-        mRenderPassDesc.depthAttachment.storeAction = MTLStoreActionDontCare;
+        // If the invalidated depth buffer has an associated resolve target then
+        // resolve the MSAA samples, otherwise discard the data.
+        auto &depthAttachment = mRenderPassDesc.depthAttachment;
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+        depthAttachment.storeAction =
+            mDepthResolveRenderTarget ? MTLStoreActionMultisampleResolve : MTLStoreActionDontCare;
+#else
+        depthAttachment.storeAction = MTLStoreActionDontCare;
+#endif
         if (renderPassStarted)
         {
-            encoder->setDepthStoreAction(MTLStoreActionDontCare);
+            encoder->setDepthStoreAction(depthAttachment.storeAction);
         }
     }
 
     if (invalidateStencilBuffer && mStencilRenderTarget)
     {
-        mRenderPassDesc.stencilAttachment.storeAction = MTLStoreActionDontCare;
+        // If the invalidated stencil buffer has an associated resolve target
+        // then resolve the MSAA samples, otherwise discard the data.
+        auto &stencilAttachment = mRenderPassDesc.stencilAttachment;
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+        stencilAttachment.storeAction =
+            mStencilResolveRenderTarget ? MTLStoreActionMultisampleResolve : MTLStoreActionDontCare;
+#else
+        stencilAttachment.storeAction = MTLStoreActionDontCare;
+#endif
         if (renderPassStarted)
         {
-            encoder->setStencilStoreAction(MTLStoreActionDontCare);
+            encoder->setStencilStoreAction(stencilAttachment.storeAction);
         }
     }
 
@@ -1675,7 +1755,7 @@ angle::Result FramebufferMtl::readPixelsToPBO(const gl::Context *context,
     ContextMtl *contextMtl = mtl::GetImpl(context);
 
     ANGLE_CHECK_GL_MATH(contextMtl,
-                        packPixelsParams.offset <= std::numeric_limits<uint32_t>::max());
+                        static_cast<std::make_unsigned_t<decltype(packPixelsParams.offset)>>(packPixelsParams.offset) <= std::numeric_limits<uint32_t>::max());
     uint32_t offset = static_cast<uint32_t>(packPixelsParams.offset);
 
     BufferMtl *packBufferMtl = mtl::GetImpl(packPixelsParams.packBuffer);
@@ -1824,8 +1904,8 @@ angle::Result FramebufferMtl::unresolveIfNeeded(const gl::Context *context,
             renderPassDesc.colorAttachments[colorIndexGL];
 
         if (colorAttachment.loadAction != MTLLoadActionLoad ||
-            !colorAttachment.hasImplicitMSTexture() ||
-            !colorAttachment.implicitMSTexture->shouldNotLoadStore())
+            !colorAttachment.texture ||
+            !colorAttachment.texture->shouldNotLoadStore())
         {
             continue;
         }
@@ -1833,9 +1913,9 @@ angle::Result FramebufferMtl::unresolveIfNeeded(const gl::Context *context,
         const angle::Format &angleFormat = colorRenderTarget->getFormat().actualAngleFormat();
 
         // Blit the resolve texture to the MS texture.
-        colorBlitParams.src      = colorAttachment.texture;
-        colorBlitParams.srcLevel = colorAttachment.level;
-        colorBlitParams.srcLayer = colorAttachment.sliceOrDepth;
+        colorBlitParams.src      = colorAttachment.resolveTexture;
+        colorBlitParams.srcLevel = colorAttachment.resolveLevel;
+        colorBlitParams.srcLayer = colorAttachment.resolveSliceOrDepth;
 
         colorBlitParams.enabledBuffers.reset();
         colorBlitParams.enabledBuffers.set(colorIndexGL);
@@ -1850,19 +1930,18 @@ angle::Result FramebufferMtl::unresolveIfNeeded(const gl::Context *context,
     mtl::DepthStencilBlitParams dsBlitParams;
     dsBlitParams.BlitParams::operator=(baseParams);
     const mtl::RenderPassDepthAttachmentDesc &depthAttachment = renderPassDesc.depthAttachment;
-    if (depthAttachment.loadAction == MTLLoadActionLoad && depthAttachment.hasImplicitMSTexture() &&
-        depthAttachment.implicitMSTexture->shouldNotLoadStore())
+    if (depthAttachment.loadAction == MTLLoadActionLoad && depthAttachment.texture &&
+        depthAttachment.texture->shouldNotLoadStore())
     {
-        dsBlitParams.src      = depthAttachment.texture;
-        dsBlitParams.srcLevel = depthAttachment.level;
-        dsBlitParams.srcLayer = depthAttachment.sliceOrDepth;
+        dsBlitParams.src      = depthAttachment.resolveTexture;
+        dsBlitParams.srcLevel = depthAttachment.resolveLevel;
+        dsBlitParams.srcLayer = depthAttachment.resolveSliceOrDepth;
     }
 
     const mtl::RenderPassStencilAttachmentDesc &stencilAttachment =
         renderPassDesc.stencilAttachment;
-    if (stencilAttachment.loadAction == MTLLoadActionLoad &&
-        stencilAttachment.hasImplicitMSTexture() &&
-        stencilAttachment.implicitMSTexture->shouldNotLoadStore())
+    if (stencilAttachment.loadAction == MTLLoadActionLoad && stencilAttachment.texture &&
+        stencilAttachment.texture->shouldNotLoadStore())
     {
         if (mState.hasSeparateDepthAndStencilAttachments())
         {
@@ -1872,9 +1951,9 @@ angle::Result FramebufferMtl::unresolveIfNeeded(const gl::Context *context,
             dsBlitParams.src = nullptr;
         }
 
-        dsBlitParams.srcStencil = stencilAttachment.texture->getStencilView();
-        dsBlitParams.srcLevel   = stencilAttachment.level;
-        dsBlitParams.srcLayer   = stencilAttachment.sliceOrDepth;
+        dsBlitParams.srcStencil = stencilAttachment.resolveTexture->getStencilView();
+        dsBlitParams.srcLevel   = stencilAttachment.resolveLevel;
+        dsBlitParams.srcLayer   = stencilAttachment.resolveSliceOrDepth;
     }
 
     if (dsBlitParams.src || dsBlitParams.srcStencil)
diff --git a/src/libANGLE/renderer/metal/ImageMtl.mm b/src/libANGLE/renderer/metal/ImageMtl.mm
index a4a809fabe7572c1d212ff8fb4da35b1f67b6a01..eb2bd204b9edda65ba372b8fcb24f8092b9f4f65 100644
--- a/src/libANGLE/renderer/metal/ImageMtl.mm
+++ b/src/libANGLE/renderer/metal/ImageMtl.mm
@@ -86,7 +86,9 @@ egl::Error TextureImageSiblingMtl::ValidateClientBuffer(const DisplayMtl *displa
         return egl::EglBadAttribute() << "Unrecognized format";
     }
 
-    if (format.metalFormat != texture.pixelFormat)
+    angle::FormatID srcAngleFormatId = mtl::Format::MetalToAngleFormatID(texture.pixelFormat);
+    const mtl::Format &srcFormat = display->getPixelFormat(srcAngleFormatId);
+    if (!format.isViewCompatible(srcFormat))
     {
         return egl::EglBadAttribute() << "Incompatible format";
     }
@@ -120,18 +122,21 @@ angle::Result TextureImageSiblingMtl::initImpl(DisplayMtl *displayMtl)
 {
     mNativeTexture = mtl::Texture::MakeFromMetal((__bridge id<MTLTexture>)(mBuffer));
 
-    if (mNativeTexture->textureType() == MTLTextureType2DArray)
+    angle::FormatID angleFormatId = intendedFormatForMTLTexture(mNativeTexture->get(), mAttribs);
+    mFormat                       = displayMtl->getPixelFormat(angleFormatId);
+
+    if (mNativeTexture->textureType() == MTLTextureType2DArray ||
+        mNativeTexture->pixelFormat() != mFormat.metalFormat)
     {
         mtl::TextureRef baseTexture = std::move(mNativeTexture);
         unsigned textureArraySlice =
             static_cast<unsigned>(mAttribs.getAsInt(EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 0));
         mNativeTexture =
-            baseTexture->createSliceMipView(textureArraySlice, mtl::kZeroNativeMipLevel);
+        baseTexture->createSliceMipViewWithCompatibleFormat(textureArraySlice,
+                                                            mtl::kZeroNativeMipLevel,
+                                                            mFormat.metalFormat);
     }
 
-    angle::FormatID angleFormatId = intendedFormatForMTLTexture(mNativeTexture->get(), mAttribs);
-    mFormat                       = displayMtl->getPixelFormat(angleFormatId);
-
     if (mNativeTexture)
     {
         size_t resourceSize = EstimateTextureSizeInBytes(
diff --git a/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm b/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm
index b574cf199428badfcf884771ed32cebcccb11e30..850c69932fb255e68455978d8b7517a01340b9e5 100644
--- a/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm
+++ b/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm
@@ -878,7 +878,7 @@ angle::Result ProgramExecutableMtl::setupDraw(const gl::Context *glContext,
 {
     ContextMtl *context = mtl::GetImpl(glContext);
 
-    if (pipelineDescChanged)
+    if (pipelineDescChanged || !cmdEncoder->hasPipelineState())
     {
         id<MTLFunction> vertexShader = nil;
         ANGLE_TRY(
diff --git a/src/libANGLE/renderer/metal/RenderTargetMtl.h b/src/libANGLE/renderer/metal/RenderTargetMtl.h
index 281085bbc46eb7d96dabf73b9c75ee0a61125bab..a07218bc78473252b0b2b9e561ec74799959177e 100644
--- a/src/libANGLE/renderer/metal/RenderTargetMtl.h
+++ b/src/libANGLE/renderer/metal/RenderTargetMtl.h
@@ -51,6 +51,9 @@ class RenderTargetMtl final : public FramebufferAttachmentRenderTarget
     const mtl::Format &getFormat() const { return mFormat; }
 
     void toRenderPassAttachmentDesc(mtl::RenderPassAttachmentDesc *rpaDescOut) const;
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    void toRenderPassResolveAttachmentDesc(mtl::RenderPassAttachmentDesc *rpaDescOut) const;
+#endif
 
   private:
     mtl::TextureWeakRef mTexture;
diff --git a/src/libANGLE/renderer/metal/RenderTargetMtl.mm b/src/libANGLE/renderer/metal/RenderTargetMtl.mm
index 18e62da867b2916085794ba3b68ee4a3812d3929..6cbf2a58812184edb425d11689bdbce5574917f0 100644
--- a/src/libANGLE/renderer/metal/RenderTargetMtl.mm
+++ b/src/libANGLE/renderer/metal/RenderTargetMtl.mm
@@ -73,10 +73,33 @@ uint32_t RenderTargetMtl::getRenderSamples() const
 
 void RenderTargetMtl::toRenderPassAttachmentDesc(mtl::RenderPassAttachmentDesc *rpaDescOut) const
 {
-    rpaDescOut->texture           = mTexture.lock();
-    rpaDescOut->implicitMSTexture = mImplicitMSTexture.lock();
+    mtl::TextureRef implicitMSTex = getImplicitMSTexture();
+    mtl::TextureRef tex           = getTexture();
+    if (implicitMSTex)
+    {
+        rpaDescOut->texture             = implicitMSTex;
+        rpaDescOut->resolveTexture      = tex;
+        rpaDescOut->resolveLevel        = mLevelIndex;
+        rpaDescOut->resolveSliceOrDepth = mLayerIndex;
+    }
+    else
+    {
+        rpaDescOut->texture      = tex;
         rpaDescOut->level        = mLevelIndex;
         rpaDescOut->sliceOrDepth = mLayerIndex;
+    }
     rpaDescOut->blendable = mFormat.getCaps().blendable;
 }
+
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+void RenderTargetMtl::toRenderPassResolveAttachmentDesc(
+    mtl::RenderPassAttachmentDesc *rpaDescOut) const
+{
+    ASSERT(!getImplicitMSTexture());
+    ASSERT(getRenderSamples() == 1);
+    rpaDescOut->resolveTexture      = getTexture();
+    rpaDescOut->resolveLevel        = mLevelIndex;
+    rpaDescOut->resolveSliceOrDepth = mLayerIndex;
+}
+#endif
 }  // namespace rx
diff --git a/src/libANGLE/renderer/metal/SurfaceMtl.mm b/src/libANGLE/renderer/metal/SurfaceMtl.mm
index 64bbaa17239f0717611ea51c6fb916473fd251e2..db5e9ac177dd625c91a1a8dc2bad1fb6226adc58 100644
--- a/src/libANGLE/renderer/metal/SurfaceMtl.mm
+++ b/src/libANGLE/renderer/metal/SurfaceMtl.mm
@@ -694,8 +694,7 @@ angle::Result WindowSurfaceMtl::obtainNextDrawable(const gl::Context *context)
         }
         mColorTextureInitialized = false;
 
-        ANGLE_MTL_LOG("Current metal drawable size=%d,%d", mColorTexture->width(),
-                      mColorTexture->height());
+        ANGLE_MTL_LOG("Current metal drawable size=%d,%d", mColorTexture->width(mtl::MipmapNativeLevel(0)),  mColorTexture->height(mtl::MipmapNativeLevel(0)));
 
         // Now we have to resize depth stencil buffers if required.
         ANGLE_TRY(ensureCompanionTexturesSizeCorrect(context));
diff --git a/src/libANGLE/renderer/metal/VertexArrayMtl.mm b/src/libANGLE/renderer/metal/VertexArrayMtl.mm
index 051fed3b2f85c55edee7d32c3e961510b4c7a968..a40766e1367d1ff2427e963957dcc2869ade32cf 100644
--- a/src/libANGLE/renderer/metal/VertexArrayMtl.mm
+++ b/src/libANGLE/renderer/metal/VertexArrayMtl.mm
@@ -1085,16 +1085,23 @@ angle::Result VertexArrayMtl::convertVertexBufferGPU(const gl::Context *glContex
     ANGLE_TRY(conversion->data.allocate(contextMtl, numVertices * targetStride, nullptr, &newBuffer,
                                         &newBufferOffset));
 
-    ANGLE_CHECK_GL_MATH(contextMtl, binding.getOffset() <= std::numeric_limits<uint32_t>::max());
+    GLintptr bindingOffset = binding.getOffset();
+
+    if constexpr (sizeof(bindingOffset) > sizeof(uint32_t))
+        ANGLE_CHECK_GL_MATH(contextMtl, static_cast<std::make_unsigned_t<decltype(bindingOffset)>>(
+                                            bindingOffset) <= std::numeric_limits<uint32_t>::max());
     ANGLE_CHECK_GL_MATH(contextMtl, newBufferOffset <= std::numeric_limits<uint32_t>::max());
     ANGLE_CHECK_GL_MATH(contextMtl, numVertices <= std::numeric_limits<uint32_t>::max());
 
     mtl::VertexFormatConvertParams params;
     VertexConversionBufferMtl *vertexConversion =
         static_cast<VertexConversionBufferMtl *>(conversion);
+    if constexpr (sizeof(vertexConversion->offset) > sizeof(uint32_t))
+        ANGLE_CHECK_GL_MATH(contextMtl,
+                            vertexConversion->offset <= std::numeric_limits<uint32_t>::max());
     params.srcBuffer            = srcBuffer->getCurrentBuffer();
-    params.srcBufferStartOffset = static_cast<uint32_t>(
-        MIN(static_cast<GLintptr>(vertexConversion->offset), binding.getOffset()));
+    params.srcBufferStartOffset = std::min(static_cast<uint32_t>(vertexConversion->offset),
+                                           static_cast<uint32_t>(bindingOffset));
     params.srcStride            = binding.getStride();
     params.srcDefaultAlphaData  = convertedFormat.defaultAlpha;
 
diff --git a/src/libANGLE/renderer/metal/mtl_command_buffer.h b/src/libANGLE/renderer/metal/mtl_command_buffer.h
index a4786802ae1a5001aad4d83543dc9b7ea483f465..3afd8e1f7339335659ed3d213310dfe22d089e62 100644
--- a/src/libANGLE/renderer/metal/mtl_command_buffer.h
+++ b/src/libANGLE/renderer/metal/mtl_command_buffer.h
@@ -118,6 +118,8 @@ class CommandQueue final : public WrappedObject<id<MTLCommandQueue>>, angle::Non
     double getTimeElapsedEntryInSeconds(uint64_t id);
     MTLCommandBufferError popCmdBufferError() { return mCmdBufferError.pop(); }
 
+    bool isDeviceLost() const { return mIsDeviceLost; }
+
   private:
     void onCommandBufferCompleted(id<MTLCommandBuffer> buf,
                                   uint64_t serial,
@@ -162,6 +164,8 @@ class CommandQueue final : public WrappedObject<id<MTLCommandQueue>>, angle::Non
     void recordCommandBufferTimeElapsed(std::lock_guard<std::mutex> &lg,
                                         uint64_t id,
                                         double seconds);
+
+    std::atomic_bool mIsDeviceLost = false;
 };
 
 class CommandBuffer final : public WrappedObject<id<MTLCommandBuffer>>, angle::NonCopyable
@@ -436,8 +440,10 @@ class RenderCommandEncoder final : public CommandEncoder
     RenderCommandEncoder &setStencilRefVals(uint32_t frontRef, uint32_t backRef);
     RenderCommandEncoder &setStencilRefVal(uint32_t ref);
 
-    RenderCommandEncoder &setViewport(const MTLViewport &viewport);
-    RenderCommandEncoder &setScissorRect(const MTLScissorRect &rect);
+    RenderCommandEncoder &setViewport(const MTLViewport &viewport,
+                                      id<MTLRasterizationRateMap> map);
+    RenderCommandEncoder &setScissorRect(const MTLScissorRect &rect,
+                                         id<MTLRasterizationRateMap> map);
 
     RenderCommandEncoder &setBlendColor(float r, float g, float b, float a);
 
@@ -586,6 +592,8 @@ class RenderCommandEncoder final : public CommandEncoder
     RenderCommandEncoder &setDepthLoadAction(MTLLoadAction action, double clearValue);
     RenderCommandEncoder &setStencilLoadAction(MTLLoadAction action, uint32_t clearValue);
 
+    RenderCommandEncoder &setRasterizationRateMap(id<MTLRasterizationRateMap> map);
+
     void setLabel(NSString *label);
 
     void pushDebugGroup(NSString *label) override;
@@ -593,8 +601,11 @@ class RenderCommandEncoder final : public CommandEncoder
 
     const RenderPassDesc &renderPassDesc() const { return mRenderPassDesc; }
     bool hasDrawCalls() const { return mHasDrawCalls; }
+    bool hasPipelineState() const { return mPipelineStateSet; }
 
     uint64_t getSerial() const { return mSerial; }
+    id<MTLRasterizationRateMap> rasterizationRateMapForPass(id<MTLRasterizationRateMap> map,
+                                                            id<MTLTexture> colorTexture) const;
 
   private:
     // Override CommandEncoder
diff --git a/src/libANGLE/renderer/metal/mtl_command_buffer.mm b/src/libANGLE/renderer/metal/mtl_command_buffer.mm
index ab7d4b54ee8bbc56141abf325a48dabfd150c673..0b534d28525375d70aa1d979b31ca003ed7534ac 100644
--- a/src/libANGLE/renderer/metal/mtl_command_buffer.mm
+++ b/src/libANGLE/renderer/metal/mtl_command_buffer.mm
@@ -647,6 +647,19 @@ void CommandQueue::onCommandBufferCompleted(id<MTLCommandBuffer> buf,
               << error.localizedDescription.UTF8String;
         mCmdBufferError.store(static_cast<MTLCommandBufferError>(error.code));
     }
+    MTLCommandBufferStatus status = buf.status;
+    if (status != MTLCommandBufferStatusCompleted)
+    {
+        NSError *error = buf.error;
+        // MTLCommandBufferErrorNotPermitted is non-fatal, all other errors
+        // result in device lost.
+        // TODO(djg): Should this also check error.domain for MTLCommandBufferErrorDomain?
+        mIsDeviceLost  = !error || error.code != MTLCommandBufferErrorNotPermitted;
+        if (mIsDeviceLost)
+        {
+            return;
+        }
+    }
 
     if (timeElapsedEntry != 0)
     {
@@ -1361,6 +1374,7 @@ void RenderCommandEncoder::reset()
     CommandEncoder::reset();
     mRecording        = false;
     mPipelineStateSet = false;
+    setRasterizationRateMap(nil);
     mCommands.clear();
 }
 
@@ -1396,7 +1410,7 @@ bool RenderCommandEncoder::finalizeLoadStoreAction(
     }
 
     // Check if we need to disable MTLLoadActionLoad & MTLStoreActionStore
-    mtl::TextureRef mainTextureRef = cppRenderPassAttachment.getImplicitMSTextureIfAvailOrTexture();
+    mtl::TextureRef mainTextureRef = cppRenderPassAttachment.texture;
     ASSERT(mainTextureRef->get() == objCRenderPassAttachment.texture);
     if (mainTextureRef->shouldNotLoadStore())
     {
@@ -1540,13 +1554,13 @@ void RenderCommandEncoder::endEncodingImpl(bool considerDiscardSimulation)
 inline void RenderCommandEncoder::initAttachmentWriteDependencyAndScissorRect(
     const RenderPassAttachmentDesc &attachment)
 {
-    TextureRef texture = attachment.texture;
+    auto texture = attachment.hasResolveTexture() ? attachment.resolveTexture : attachment.texture;
+    auto &mipLevel = attachment.hasResolveTexture() ? attachment.resolveLevel : attachment.level;
+
     if (texture)
     {
         cmdBuffer().setWriteDependency(texture, /*isRenderCommand=*/true);
 
-        const MipmapNativeLevel &mipLevel = attachment.level;
-
         mRenderPassMaxScissorRect.width =
             std::min<NSUInteger>(mRenderPassMaxScissorRect.width, texture->width(mipLevel));
         mRenderPassMaxScissorRect.height =
@@ -1824,7 +1838,8 @@ RenderCommandEncoder &RenderCommandEncoder::setStencilRefVal(uint32_t ref)
     return setStencilRefVals(ref, ref);
 }
 
-RenderCommandEncoder &RenderCommandEncoder::setViewport(const MTLViewport &viewport)
+RenderCommandEncoder &RenderCommandEncoder::setViewport(const MTLViewport &viewport,
+                                                        id<MTLRasterizationRateMap> map)
 {
     if (mStateCache.viewport.valid() && mStateCache.viewport.value() == viewport)
     {
@@ -1837,12 +1852,22 @@ RenderCommandEncoder &RenderCommandEncoder::setViewport(const MTLViewport &viewp
     return *this;
 }
 
-RenderCommandEncoder &RenderCommandEncoder::setScissorRect(const MTLScissorRect &rect)
+RenderCommandEncoder &RenderCommandEncoder::setScissorRect(const MTLScissorRect &rect, id<MTLRasterizationRateMap> map)
 {
+    auto maxScissorRect =
+        MTLCoordinate2DMake(mRenderPassMaxScissorRect.width, mRenderPassMaxScissorRect.height);
+
+    if (map)
+    {
+        maxScissorRect = [map mapPhysicalToScreenCoordinates:maxScissorRect forLayer:0];
+        if (!(rect.width * rect.height))
+            return *this;
+    }
+
     NSUInteger clampedWidth =
-        rect.x > mRenderPassMaxScissorRect.width ? 0 : mRenderPassMaxScissorRect.width - rect.x;
+        rect.x > maxScissorRect.x ? 0 : (NSUInteger)ceilf(maxScissorRect.x) - rect.x;
     NSUInteger clampedHeight =
-        rect.y > mRenderPassMaxScissorRect.height ? 0 : mRenderPassMaxScissorRect.height - rect.y;
+        rect.y > maxScissorRect.y ? 0 : (NSUInteger)ceilf(maxScissorRect.y) - rect.y;
 
     MTLScissorRect clampedRect = {rect.x, rect.y, std::min(rect.width, clampedWidth),
                                   std::min(rect.height, clampedHeight)};
@@ -1854,6 +1879,23 @@ RenderCommandEncoder &RenderCommandEncoder::setScissorRect(const MTLScissorRect
 
     mStateCache.scissorRect = clampedRect;
 
+    if (map)
+    {
+        auto adjustedOrigin =
+            [map mapPhysicalToScreenCoordinates:MTLCoordinate2DMake(clampedRect.x, clampedRect.y)
+                                       forLayer:0];
+        auto adjustedSize =
+            [map mapPhysicalToScreenCoordinates:MTLCoordinate2DMake(clampedRect.width,
+                                                                    clampedRect.height)
+                                       forLayer:0];
+
+        clampedRect.x      = (NSUInteger)roundf(adjustedOrigin.x);
+        clampedRect.y      = (NSUInteger)roundf(adjustedOrigin.y);
+        MTLSize screenSize = [map screenSize];
+        clampedRect.width  = std::min<NSUInteger>(screenSize.width, roundf(adjustedSize.x));
+        clampedRect.height = std::min<NSUInteger>(screenSize.height, roundf(adjustedSize.y));
+    }
+
     mCommands.push(CmdType::SetScissorRect).push(clampedRect);
 
     return *this;
@@ -2259,6 +2301,17 @@ void RenderCommandEncoder::popDebugGroup()
     mCommands.push(CmdType::PopDebugGroup);
 }
 
+id<MTLRasterizationRateMap> RenderCommandEncoder::rasterizationRateMapForPass(id<MTLRasterizationRateMap> map,
+                                                                              id<MTLTexture> texture) const
+{
+    if (!mCachedRenderPassDescObjC.get())
+        return nil;
+
+    MTLSize size = [map physicalSizeForLayer:0];
+    id<MTLTexture> t = mCachedRenderPassDescObjC.get().colorAttachments[0].texture;
+    return t.width == size.width && t.height == size.height ? map : nil;
+}
+
 RenderCommandEncoder &RenderCommandEncoder::setColorStoreAction(MTLStoreAction action,
                                                                 uint32_t colorAttachmentIndex)
 {
@@ -2354,6 +2407,16 @@ RenderCommandEncoder &RenderCommandEncoder::setStencilLoadAction(MTLLoadAction a
     return *this;
 }
 
+RenderCommandEncoder &RenderCommandEncoder::setRasterizationRateMap(id<MTLRasterizationRateMap> map)
+{
+    if (map != mCachedRenderPassDescObjC.get().rasterizationRateMap)
+    {
+        mCachedRenderPassDescObjC.get().rasterizationRateMap = map;
+    }
+
+    return *this;
+}
+
 void RenderCommandEncoder::setLabel(NSString *label)
 {
     mLabel = std::move(label);
diff --git a/src/libANGLE/renderer/metal/mtl_format_utils.h b/src/libANGLE/renderer/metal/mtl_format_utils.h
index db95298598177500cebd0eb81cfcb845656031b2..8cbd1014b7dd9c2886272df3ae0923ccec49b512 100644
--- a/src/libANGLE/renderer/metal/mtl_format_utils.h
+++ b/src/libANGLE/renderer/metal/mtl_format_utils.h
@@ -89,6 +89,10 @@ struct Format : public FormatBase
     // Need conversion between source format and this format?
     bool needConversion(angle::FormatID srcFormatId) const;
 
+    // Are the formats view compatible without requiring
+    // MTLTextureUsagePixelFormatView?
+    bool isViewCompatible(const Format &srcFormat) const;
+
     MTLPixelFormat metalFormat = MTLPixelFormatInvalid;
 
     LoadFunctionMap textureLoadFunctions       = nullptr;
diff --git a/src/libANGLE/renderer/metal/mtl_format_utils.mm b/src/libANGLE/renderer/metal/mtl_format_utils.mm
index b6fb44c79e2ed92c908b5b8997695d2253bbe2a2..7f5cacbf7104aa15142cb78688a91a032657c46f 100644
--- a/src/libANGLE/renderer/metal/mtl_format_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_format_utils.mm
@@ -161,6 +161,26 @@ bool Format::needConversion(angle::FormatID srcFormatId) const
     return srcFormatId != actualFormatId;
 }
 
+bool Format::isViewCompatible(const Format &srcFormat) const
+{
+    if (srcFormat.metalFormat == metalFormat)
+        return true;
+
+    // The pixel layout is considered different if the number of components differs,
+    if (srcFormat.caps.channels != caps.channels)
+        return false;
+
+    // ... or if their size or order is different from the components in the original pixel format.
+    if (srcFormat.caps.pixelBytes != caps.pixelBytes)
+        return false;
+
+    // This is overly conservative but reject compressed formats
+    if (srcFormat.caps.compressed || caps.compressed)
+        return false;
+
+    return true;
+}
+
 bool Format::isPVRTC() const
 {
     switch (metalFormat)
diff --git a/src/libANGLE/renderer/metal/mtl_render_utils.mm b/src/libANGLE/renderer/metal/mtl_render_utils.mm
index 5745d4e39da8667535cc35a5fb9c536db7e97b06..501e9e0cfb46e46e43a2f303bf5971dfc00387bf 100644
--- a/src/libANGLE/renderer/metal/mtl_render_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_render_utils.mm
@@ -596,7 +596,8 @@ void DispatchCompute(ContextMtl *contextMtl,
                      id<MTLComputePipelineState> pipelineState,
                      size_t numThreads)
 {
-    NSUInteger w = std::min<NSUInteger>(pipelineState.threadExecutionWidth, numThreads);
+    ASSERT(numThreads != 0);
+    NSUInteger w = std::clamp<NSUInteger>(numThreads, 1u, pipelineState.threadExecutionWidth);
     MTLSize threadsPerThreadgroup = MTLSizeMake(w, 1, 1);
 
     if (contextMtl->getDisplay()->getFeatures().hasNonUniformDispatch.enabled)
@@ -676,8 +677,9 @@ void SetupCommonBlitWithDrawStates(const gl::Context *context,
         GetViewport(params.dstRect, params.dstTextureSize.height, params.dstFlipY);
     MTLScissorRect scissorRectMtl =
         GetScissorRect(params.dstScissorRect, params.dstTextureSize.height, params.dstFlipY);
-    cmdEncoder->setViewport(viewportMtl);
-    cmdEncoder->setScissorRect(scissorRectMtl);
+
+    cmdEncoder->setViewport(viewportMtl, nil);
+    cmdEncoder->setScissorRect(scissorRectMtl, nil);
 
     if (params.src)
     {
@@ -1168,8 +1170,8 @@ angle::Result ClearUtils::setupClearWithDraw(const gl::Context *context,
 
     scissorRect = GetScissorRect(params.clearArea, params.dstTextureSize.height, params.flipY);
 
-    cmdEncoder->setViewport(viewport);
-    cmdEncoder->setScissorRect(scissorRect);
+    cmdEncoder->setViewport(viewport, nil);
+    cmdEncoder->setScissorRect(scissorRect, nil);
 
     // uniform
     ClearParamsUniform uniformParams;
diff --git a/src/libANGLE/renderer/metal/mtl_resource_spi.h b/src/libANGLE/renderer/metal/mtl_resource_spi.h
index 3eaa9a64e79af26faa786102ece3f0bdea84d090..7ecb6db9d993df8985fcb6f701dfe7ca75857cae 100644
--- a/src/libANGLE/renderer/metal/mtl_resource_spi.h
+++ b/src/libANGLE/renderer/metal/mtl_resource_spi.h
@@ -1,9 +1,63 @@
-//
-// Copyright 2021 The ANGLE Project Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/*
+ * Copyright (C) 2021 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
 //
 // mtl_resource_spi.h:
-//    Used to set Metal resource ownership identity with SPI.
-//    Purposefully empty header file. Actual implementation will be hosted in WebKit.
+//    Used to set Metal resource ownership identity with SPI
 //
+
+#ifndef LIBANGLE_RENDERER_METAL_RESOURCE_SPI_H_
+#define LIBANGLE_RENDERER_METAL_RESOURCE_SPI_H_
+
+#import "common/apple/apple_platform.h"
+
+#if ANGLE_USE_METAL_OWNERSHIP_IDENTITY
+
+#    import <Metal/MTLResource_Private.h>
+#    import <Metal/Metal.h>
+#    import <mach/mach_types.h>
+
+namespace rx
+{
+namespace mtl
+{
+inline void setOwnerWithIdentity(id<MTLResource> resource, task_id_token_t identityToken)
+{
+    if (identityToken != TASK_ID_TOKEN_NULL)
+    {
+        kern_return_t kr = [(id<MTLResourceSPI>)resource setOwnerWithIdentity:identityToken];
+        if (ANGLE_UNLIKELY(kr != KERN_SUCCESS))
+        {
+            ERR() << "setOwnerWithIdentity failed with: %s (%x)" << mach_error_string(kr) << kr;
+            ASSERT(false);
+        }
+    }
+    return;
+}
+}  // namespace mtl
+}  // namespace rx
+#endif
+
+#endif /* LIBANGLE_RENDERER_METAL_RESOURCE_SPI_H_ */
diff --git a/src/libANGLE/renderer/metal/mtl_resources.h b/src/libANGLE/renderer/metal/mtl_resources.h
index 1632afc5c0973609b7e24ac135a872919e38f47b..5abcd18926906851264b5225968ed650ff5bb0bd 100644
--- a/src/libANGLE/renderer/metal/mtl_resources.h
+++ b/src/libANGLE/renderer/metal/mtl_resources.h
@@ -228,6 +228,11 @@ class Texture final : public Resource,
     TextureRef createCubeFaceView(uint32_t face);
     // Create a view of one slice at a level.
     TextureRef createSliceMipView(uint32_t slice, const MipmapNativeLevel &level);
+    // Same as createSliceMipView but the target format must be compatible, for example sRGB to linear. In this
+    // case texture doesn't need format view usage flag.
+    TextureRef createSliceMipViewWithCompatibleFormat(uint32_t slice,
+                                                      const MipmapNativeLevel &level,
+                                                      MTLPixelFormat format);
     // Create a levels range view
     TextureRef createMipsView(const MipmapNativeLevel &baseLevel, uint32_t levels);
     // Create a view of a level.
diff --git a/src/libANGLE/renderer/metal/mtl_resources.mm b/src/libANGLE/renderer/metal/mtl_resources.mm
index 2cf7d6c6ab7040a5847a0dcb1266dd1a8cfb991d..b70e647a9f908e89c727cc794c1ebc6a6b91a4b8 100644
--- a/src/libANGLE/renderer/metal/mtl_resources.mm
+++ b/src/libANGLE/renderer/metal/mtl_resources.mm
@@ -78,12 +78,10 @@ MTLResourceOptions resourceOptionsForStorageMode(MTLStorageMode storageMode)
             return MTLResourceStorageModePrivate;
         case MTLStorageModeMemoryless:
             return MTLResourceStorageModeMemoryless;
-#if TARGET_OS_SIMULATOR
         default:
             // TODO(http://anglebug.com/42266474): Remove me once hacked SDKs are fixed.
             UNREACHABLE();
             return MTLResourceStorageModeShared;
-#endif
     }
 }
 
@@ -705,7 +703,9 @@ TextureRef Texture::createCubeFaceView(uint32_t face)
     }
 }
 
-TextureRef Texture::createSliceMipView(uint32_t slice, const MipmapNativeLevel &level)
+TextureRef Texture::createSliceMipViewWithCompatibleFormat(uint32_t slice,
+                                                           const MipmapNativeLevel &level,
+                                                           MTLPixelFormat format)
 {
     ANGLE_MTL_OBJC_SCOPE
     {
@@ -714,7 +714,7 @@ TextureRef Texture::createSliceMipView(uint32_t slice, const MipmapNativeLevel &
             case MTLTextureTypeCube:
             case MTLTextureType2D:
             case MTLTextureType2DArray:
-                return TextureRef(new Texture(this, pixelFormat(), MTLTextureType2D,
+                return TextureRef(new Texture(this, format, MTLTextureType2D,
                                               NSMakeRange(level.get(), 1), NSMakeRange(slice, 1)));
             default:
                 UNREACHABLE();
@@ -723,6 +723,11 @@ TextureRef Texture::createSliceMipView(uint32_t slice, const MipmapNativeLevel &
     }
 }
 
+TextureRef Texture::createSliceMipView(uint32_t slice, const MipmapNativeLevel &level)
+{
+    return createSliceMipViewWithCompatibleFormat(slice, level, pixelFormat());
+}
+
 TextureRef Texture::createMipView(const MipmapNativeLevel &level)
 {
     ANGLE_MTL_OBJC_SCOPE
diff --git a/src/libANGLE/renderer/metal/mtl_state_cache.h b/src/libANGLE/renderer/metal/mtl_state_cache.h
index c196037667e7c0eee8d9e3ccd1bc1a0ccc90b41a..40d2935d70fff89f71d2b6f1f45e0d1b754a498c 100644
--- a/src/libANGLE/renderer/metal/mtl_state_cache.h
+++ b/src/libANGLE/renderer/metal/mtl_state_cache.h
@@ -304,20 +304,21 @@ struct RenderPassAttachmentDesc
     bool equalIgnoreLoadStoreOptions(const RenderPassAttachmentDesc &other) const;
     bool operator==(const RenderPassAttachmentDesc &other) const;
 
-    ANGLE_INLINE bool hasImplicitMSTexture() const { return implicitMSTexture.get(); }
-
-    const TextureRef &getImplicitMSTextureIfAvailOrTexture() const
-    {
-        return hasImplicitMSTexture() ? implicitMSTexture : texture;
-    }
+    ANGLE_INLINE bool hasResolveTexture() const { return resolveTexture.get(); }
  
+    // When rendering with implicit multisample, |texture| is the texture that
+    // will be rendered into and discarded at the end of a render pass. Its
+    // result will be automatically resolved into |resolveTexture|.
     TextureRef texture;
     // Implicit multisample texture that will be rendered into and discarded at the end of
     // a render pass. Its result will be resolved into normal texture above.
-    TextureRef implicitMSTexture;
+    TextureRef resolveTexture;
     MipmapNativeLevel level;
     uint32_t sliceOrDepth;
 
+    MipmapNativeLevel resolveLevel;
+    uint32_t resolveSliceOrDepth;
+
     // This attachment is blendable or not.
     bool blendable;
     MTLLoadAction loadAction;
diff --git a/src/libANGLE/renderer/metal/mtl_state_cache.mm b/src/libANGLE/renderer/metal/mtl_state_cache.mm
index 7acb9c322a087f9c79f779b9b244ac062e13a0ec..9ef24fef04c5271159b30f8755480d85d8860c00 100644
--- a/src/libANGLE/renderer/metal/mtl_state_cache.mm
+++ b/src/libANGLE/renderer/metal/mtl_state_cache.mm
@@ -141,44 +141,29 @@ id<MTLTexture> ToObjC(const TextureRef &texture)
 void BaseRenderPassAttachmentDescToObjC(const RenderPassAttachmentDesc &src,
                                         MTLRenderPassAttachmentDescriptor *dst)
 {
-    const TextureRef &implicitMsTexture = src.implicitMSTexture;
-
-    if (implicitMsTexture)
-    {
-        dst.texture        = ToObjC(implicitMsTexture);
-        dst.level          = 0;
-        dst.slice          = 0;
-        dst.depthPlane     = 0;
-        dst.resolveTexture = ToObjC(src.texture);
-        dst.resolveLevel   = src.level.get();
-        if (dst.resolveTexture.textureType == MTLTextureType3D)
-        {
-            dst.resolveDepthPlane = src.sliceOrDepth;
-            dst.resolveSlice      = 0;
-        }
-        else
-        {
-            dst.resolveSlice      = src.sliceOrDepth;
-            dst.resolveDepthPlane = 0;
-        }
-    }
-    else
-    {
     dst.texture = ToObjC(src.texture);
     dst.level   = src.level.get();
     if (dst.texture.textureType == MTLTextureType3D)
     {
-            dst.depthPlane = src.sliceOrDepth;
         dst.slice      = 0;
+        dst.depthPlane = src.sliceOrDepth;
     }
     else
     {
         dst.slice      = src.sliceOrDepth;
         dst.depthPlane = 0;
     }
-        dst.resolveTexture    = nil;
-        dst.resolveLevel      = 0;
+
+    dst.resolveTexture = ToObjC(src.resolveTexture);
+    dst.resolveLevel   = src.resolveLevel.get();
+    if (dst.resolveTexture.textureType == MTLTextureType3D)
+    {
         dst.resolveSlice      = 0;
+        dst.resolveDepthPlane = src.resolveSliceOrDepth;
+    }
+    else
+    {
+        dst.resolveSlice      = src.resolveSliceOrDepth;
         dst.resolveDepthPlane = 0;
     }
 
@@ -687,9 +672,11 @@ RenderPassAttachmentDesc::RenderPassAttachmentDesc()
 void RenderPassAttachmentDesc::reset()
 {
     texture.reset();
-    implicitMSTexture.reset();
+    resolveTexture.reset();
     level               = mtl::kZeroNativeMipLevel;
     sliceOrDepth        = 0;
+    resolveLevel        = mtl::kZeroNativeMipLevel;
+    resolveSliceOrDepth = 0;
     blendable           = false;
     loadAction          = MTLLoadActionLoad;
     storeAction         = MTLStoreActionStore;
@@ -699,7 +686,7 @@ void RenderPassAttachmentDesc::reset()
 bool RenderPassAttachmentDesc::equalIgnoreLoadStoreOptions(
     const RenderPassAttachmentDesc &other) const
 {
-    return texture == other.texture && implicitMSTexture == other.implicitMSTexture &&
+    return texture == other.texture && resolveTexture == other.resolveTexture &&
            level == other.level && sliceOrDepth == other.sliceOrDepth &&
            blendable == other.blendable;
 }
diff --git a/src/libANGLE/renderer/metal/mtl_utils.mm b/src/libANGLE/renderer/metal/mtl_utils.mm
index 2d23bc37558b8cb46d27a36113f4ebb880753227..51a0f284d0e85b5fb7ec349f6d1a32d11ecaf082 100644
--- a/src/libANGLE/renderer/metal/mtl_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_utils.mm
@@ -1480,6 +1480,24 @@ bool SupportsAppleGPUFamily(id<MTLDevice> device, uint8_t appleFamily)
     return [device supportsFamily:family];
 }
 
+#if (defined(__MAC_13_0) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_13_0) ||        \
+    (defined(__IPHONE_16_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_16_0) || \
+    (defined(__TVOS_16_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_16_0)
+#    define ANGLE_MTL_FEATURE_SET_DEPRECATED 1
+#    define ANGLE_MTL_GPU_FAMILY_MAC1_DEPRECATED 1
+#endif
+
+#if ANGLE_MTL_GPU_FAMILY_MAC1_DEPRECATED
+#    define ANGLE_MTL_GPU_FAMILY_MAC1 MTLGPUFamilyMac2
+#    define ANGLE_MTL_GPU_FAMILY_MAC2 MTLGPUFamilyMac2
+#elif TARGET_OS_MACCATALYST
+#    define ANGLE_MTL_GPU_FAMILY_MAC1 MTLGPUFamilyMacCatalyst1
+#    define ANGLE_MTL_GPU_FAMILY_MAC2 MTLGPUFamilyMacCatalyst2
+#else  // !ANGLE_MTL_GPU_FAMILY_MAC1_DEPRECATED && !TARGET_OS_MACCATALYST
+#    define ANGLE_MTL_GPU_FAMILY_MAC1 MTLGPUFamilyMac1
+#    define ANGLE_MTL_GPU_FAMILY_MAC2 MTLGPUFamilyMac2
+#endif
+
 bool SupportsMacGPUFamily(id<MTLDevice> device, uint8_t macFamily)
 {
 #if TARGET_OS_OSX || TARGET_OS_MACCATALYST
@@ -1525,8 +1543,7 @@ static NSUInteger getNextLocationForAttachment(const mtl::RenderPassAttachmentDe
                                                const Context *context,
                                                NSUInteger currentRenderTargetSize)
 {
-    mtl::TextureRef texture =
-        attachment.implicitMSTexture ? attachment.implicitMSTexture : attachment.texture;
+    mtl::TextureRef texture = attachment.texture;
 
     if (texture)
     {
diff --git a/src/libANGLE/renderer/metal/shaders/create_mtl_internal_shaders.py b/src/libANGLE/renderer/metal/shaders/create_mtl_internal_shaders.py
new file mode 100644
index 0000000000000000000000000000000000000000..23e8295bcc16412d3aef3fc8e85748fa4ad77a70
--- /dev/null
+++ b/src/libANGLE/renderer/metal/shaders/create_mtl_internal_shaders.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python3
+# Copyright 2021 The ANGLE Project Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# create_mtl_internal_shaders.py:
+#   Script to compile a metalLib into NSData, for including the compilded
+#       library in the ANGLE dylib.
+
+import os
+import sys
+import json
+from datetime import datetime
+
+sys.path.append('../..')
+
+template_header_boilerplate = """// GENERATED FILE - DO NOT EDIT.
+// Generated by {script_name}
+//
+// Copyright {copyright_year} The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+"""
+
+
+# Convert content of a file to byte array and store in a header file.
+# variable_name: name of C++ variable that will hold the file content as byte array.
+# filename: the file whose content will be converted to C++ byte array.
+# dest_src_file: destination header file that will contain the byte array.
+def append_file_as_byte_array_string(variable_name, filename, dest_src_file):
+    string = '// Generated from {0}:\n'.format(filename)
+    string += 'constexpr uint8_t {0}[]={{\n'.format(variable_name)
+    bytes_ = open(filename, "rb").read()
+    for byte in bytes_:
+        string += '0x{:02x}'.format(byte) + ", "
+    string += "\n};\n"
+    with open(dest_src_file, "a") as out_file:
+        out_file.write(string)
+
+
+def main():
+    input_file = sys.argv[1]
+    output_file = sys.argv[2]
+    os.chdir(sys.path[0])
+
+    boilerplate_code = template_header_boilerplate.format(
+        script_name=os.path.basename(sys.argv[0]), copyright_year=datetime.today().year)
+
+    # -------- Compile shaders -----------
+    # boiler plate code
+    os.system("echo \"{0}\" > \"{1}\"".format(boilerplate_code, output_file))
+    os.system(
+        'echo "// Compiled binary for Metal default shaders.\n\n" >>  \"{0}\"'.format(output_file))
+    os.system('echo "#include <TargetConditionals.h>\n\n" >>  \"{0}\"'.format(output_file))
+
+    os.system('echo "// clang-format off" >> \"{0}\"'.format(output_file))
+
+    append_file_as_byte_array_string('gDefaultMetallib', input_file, output_file)
+
+    os.system('echo "// clang-format on" >> \"{0}\"'.format(output_file))
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/src/libANGLE/renderer/vulkan/CLProgramVk.cpp b/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
index 170f8cb57d799d3ef843b9e4f2aa486110e3522e..6bbc8a93f3718c18e13f15e52a7e677143175176 100644
--- a/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
@@ -844,6 +844,11 @@ bool CLProgramVk::buildInternal(const cl::DevicePtrs &devices,
         // add clspv compiler options based on device features
         processedOptions += ClspvGetCompilerOptions(&device->getImpl<CLDeviceVk>());
 
+        cl_uint addressBits;
+        ANGLE_CL_IMPL_TRY(
+            device->getInfo(cl::DeviceInfo::AddressBits, sizeof(cl_uint), &addressBits, nullptr));
+        processedOptions += addressBits == 64 ? " -arch=spir64" : " -arch=spir";
+
         if (buildType != BuildType::BINARY)
         {
             // Invoke clspv
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 98a6f7458f22b793c1379a8b318dbbcd04dcd472..556198389988c07542241d4a28de5eb1442a83df 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -6087,6 +6087,10 @@ angle::Result ContextVk::syncState(const gl::Context *context,
                             break;
                         case gl::state::EXTENDED_DIRTY_BIT_BLEND_ADVANCED_COHERENT:
                             break;
+                        case gl::state::EXTENDED_DIRTY_BIT_VARIABLE_RASTERIZATION_RATE:
+                            // Noop until addition of backend support for
+                            // ANGLE_variable_rasterization_rate_metal extension
+                            break;
                         default:
                             UNREACHABLE();
                     }
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index df1efd1a8d44d1319992f5ec8086dac0168a8a66..873ce88ba04bca4a0a21e37ab547d25d7a56b425 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -697,6 +697,9 @@ bool ValidCapUncommon(const PrivateState &state, ErrorSet *errors, GLenum cap, b
             return state.getClientVersion() >= Version(2, 0) &&
                    state.getExtensions().blendEquationAdvancedCoherentKHR;
 
+        case GL_VARIABLE_RASTERIZATION_RATE_ANGLE:
+            return state.getExtensions().variableRasterizationRateMetalANGLE;
+
         default:
             break;
     }
@@ -6114,6 +6117,18 @@ bool ValidateMaxShaderCompilerThreadsKHR(const Context *context,
     return true;
 }
 
+bool ValidateBindMetalRasterizationRateMapANGLE(const Context *context,
+                                                angle::EntryPoint entryPoint,
+                                                GLMTLRasterizationRateMapANGLE map)
+{
+    if (!context->getExtensions().variableRasterizationRateMetalANGLE)
+    {
+        ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kExtensionNotEnabled);
+        return false;
+    }
+    return true;
+}
+
 bool ValidateMultiDrawArraysANGLE(const Context *context,
                                   angle::EntryPoint entryPoint,
                                   PrimitiveMode mode,
diff --git a/src/libANGLE/validationES32.cpp b/src/libANGLE/validationES32.cpp
index e882e8eb92ee039f3d75866da2075cc1d8e4541f..acabea832b47994eaf3ab387dbccede079c29c20 100644
--- a/src/libANGLE/validationES32.cpp
+++ b/src/libANGLE/validationES32.cpp
@@ -464,12 +464,13 @@ bool ValidateGetPointerv(const Context *context,
                 return false;
             }
             break;
-
+        // GL_ANGLE_variable_rasterization_rate_metal
+        case GL_METAL_RASTERIZATION_RATE_MAP_BINDING_ANGLE:
+            return context->getExtensions().variableRasterizationRateMetalANGLE;
         default:
             ANGLE_VALIDATION_ERROR(GL_INVALID_ENUM, kInvalidPointerQuery);
             return false;
     }
-
     return true;
 }
 
diff --git a/src/libANGLE/validationESEXT.cpp b/src/libANGLE/validationESEXT.cpp
index 31df3f190cf096e9e138ae031b8e53c997325f6d..03ddffc94def573f8938023e0e68938075aed4de 100644
--- a/src/libANGLE/validationESEXT.cpp
+++ b/src/libANGLE/validationESEXT.cpp
@@ -4542,6 +4542,29 @@ bool ValidateLogicOpANGLE(const PrivateState &state,
     return ValidateLogicOpCommon(state, errors, entryPoint, opcodePacked);
 }
 
+// GL_WEBKIT_explicit_resolve_target
+bool ValidateFramebufferResolveRenderbufferWEBKIT(const Context *context,
+                                                  angle::EntryPoint entryPoint,
+                                                  GLenum target,
+                                                  GLenum attachment,
+                                                  GLenum renderbuffertarget,
+                                                  RenderbufferID renderbuffer)
+{
+#if ANGLE_WEBKIT_EXPLICIT_RESOLVE_TARGET_ENABLED
+    if (!context->getExtensions().explicitResolveTargetWEBKIT)
+    {
+        ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kExtensionNotEnabled);
+        return false;
+    }
+
+    return ValidateFramebufferRenderbufferBase(context, entryPoint, target, attachment,
+                                               renderbuffertarget, renderbuffer);
+#else
+    UNIMPLEMENTED();
+    return false;
+#endif
+}
+
 bool ValidateFramebufferFoveationConfigQCOM(const Context *context,
                                             angle::EntryPoint entryPoint,
                                             FramebufferID framebufferPacked,
diff --git a/src/libANGLE/validationESEXT_autogen.h b/src/libANGLE/validationESEXT_autogen.h
index 9bdc56f0bf198d179439cbba1253bcb421a92d9e..649a74d927f0cb6b1b5b269ccdfbae18f367a79a 100644
--- a/src/libANGLE/validationESEXT_autogen.h
+++ b/src/libANGLE/validationESEXT_autogen.h
@@ -1006,6 +1006,11 @@ bool ValidateGetTranslatedShaderSourceANGLE(const Context *context,
                                             const GLsizei *length,
                                             const GLchar *source);
 
+// GL_ANGLE_variable_rasterization_rate_metal
+bool ValidateBindMetalRasterizationRateMapANGLE(const Context *context,
+                                                angle::EntryPoint entryPoint,
+                                                GLMTLRasterizationRateMapANGLE map);
+
 // GL_ANGLE_vulkan_image
 bool ValidateAcquireTexturesANGLE(const Context *context,
                                   angle::EntryPoint entryPoint,
@@ -2936,6 +2941,14 @@ bool ValidateStartTilingQCOM(const Context *context,
                              GLuint width,
                              GLuint height,
                              GLbitfield preserveMask);
+
+// GL_WEBKIT_explicit_resolve_target
+bool ValidateFramebufferResolveRenderbufferWEBKIT(const Context *context,
+                                                  angle::EntryPoint entryPoint,
+                                                  GLenum target,
+                                                  GLenum attachment,
+                                                  GLenum renderbuffertarget,
+                                                  RenderbufferID renderbufferPacked);
 }  // namespace gl
 
 #endif  // LIBANGLE_VALIDATION_ESEXT_AUTOGEN_H_
diff --git a/src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp b/src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp
index 75583a64402a65144e4573552c1debb879725b1b..4f7aedccfabd0fccb861fc575f82eef543a321ba 100644
--- a/src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp
+++ b/src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp
@@ -182,6 +182,7 @@ const ProcEntry g_procTable[] = {
     {"glBindFramebuffer", P(GL_BindFramebuffer)},
     {"glBindFramebufferOES", P(GL_BindFramebufferOES)},
     {"glBindImageTexture", P(GL_BindImageTexture)},
+    {"glBindMetalRasterizationRateMapANGLE", P(GL_BindMetalRasterizationRateMapANGLE)},
     {"glBindProgramPipeline", P(GL_BindProgramPipeline)},
     {"glBindProgramPipelineEXT", P(GL_BindProgramPipelineEXT)},
     {"glBindRenderbuffer", P(GL_BindRenderbuffer)},
@@ -397,6 +398,7 @@ const ProcEntry g_procTable[] = {
     {"glFramebufferPixelLocalStorageRestoreANGLE", P(GL_FramebufferPixelLocalStorageRestoreANGLE)},
     {"glFramebufferRenderbuffer", P(GL_FramebufferRenderbuffer)},
     {"glFramebufferRenderbufferOES", P(GL_FramebufferRenderbufferOES)},
+    {"glFramebufferResolveRenderbufferWEBKIT", P(GL_FramebufferResolveRenderbufferWEBKIT)},
     {"glFramebufferTexture", P(GL_FramebufferTexture)},
     {"glFramebufferTexture2D", P(GL_FramebufferTexture2D)},
     {"glFramebufferTexture2DMultisampleEXT", P(GL_FramebufferTexture2DMultisampleEXT)},
diff --git a/src/libGLESv2/entry_points_gles_ext_autogen.cpp b/src/libGLESv2/entry_points_gles_ext_autogen.cpp
index b1c1f158678acba8ce38df87c6cd0122ebd1468f..b6f87de9dbcd7a574b910486b76859bb966b3965 100644
--- a/src/libGLESv2/entry_points_gles_ext_autogen.cpp
+++ b/src/libGLESv2/entry_points_gles_ext_autogen.cpp
@@ -4817,6 +4817,36 @@ void GL_APIENTRY GL_GetTranslatedShaderSourceANGLE(GLuint shader,
     ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
 }
 
+// GL_ANGLE_variable_rasterization_rate_metal
+void GL_APIENTRY GL_BindMetalRasterizationRateMapANGLE(GLuint renderbuffer, GLMTLRasterizationRateMapANGLE map)
+{
+    Context *context = GetValidGlobalContext();
+    EVENT(context, GLBindMetalRasterizationRateMapANGLE, "context = %d, map = 0x%016" PRIxPTR "",
+          CID(context), (uintptr_t)map);
+
+    if (context)
+    {
+        SCOPED_SHARE_CONTEXT_LOCK(context);
+        bool isCallValid =
+            (context->skipValidation() ||
+             (ValidatePixelLocalStorageInactive(
+                  context->getPrivateState(), context->getMutableErrorSetForValidation(),
+                  angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE) &&
+              ValidateBindMetalRasterizationRateMapANGLE(
+                  context, angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE, map)));
+        if (isCallValid)
+        {
+            context->bindMetalRasterizationRateMap(renderbuffer, map);
+        }
+        ANGLE_CAPTURE_GL(BindMetalRasterizationRateMapANGLE, isCallValid, context, map);
+    }
+    else
+    {
+        GenerateContextLostErrorOnCurrentGlobalContext();
+    }
+    ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
+}
+
 // GL_ANGLE_vulkan_image
 void GL_APIENTRY GL_AcquireTexturesANGLE(GLuint numTextures,
                                          const GLuint *textures,
@@ -14417,4 +14447,44 @@ GL_StartTilingQCOM(GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield p
     ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
 }
 
+// GL_WEBKIT_explicit_resolve_target
+void GL_APIENTRY GL_FramebufferResolveRenderbufferWEBKIT(GLenum target,
+                                                         GLenum attachment,
+                                                         GLenum renderbuffertarget,
+                                                         GLuint renderbuffer)
+{
+    Context *context = GetValidGlobalContext();
+    EVENT(context, GLFramebufferResolveRenderbufferWEBKIT,
+          "context = %d, target = %s, attachment = %s, renderbuffertarget = %s, renderbuffer = %u",
+          CID(context), GLenumToString(GLESEnum::AllEnums, target),
+          GLenumToString(GLESEnum::AllEnums, attachment),
+          GLenumToString(GLESEnum::AllEnums, renderbuffertarget), renderbuffer);
+
+    if (context)
+    {
+        RenderbufferID renderbufferPacked = PackParam<RenderbufferID>(renderbuffer);
+        SCOPED_SHARE_CONTEXT_LOCK(context);
+        bool isCallValid =
+            (context->skipValidation() ||
+             (ValidatePixelLocalStorageInactive(
+                  context->getPrivateState(), context->getMutableErrorSetForValidation(),
+                  angle::EntryPoint::GLFramebufferResolveRenderbufferWEBKIT) &&
+              ValidateFramebufferResolveRenderbufferWEBKIT(
+                  context, angle::EntryPoint::GLFramebufferResolveRenderbufferWEBKIT, target,
+                  attachment, renderbuffertarget, renderbufferPacked)));
+        if (isCallValid)
+        {
+            context->framebufferResolveRenderbufferWEBKIT(target, attachment, renderbuffertarget,
+                                                          renderbufferPacked);
+        }
+        ANGLE_CAPTURE_GL(FramebufferResolveRenderbufferWEBKIT, isCallValid, context, target,
+                         attachment, renderbuffertarget, renderbufferPacked);
+    }
+    else
+    {
+        GenerateContextLostErrorOnCurrentGlobalContext();
+    }
+    ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
+}
+
 }  // extern "C"
diff --git a/src/libGLESv2/entry_points_gles_ext_autogen.h b/src/libGLESv2/entry_points_gles_ext_autogen.h
index f50c97ac7c4166369ce804a08d3f83a0cc5f0e96..562577886d6ed0813b9b32b4ffeb1346dc5f11d4 100644
--- a/src/libGLESv2/entry_points_gles_ext_autogen.h
+++ b/src/libGLESv2/entry_points_gles_ext_autogen.h
@@ -744,6 +744,10 @@ ANGLE_EXPORT void GL_APIENTRY GL_GetTranslatedShaderSourceANGLE(GLuint shader,
                                                                 GLsizei *length,
                                                                 GLchar *source);
 
+// GL_ANGLE_variable_rasterization_rate_metal
+ANGLE_EXPORT void GL_APIENTRY
+GL_BindMetalRasterizationRateMapANGLE(GLuint framebuffer, GLMTLRasterizationRateMapANGLE map);
+
 // GL_ANGLE_vulkan_image
 ANGLE_EXPORT void GL_APIENTRY GL_AcquireTexturesANGLE(GLuint numTextures,
                                                       const GLuint *textures,
@@ -1975,6 +1979,12 @@ ANGLE_EXPORT void GL_APIENTRY GL_TextureFoveationParametersQCOM(GLuint texture,
 ANGLE_EXPORT void GL_APIENTRY GL_EndTilingQCOM(GLbitfield preserveMask);
 ANGLE_EXPORT void GL_APIENTRY
 GL_StartTilingQCOM(GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask);
+
+// GL_WEBKIT_explicit_resolve_target
+ANGLE_EXPORT void GL_APIENTRY GL_FramebufferResolveRenderbufferWEBKIT(GLenum target,
+                                                                      GLenum attachment,
+                                                                      GLenum renderbuffertarget,
+                                                                      GLuint renderbuffer);
 }  // extern "C"
 
 #endif  // LIBGLESV2_ENTRY_POINTS_GLES_EXT_AUTOGEN_H_
diff --git a/src/libGLESv2/libGLESv2_autogen.cpp b/src/libGLESv2/libGLESv2_autogen.cpp
index 76ced2d99168cf9e28f2e22425657948047573ee..af0caf74ce1b632d94582ad1df6cf22e436715a3 100644
--- a/src/libGLESv2/libGLESv2_autogen.cpp
+++ b/src/libGLESv2/libGLESv2_autogen.cpp
@@ -3948,6 +3948,12 @@ void GL_APIENTRY glGetTranslatedShaderSourceANGLE(GLuint shader,
     return GL_GetTranslatedShaderSourceANGLE(shader, bufSize, length, source);
 }
 
+// GL_ANGLE_variable_rasterization_rate_metal
+void GL_APIENTRY glBindMetalRasterizationRateMapANGLE(GLuint renderbuffer, GLMTLRasterizationRateMapANGLE map)
+{
+    return GL_BindMetalRasterizationRateMapANGLE(renderbuffer, map);
+}
+
 // GL_ANGLE_vulkan_image
 void GL_APIENTRY glAcquireTexturesANGLE(GLuint numTextures,
                                         const GLuint *textures,
@@ -6118,4 +6124,14 @@ glStartTilingQCOM(GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield pr
     return GL_StartTilingQCOM(x, y, width, height, preserveMask);
 }
 
+// GL_WEBKIT_explicit_resolve_target
+void GL_APIENTRY glFramebufferResolveRenderbufferWEBKIT(GLenum target,
+                                                        GLenum attachment,
+                                                        GLenum renderbuffertarget,
+                                                        GLuint renderbuffer)
+{
+    return GL_FramebufferResolveRenderbufferWEBKIT(target, attachment, renderbuffertarget,
+                                                   renderbuffer);
+}
+
 }  // extern "C"
diff --git a/src/libGLESv2/libGLESv2_autogen.def b/src/libGLESv2/libGLESv2_autogen.def
index f637f2b1818f24341efe75eeb5273646e9e6572f..fc39e179b84bf0b31de43b9d738636d9fe533d28 100644
--- a/src/libGLESv2/libGLESv2_autogen.def
+++ b/src/libGLESv2/libGLESv2_autogen.def
@@ -672,6 +672,9 @@ EXPORTS
     ; GL_ANGLE_translated_shader_source
     glGetTranslatedShaderSourceANGLE
 
+    ; GL_ANGLE_variable_rasterization_rate_metal
+    glBindMetalRasterizationRateMapANGLE
+
     ; GL_ANGLE_vulkan_image
     glAcquireTexturesANGLE
     glReleaseTexturesANGLE
@@ -1292,6 +1295,9 @@ EXPORTS
     glEndTilingQCOM
     glStartTilingQCOM
 
+    ; GL_WEBKIT_explicit_resolve_target
+    glFramebufferResolveRenderbufferWEBKIT
+
     ; EGL 1.0
     EGL_ChooseConfig
     EGL_CopyBuffers
diff --git a/src/libGLESv2/libGLESv2_no_capture_autogen.def b/src/libGLESv2/libGLESv2_no_capture_autogen.def
index 5c850d9638211f7f7f4fa0e175d4e5a2d9a05c71..6e257519c5f7582fef749ef2d1ee3b525180702d 100644
--- a/src/libGLESv2/libGLESv2_no_capture_autogen.def
+++ b/src/libGLESv2/libGLESv2_no_capture_autogen.def
@@ -672,6 +672,9 @@ EXPORTS
     ; GL_ANGLE_translated_shader_source
     glGetTranslatedShaderSourceANGLE
 
+    ; GL_ANGLE_variable_rasterization_rate_metal
+    glBindMetalRasterizationRateMapANGLE
+
     ; GL_ANGLE_vulkan_image
     glAcquireTexturesANGLE
     glReleaseTexturesANGLE
@@ -1292,6 +1295,9 @@ EXPORTS
     glEndTilingQCOM
     glStartTilingQCOM
 
+    ; GL_WEBKIT_explicit_resolve_target
+    glFramebufferResolveRenderbufferWEBKIT
+
     ; EGL 1.0
     EGL_ChooseConfig
     EGL_CopyBuffers
diff --git a/src/libGLESv2/libGLESv2_vulkan_secondaries_autogen.def b/src/libGLESv2/libGLESv2_vulkan_secondaries_autogen.def
index 379e81a709496b484d091f428e8d8990244fc52c..fb9c537ad9c466888b10564ac3ddf71ddd15538a 100644
--- a/src/libGLESv2/libGLESv2_vulkan_secondaries_autogen.def
+++ b/src/libGLESv2/libGLESv2_vulkan_secondaries_autogen.def
@@ -672,6 +672,9 @@ EXPORTS
     ; GL_ANGLE_translated_shader_source
     glGetTranslatedShaderSourceANGLE
 
+    ; GL_ANGLE_variable_rasterization_rate_metal
+    glBindMetalRasterizationRateMapANGLE
+
     ; GL_ANGLE_vulkan_image
     glAcquireTexturesANGLE
     glReleaseTexturesANGLE
@@ -1292,6 +1295,9 @@ EXPORTS
     glEndTilingQCOM
     glStartTilingQCOM
 
+    ; GL_WEBKIT_explicit_resolve_target
+    glFramebufferResolveRenderbufferWEBKIT
+
     ; EGL 1.0
     EGL_ChooseConfig
     EGL_CopyBuffers
diff --git a/src/libGLESv2/libGLESv2_with_capture_autogen.def b/src/libGLESv2/libGLESv2_with_capture_autogen.def
index 4e329ce76aa2df784eaa6328e116df2f8c3aaacd..714c611c34d7ae22b71403b3563abb3fc7fb4e69 100644
--- a/src/libGLESv2/libGLESv2_with_capture_autogen.def
+++ b/src/libGLESv2/libGLESv2_with_capture_autogen.def
@@ -672,6 +672,9 @@ EXPORTS
     ; GL_ANGLE_translated_shader_source
     glGetTranslatedShaderSourceANGLE
 
+    ; GL_ANGLE_variable_rasterization_rate_metal
+    glBindMetalRasterizationRateMapANGLE
+
     ; GL_ANGLE_vulkan_image
     glAcquireTexturesANGLE
     glReleaseTexturesANGLE
@@ -1292,6 +1295,9 @@ EXPORTS
     glEndTilingQCOM
     glStartTilingQCOM
 
+    ; GL_WEBKIT_explicit_resolve_target
+    glFramebufferResolveRenderbufferWEBKIT
+
     ; EGL 1.0
     EGL_ChooseConfig
     EGL_CopyBuffers
diff --git a/src/libGLESv2/opengl32_autogen.def b/src/libGLESv2/opengl32_autogen.def
index a3d951ba0ee8f6d479f11c265f7f0cb9c93ff0d4..d0d37ddca34b2d85e6635e679781b05f64e13ef4 100644
--- a/src/libGLESv2/opengl32_autogen.def
+++ b/src/libGLESv2/opengl32_autogen.def
@@ -672,6 +672,9 @@ EXPORTS
     ; GL_ANGLE_translated_shader_source
     glGetTranslatedShaderSourceANGLE
 
+    ; GL_ANGLE_variable_rasterization_rate_metal
+    glBindMetalRasterizationRateMapANGLE
+
     ; GL_ANGLE_vulkan_image
     glAcquireTexturesANGLE
     glReleaseTexturesANGLE
@@ -1276,3 +1279,6 @@ EXPORTS
     ; GL_QCOM_tiled_rendering
     glEndTilingQCOM
     glStartTilingQCOM
+
+    ; GL_WEBKIT_explicit_resolve_target
+    glFramebufferResolveRenderbufferWEBKIT
diff --git a/src/tests/angle_end2end_tests.gni b/src/tests/angle_end2end_tests.gni
index c7fdefbaa646d44373e5e38b73b2d30b72890dd7..f1069c293262b4ea9cad1e6379611141799e1cc2 100644
--- a/src/tests/angle_end2end_tests.gni
+++ b/src/tests/angle_end2end_tests.gni
@@ -87,6 +87,7 @@ angle_end2end_tests_sources = [
   "gl_tests/FramebufferMixedSamplesTest.cpp",
   "gl_tests/FramebufferMultiviewTest.cpp",
   "gl_tests/FramebufferRenderMipmapTest.cpp",
+  "gl_tests/FramebufferResolveTest.cpp",
   "gl_tests/FramebufferTest.cpp",
   "gl_tests/GLSLTest.cpp",
   "gl_tests/GeometryShaderTest.cpp",
@@ -167,6 +168,7 @@ angle_end2end_tests_sources = [
   "gl_tests/TextureTest.cpp",
   "gl_tests/TextureUploadFormatTest.cpp",
   "gl_tests/TiledRenderingTest.cpp",
+  "gl_tests/TimeoutDrawTest.cpp",
   "gl_tests/TimerQueriesTest.cpp",
   "gl_tests/TransformFeedbackTest.cpp",
   "gl_tests/UniformBufferTest.cpp",
@@ -230,6 +232,7 @@ angle_end2end_tests_mac_sources = [
   "egl_tests/EGLSyncTestMetalSharedEvent.mm",
   "egl_tests/EGLWaitUntilWorkScheduledTest.cpp",
   "gl_tests/ImageTestMetal.mm",
+  "gl_tests/VariableRasterizationRateTestMetal.mm",
 ]
 angle_end2end_tests_win_sources = [
   "egl_tests/EGLDeviceTest.cpp",
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index e47beb99b5e7da2344d8f29e0d8fc34175182154..f534a50b70bdb36f488740016f1e7625c7f07910 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -20630,6 +20630,816 @@ void main ()
     drawQuad(program, "position", 0);
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
 }
+
+// Test highp int scalar + vec
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingAdd1)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u + ivec4(0, -1, 1, 2147483645);
+    o.r = r.x == 2 ? 1.0 : 0.0;
+    o.g = r.y == 1 ? 1.0 : 0.0;
+    o.b = r.z == 3 ? 1.0 : 0.0;
+    o.a = r.w == 2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec + scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingAdd2)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, 2147483645) + u;
+    o.r = r.x == 2 ? 1.0 : 0.0;
+    o.g = r.y == 1 ? 1.0 : 0.0;
+    o.b = r.z == 3 ? 1.0 : 0.0;
+    o.a = r.w == 2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec += scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingAdd3)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, 2147483645);
+    r += u;
+    o.r = r.x == 2 ? 1.0 : 0.0;
+    o.g = r.y == 1 ? 1.0 : 0.0;
+    o.b = r.z == 3 ? 1.0 : 0.0;
+    o.a = r.w == 2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar + vec handles UB
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingAddUB)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u + ivec4(0, -1, 1, 2147483647);
+    o.r = r.x == 2 ? 1.0 : 0.0;
+    o.g = r.y == 1 ? 1.0 : 0.0;
+    o.b = r.z == 3 ? 1.0 : 0.0;
+    o.a = r.w == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar - vec
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingSub1)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u - ivec4(0, -1, 1, 2147483647);
+    o.r = r.x == 2 ? 1.0 : 0.0;
+    o.g = r.y == 3 ? 1.0 : 0.0;
+    o.b = r.z == 1 ? 1.0 : 0.0;
+    o.a = r.w == -2147483645 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec - scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingSub2)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, -2147483646) - u;
+    o.r = r.x == -2 ? 1.0 : 0.0;
+    o.g = r.y == -3 ? 1.0 : 0.0;
+    o.b = r.z == -1 ? 1.0 : 0.0;
+    o.a = r.w == -2147483648 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec -= scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingSub3)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, -2147483646);
+    r -= u;
+    o.r = r.x == -2 ? 1.0 : 0.0;
+    o.g = r.y == -3 ? 1.0 : 0.0;
+    o.b = r.z == -1 ? 1.0 : 0.0;
+    o.a = r.w == -2147483648 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+//
+// Test highp int scalar - vec handles UB
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingSubUB)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, -2147483648) - u;
+    o.r = r.x == -2 ? 1.0 : 0.0;
+    o.g = r.y == -3 ? 1.0 : 0.0;
+    o.b = r.z == -1 ? 1.0 : 0.0;
+    o.a = r.w == 2147483646 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar * vec
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingMul1)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u * ivec4(0, -1, 1, 3);
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == -2 ? 1.0 : 0.0;
+    o.b = r.z == 2 ? 1.0 : 0.0;
+    o.a = r.w == 6 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec * scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingMul2)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, 3) * u;
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == -2 ? 1.0 : 0.0;
+    o.b = r.z == 2 ? 1.0 : 0.0;
+    o.a = r.w == 6 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec *= scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingMul3)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, 3);
+    r *= u;
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == -2 ? 1.0 : 0.0;
+    o.b = r.z == 2 ? 1.0 : 0.0;
+    o.a = r.w == 6 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar * vector handles UB
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingMulUB)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u * ivec4(0, -1, 1, -2147483648);
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == 1 ? 1.0 : 0.0;
+    o.b = r.z == -1 ? 1.0 : 0.0;
+    o.a = r.w == -2147483648 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, -1);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar / vec
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingDiv1)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u / ivec4(2, -1, 1, 3);
+    o.r = r.x == 1 ? 1.0 : 0.0;
+    o.g = r.y == -2 ? 1.0 : 0.0;
+    o.b = r.z == 2 ? 1.0 : 0.0;
+    o.a = r.w == 0 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec / scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingDiv2)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(2, -1, 1, 3) / u;
+    o.r = r.x == 1 ? 1.0 : 0.0;
+    o.g = r.y == 0 ? 1.0 : 0.0;
+    o.b = r.z == 0 ? 1.0 : 0.0;
+    o.a = r.w == 1 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec /= scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingDiv3)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(2, -1, 1, 3);
+    r /= u;
+    o.r = r.x == 1 ? 1.0 : 0.0;
+    o.g = r.y == 0 ? 1.0 : 0.0;
+    o.b = r.z == 0 ? 1.0 : 0.0;
+    o.a = r.w == 1 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar / vec handles UB
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingDivUB)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u / ivec4(0, -1, 1, 3);
+    o.r = r.x == -2147483648 ? 1.0 : 0.0;
+    // FIXME: ANGLE_div should also handle -INT_MAX / -1
+    // o.g = r.y == -2147483648 ? 1.0 : 0.0;
+    o.g = 1.0;
+    o.b = r.z == -2147483648 ? 1.0 : 0.0;
+    o.a = r.w == -715827882 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, -2147483648);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar % vec
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingMod1)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u % ivec4(2, 7, 1, 3);
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == 2 ? 1.0 : 0.0;
+    o.b = r.z == 0 ? 1.0 : 0.0;
+    o.a = r.w == 2 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec % scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingMod2)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(2147483647, 0, 1, 3) % u;
+    o.r = r.x == 1 ? 1.0 : 0.0;
+    o.g = r.y == 0 ? 1.0 : 0.0;
+    o.b = r.z == 1 ? 1.0 : 0.0;
+    o.a = r.w == 1 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int vec %= scalar
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingMod3)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(2147483647, 0, 1, 3);
+    r %= u;
+    o.r = r.x == 1 ? 1.0 : 0.0;
+    o.g = r.y == 0 || r.y == 1 ? 1.0 : 0.0;
+    o.b = r.z == 1 ? 1.0 : 0.0;
+    o.a = r.w == 1 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int scalar % vec handles UB
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingModUB)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = u % ivec4(0, -1, 1, 3);
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == 0 ? 1.0 : 0.0;
+    o.b = r.z == 0 ? 1.0 : 0.0;
+    o.a = r.w == -2 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, -2147483648);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int float to int vector conversion
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingFtoi1)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform vec4 u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(u);
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == -1 ? 1.0 : 0.0;
+    o.b = r.z == -2147483648 ? 1.0 : 0.0;
+    o.a = r.w ==  83645 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4f(u, 0., -1., -2147483648., 83645.);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int float to int scalar conversion
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingFtoi2)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform vec4 u;
+out vec4 o;
+void main() {
+    int rx = int(u.x);
+    int ry = int(u.y);
+    int rz = int(u.z);
+    int rw = int(u.w);
+    o.r = rx == 0 ? 1.0 : 0.0;
+    o.g = ry == -1 ? 1.0 : 0.0;
+    o.b = rz == -2147483648 ? 1.0 : 0.0;
+    o.a = rw ==  83645 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4f(u, 0., -1., -2147483648., 83645.);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+//
+// Test highp int float to int vector conversion handles UB
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingFtoiUB1)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform vec4 u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(u);
+    o.r = r.x == 0 ? 1.0 : 0.0;
+    o.g = r.y == -1 ? 1.0 : 0.0;
+    o.b = r.z == -2147483648 ? 1.0 : 0.0;
+    o.a = r.w ==  2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4f(u, 0., -1., -2147483649., 2147483648.);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int float to int scalar conversion handles UB
+TEST_P(GLSLTest_ES3, IntVecOperatorOverloadingFtoiUB2)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform vec4 u;
+out vec4 o;
+void main() {
+    int rx = int(u.x);
+    int ry = int(u.y);
+    int rz = int(u.z);
+    int rw = int(u.w);
+    o.r = rx == 0 ? 1.0 : 0.0;
+    o.g = ry == -1 ? 1.0 : 0.0;
+    o.b = rz == -2147483648 ? 1.0 : 0.0;
+    o.a = rw ==  2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4f(u, 0., -1., -2147483649., 2147483648.);
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int wrap behavior with plus and minus.
+TEST_P(GLSLTest_ES3, IntVecAdditionSubtractionWrap)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(u) + ivec4(0, -1, 1, 3);
+    r -= ivec4(2147483647, 2147483647, 2147483647, 0);
+    r += ivec4(0, 0, 0, 2147483647);
+    o.r = r.x == -2147483647 ? 1.0 : 0.0;
+    o.g = r.y == -2147483648 ? 1.0 : 0.0;
+    o.b = r.z == -2147483646 ? 1.0 : 0.0;
+    o.a = r.w == -2147483646 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int wrap behavior with multiplication.
+TEST_P(GLSLTest_ES3, IntVecMultiplicationWrap)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(u) + ivec4(-2, 2, 1, -1);
+    r *= ivec4(2147483647, 2147483647, -2147483648, -2147483648);
+    o.r = r.x == 2 ? 1.0 : 0.0;
+    o.g = r.y == -2 ? 1.0 : 0.0;
+    o.b = r.z == -2147483648 ? 1.0 : 0.0;
+    o.a = r.w == -2147483648 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int wrap behavior with division.
+TEST_P(GLSLTest_ES3, IntVecDivisionWrap)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(u) + ivec4(2147483647, 2147483647, -2147483648, -2147483648);
+    r /= ivec4(1, -1, 1, -1);
+    o.r = r.x == 2147483647 ? 1.0 : 0.0;
+    o.g = r.y == -2147483647 ? 1.0 : 0.0;
+    o.b = r.z == -2147483648 ? 1.0 : 0.0;
+    o.a = 1.0;
+
+    // FIXME
+    //o.a = r.w == -2147483648 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int wrap behavior with modulus.
+TEST_P(GLSLTest_ES3, IntScalarModulus)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(u) + ivec4(2147483647, 2147483647, -2147483648, -2147483648);
+    r %= 8;
+    o.r = r.x == 7 ? 1.0 : 0.0;
+    o.g = r.y == 7 ? 1.0 : 0.0;
+    o.b = r.z == 0 ? 1.0 : 0.0;
+    o.a = r.w == 0 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp int modulus undefinedness with negative numbers
+TEST_P(GLSLTest_ES3, IntScalarModulusUB1)
+{
+    ANGLE_SKIP_TEST_IF(!IsMetal());
+
+    constexpr char kFS[] = R"(#version 300 es
+precision highp int;
+precision highp float;
+uniform int u;
+out vec4 o;
+void main() {
+    ivec4 r = ivec4(u) + ivec4(-1, 1, -7, 7);
+    r %= 8;
+    o.r = r.x == -1 ? 1.0 : 0.0;
+    o.g = r.y == 1 ? 1.0 : 0.0;
+    o.b = r.z == -7 ? 1.0 : 0.0;
+    o.a = r.w == 7 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
 }  // anonymous namespace
 
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(
diff --git a/src/tests/gl_tests/PbufferTest.cpp b/src/tests/gl_tests/PbufferTest.cpp
index 9cdce6ef4334e95dda21e6e011688bc47d96e59b..ee3b1bf1a0a14166e6b43580659b9e49dbed1ea4 100644
--- a/src/tests/gl_tests/PbufferTest.cpp
+++ b/src/tests/gl_tests/PbufferTest.cpp
@@ -302,7 +302,6 @@ TEST_P(PbufferTest, BindTexImageOverwrite)
     // Test skipped because Pbuffers are not supported or Pbuffer does not support binding to RGBA
     // textures.
     ANGLE_SKIP_TEST_IF(!mSupportsPbuffers || !mSupportsBindTexImage);
-
     EGLWindow *window = getEGLWindow();
     window->makeCurrent();
 
@@ -532,7 +531,6 @@ TEST_P(PbufferTest, BindTexImageOverwriteReleasesOrphanedPbuffer)
     // Test skipped because Pbuffers are not supported or Pbuffer does not support binding to RGBA
     // textures.
     ANGLE_SKIP_TEST_IF(!mSupportsPbuffers || !mSupportsBindTexImage);
-
     EGLWindow *window = getEGLWindow();
     window->makeCurrent();
 
diff --git a/src/tests/gl_tests/TimeoutDrawTest.cpp b/src/tests/gl_tests/TimeoutDrawTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0320255d8d6340e9d5a7a4dd0543c0053e8f59ea
--- /dev/null
+++ b/src/tests/gl_tests/TimeoutDrawTest.cpp
@@ -0,0 +1,176 @@
+//
+// Copyright 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "test_utils/ANGLETest.h"
+
+#include "test_utils/gl_raii.h"
+#include "util/shader_utils.h"
+
+using namespace angle;
+
+namespace
+{
+class TimeoutDrawTest : public ANGLETest<>
+{
+  protected:
+    TimeoutDrawTest()
+    {
+        setWindowWidth(128);
+        setWindowHeight(128);
+        setConfigRedBits(8);
+        setConfigGreenBits(8);
+        setConfigBlueBits(8);
+        setConfigAlphaBits(8);
+        // Tests should skip if robustness not supported, but this can be done only after
+        // Metal supports robustness.
+        if (IsEGLClientExtensionEnabled("EGL_EXT_create_context_robustness"))
+        {
+            setContextResetStrategy(EGL_LOSE_CONTEXT_ON_RESET_EXT);
+        }
+        else
+        {
+            setContextResetStrategy(EGL_NO_RESET_NOTIFICATION_EXT);
+        }
+    }
+    void testSetUp() override
+    {
+        glClear(GL_COLOR_BUFFER_BIT);
+        glFinish();
+    }
+};
+
+// Tests that trivial infinite loops in vertex shaders hang instead of progress.
+TEST_P(TimeoutDrawTest, TrivialInfiniteLoopVS)
+{
+    constexpr char kVS[] = R"(precision highp float;
+attribute vec4 a_position;
+void main()
+{
+    for (;;) {}
+    gl_Position = a_position;
+})";
+    ANGLE_GL_PROGRAM(program, kVS, essl1_shaders::fs::Red());
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glFinish();
+    EXPECT_GL_ERROR(GL_CONTEXT_LOST);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);  // Should read through client buffer since context should be lost.
+}
+
+// Tests that trivial infinite loops in fragment shaders hang instead of progress.
+TEST_P(TimeoutDrawTest, TrivialInfiniteLoopFS)
+{
+    constexpr char kFS[] = R"(precision mediump float;
+void main()
+{
+    for (;;) {}
+    gl_FragColor = vec4(1, 0, 0, 1);
+})";
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glFinish();
+    EXPECT_GL_ERROR(GL_CONTEXT_LOST);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);  // Should read through client buffer since context should be lost.
+}
+
+
+// Tests that infinite loops based on user-supplied values in vertex shaders hang instead of progress.
+// Otherwise optimizer would be able to assume something about the domain of the user-supplied value.
+TEST_P(TimeoutDrawTest, DynamicInfiniteLoopVS)
+{
+    constexpr char kVS[] = R"(precision highp float;
+attribute vec4 a_position;
+uniform int f;
+void main()
+{
+    for (;f != 0;) {}
+    gl_Position = a_position;
+})";
+    ANGLE_GL_PROGRAM(program, kVS, essl1_shaders::fs::Red());
+
+    glUseProgram(program);
+    GLint uniformLocation = glGetUniformLocation(program, "f");
+    glUniform1i(uniformLocation, 77);
+
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glFinish();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);  // Should read through client buffer since context should be lost.
+    EXPECT_GL_ERROR(GL_CONTEXT_LOST);
+}
+
+// Tests that infinite loops based on user-supplied values in fragment shaders hang instead of progress.
+// Otherwise optimizer would be able to assume something about the domain of the user-supplied value.
+TEST_P(TimeoutDrawTest, DynamicInfiniteLoopFS)
+{
+    constexpr char kFS[] = R"(precision mediump float;
+uniform int f;
+void main()
+{
+    for (;f != 0;) {}
+    gl_FragColor = vec4(1, 0, 0, 1);
+})";
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
+    glUseProgram(program);
+    GLint uniformLocation = glGetUniformLocation(program, "f");
+    glUniform1i(uniformLocation, 88);
+    EXPECT_GL_NO_ERROR();
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glFinish();
+    EXPECT_GL_ERROR(GL_CONTEXT_LOST);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);  // Should read through client buffer since context should be lost.
+}
+
+// Tests that infinite loops based on user-supplied values in vertex shaders hang instead of progress.
+// Otherwise optimizer would be able to assume something about the domain of the user-supplied value.
+// Explicit value break variant.
+TEST_P(TimeoutDrawTest, DynamicInfiniteLoop2VS)
+{
+    constexpr char kVS[] = R"(precision highp float;
+attribute vec4 a_position;
+uniform int f;
+void main()
+{
+    for (;;) { if (f <= 1) break; }
+    gl_Position = a_position;
+})";
+    ANGLE_GL_PROGRAM(program, kVS, essl1_shaders::fs::Red());
+    glUseProgram(program);
+    GLint uniformLocation = glGetUniformLocation(program, "f");
+    glUniform1i(uniformLocation, 66);
+    EXPECT_GL_NO_ERROR();
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glFinish();
+    EXPECT_GL_ERROR(GL_CONTEXT_LOST);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);  // Should read through client buffer since context should be lost.
+}
+
+// Tests that infinite loops based on user-supplied values in fragment shaders hang instead of progress.
+// Otherwise optimizer would be able to assume something about the domain of the user-supplied value.
+// Explicit value break variant.
+TEST_P(TimeoutDrawTest, DynamicInfiniteLoop2FS)
+{
+    constexpr char kFS[] = R"(precision mediump float;
+uniform float f;
+void main()
+{
+    for (;;) { if (f < 0.1) break; }
+    gl_FragColor = vec4(1, 0, f, 1);
+})";
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
+    glUseProgram(program);
+    GLint uniformLocation = glGetUniformLocation(program, "f");
+    glUniform1f(uniformLocation, .5f);
+    EXPECT_GL_NO_ERROR();
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glFinish();
+    EXPECT_GL_ERROR(GL_CONTEXT_LOST);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);  // Should read through client buffer since context should be lost.
+}
+
+}
+
+ANGLE_INSTANTIATE_TEST(TimeoutDrawTest,
+                       WithRobustness(ES2_METAL().enable(Feature::InjectAsmStatementIntoLoopBodies)),
+                       WithRobustness(ES3_METAL().enable(Feature::InjectAsmStatementIntoLoopBodies)));
diff --git a/util/autogen/angle_features_autogen.cpp b/util/autogen/angle_features_autogen.cpp
index 4ed0cc0802aa86b47a3cb30b2574745fe04dc7d3..e1e8eb111a5e5aa45dfe95363233079328c1723e 100644
--- a/util/autogen/angle_features_autogen.cpp
+++ b/util/autogen/angle_features_autogen.cpp
@@ -207,6 +207,7 @@ constexpr PackedEnumMap<Feature, const char *> kFeatureNames = {{
     {Feature::HasShaderStencilOutput, "hasShaderStencilOutput"},
     {Feature::HasStencilAutoResolve, "hasStencilAutoResolve"},
     {Feature::HasTextureSwizzle, "hasTextureSwizzle"},
+    {Feature::HasVariableRasterizationRate, "hasVariableRasterizationRate"},
     {Feature::InitFragmentOutputVariables, "initFragmentOutputVariables"},
     {Feature::InitializeCurrentVertexAttributes, "initializeCurrentVertexAttributes"},
     {Feature::InjectAsmStatementIntoLoopBodies, "injectAsmStatementIntoLoopBodies"},
diff --git a/util/autogen/angle_features_autogen.h b/util/autogen/angle_features_autogen.h
index d7cb44d2bad4f004054e20e72d9afff86e70f27e..50550ba0e0763dce9b09f0b87d0f69b89b8c258a 100644
--- a/util/autogen/angle_features_autogen.h
+++ b/util/autogen/angle_features_autogen.h
@@ -207,6 +207,7 @@ enum class Feature
     HasShaderStencilOutput,
     HasStencilAutoResolve,
     HasTextureSwizzle,
+    HasVariableRasterizationRate,
     InitFragmentOutputVariables,
     InitializeCurrentVertexAttributes,
     InjectAsmStatementIntoLoopBodies,
diff --git a/util/capture/frame_capture_replay_autogen.cpp b/util/capture/frame_capture_replay_autogen.cpp
index c0dcff29487ea801fef76afcff3d09040253e237..bedf8d51e8c790b8acd5072c977f900c309a2287 100644
--- a/util/capture/frame_capture_replay_autogen.cpp
+++ b/util/capture/frame_capture_replay_autogen.cpp
@@ -105,6 +105,10 @@ void ReplayTraceFunctionCall(const CallCapture &call, const TraceFunctionMap &cu
                                captures[3].value.GLbooleanVal, captures[4].value.GLintVal,
                                captures[5].value.GLenumVal, captures[6].value.GLenumVal);
             break;
+        case angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE:
+            glBindMetalRasterizationRateMapANGLE(
+                captures[0].value.GLMTLRasterizationRateMapANGLEVal);
+            break;
         case angle::EntryPoint::GLBindProgramPipeline:
             glBindProgramPipeline(gProgramPipelineMap[captures[0].value.GLuintVal]);
             break;
@@ -1061,6 +1065,11 @@ void ReplayTraceFunctionCall(const CallCapture &call, const TraceFunctionMap &cu
                                          captures[2].value.GLenumVal,
                                          gRenderbufferMap[captures[3].value.GLuintVal]);
             break;
+        case angle::EntryPoint::GLFramebufferResolveRenderbufferWEBKIT:
+            glFramebufferResolveRenderbufferWEBKIT(
+                captures[0].value.GLenumVal, captures[1].value.GLenumVal,
+                captures[2].value.GLenumVal, gRenderbufferMap[captures[3].value.GLuintVal]);
+            break;
         case angle::EntryPoint::GLFramebufferTexture:
             glFramebufferTexture(captures[0].value.GLenumVal, captures[1].value.GLenumVal,
                                  gTextureMap[captures[2].value.GLuintVal],
diff --git a/util/capture/trace_gles_loader_autogen.cpp b/util/capture/trace_gles_loader_autogen.cpp
index 3d7ab38014a8a36eb2e64afe5dc3b31151db3476..1948da0e40705d0160b2c861e564305777a8ce59 100644
--- a/util/capture/trace_gles_loader_autogen.cpp
+++ b/util/capture/trace_gles_loader_autogen.cpp
@@ -632,6 +632,8 @@ ANGLE_TRACE_LOADER_EXPORT PFNGLSAMPLEMASKIANGLEPROC t_glSampleMaskiANGLE;
 ANGLE_TRACE_LOADER_EXPORT PFNGLTEXSTORAGE2DMULTISAMPLEANGLEPROC t_glTexStorage2DMultisampleANGLE;
 ANGLE_TRACE_LOADER_EXPORT PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC
     t_glGetTranslatedShaderSourceANGLE;
+ANGLE_TRACE_LOADER_EXPORT PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC
+    t_glBindMetalRasterizationRateMapANGLE;
 ANGLE_TRACE_LOADER_EXPORT PFNGLACQUIRETEXTURESANGLEPROC t_glAcquireTexturesANGLE;
 ANGLE_TRACE_LOADER_EXPORT PFNGLRELEASETEXTURESANGLEPROC t_glReleaseTexturesANGLE;
 ANGLE_TRACE_LOADER_EXPORT PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC t_glBindUniformLocationCHROMIUM;
@@ -884,6 +886,8 @@ ANGLE_TRACE_LOADER_EXPORT PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC
 ANGLE_TRACE_LOADER_EXPORT PFNGLENDTILINGQCOMPROC t_glEndTilingQCOM;
 ANGLE_TRACE_LOADER_EXPORT PFNGLSTARTTILINGQCOMPROC t_glStartTilingQCOM;
 ANGLE_TRACE_LOADER_EXPORT PFNGLBLENDEQUATIONOESPROC t_glBlendEquationOES;
+ANGLE_TRACE_LOADER_EXPORT PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    t_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_TRACE_LOADER_EXPORT PFNGLDRAWTEXFOESPROC t_glDrawTexfOES;
 ANGLE_TRACE_LOADER_EXPORT PFNGLDRAWTEXFVOESPROC t_glDrawTexfvOES;
 ANGLE_TRACE_LOADER_EXPORT PFNGLDRAWTEXIOESPROC t_glDrawTexiOES;
@@ -1854,6 +1858,9 @@ void LoadTraceGLES(LoadProc loadProc)
         loadProc("glTexStorage2DMultisampleANGLE"));
     t_glGetTranslatedShaderSourceANGLE = reinterpret_cast<PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC>(
         loadProc("glGetTranslatedShaderSourceANGLE"));
+    t_glBindMetalRasterizationRateMapANGLE =
+        reinterpret_cast<PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC>(
+            loadProc("glBindMetalRasterizationRateMapANGLE"));
     t_glAcquireTexturesANGLE =
         reinterpret_cast<PFNGLACQUIRETEXTURESANGLEPROC>(loadProc("glAcquireTexturesANGLE"));
     t_glReleaseTexturesANGLE =
@@ -2289,6 +2296,9 @@ void LoadTraceGLES(LoadProc loadProc)
     t_glStartTilingQCOM = reinterpret_cast<PFNGLSTARTTILINGQCOMPROC>(loadProc("glStartTilingQCOM"));
     t_glBlendEquationOES =
         reinterpret_cast<PFNGLBLENDEQUATIONOESPROC>(loadProc("glBlendEquationOES"));
+    t_glFramebufferResolveRenderbufferWEBKIT =
+        reinterpret_cast<PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC>(
+            loadProc("glFramebufferResolveRenderbufferWEBKIT"));
     t_glDrawTexfOES  = reinterpret_cast<PFNGLDRAWTEXFOESPROC>(loadProc("glDrawTexfOES"));
     t_glDrawTexfvOES = reinterpret_cast<PFNGLDRAWTEXFVOESPROC>(loadProc("glDrawTexfvOES"));
     t_glDrawTexiOES  = reinterpret_cast<PFNGLDRAWTEXIOESPROC>(loadProc("glDrawTexiOES"));
diff --git a/util/capture/trace_gles_loader_autogen.h b/util/capture/trace_gles_loader_autogen.h
index 92835c6a5d5160b144172cecd76eb1661c417174..1e5acc666d7af1f0a29fca7752914ac9710632c8 100644
--- a/util/capture/trace_gles_loader_autogen.h
+++ b/util/capture/trace_gles_loader_autogen.h
@@ -597,6 +597,7 @@
 #define glSampleMaskiANGLE t_glSampleMaskiANGLE
 #define glTexStorage2DMultisampleANGLE t_glTexStorage2DMultisampleANGLE
 #define glGetTranslatedShaderSourceANGLE t_glGetTranslatedShaderSourceANGLE
+#define glBindMetalRasterizationRateMapANGLE t_glBindMetalRasterizationRateMapANGLE
 #define glAcquireTexturesANGLE t_glAcquireTexturesANGLE
 #define glReleaseTexturesANGLE t_glReleaseTexturesANGLE
 #define glBindUniformLocationCHROMIUM t_glBindUniformLocationCHROMIUM
@@ -832,6 +833,7 @@
 #define glEndTilingQCOM t_glEndTilingQCOM
 #define glStartTilingQCOM t_glStartTilingQCOM
 #define glBlendEquationOES t_glBlendEquationOES
+#define glFramebufferResolveRenderbufferWEBKIT t_glFramebufferResolveRenderbufferWEBKIT
 #define glDrawTexfOES t_glDrawTexfOES
 #define glDrawTexfvOES t_glDrawTexfvOES
 #define glDrawTexiOES t_glDrawTexiOES
@@ -1522,6 +1524,8 @@ ANGLE_TRACE_LOADER_EXPORT extern PFNGLTEXSTORAGE2DMULTISAMPLEANGLEPROC
     t_glTexStorage2DMultisampleANGLE;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC
     t_glGetTranslatedShaderSourceANGLE;
+ANGLE_TRACE_LOADER_EXPORT extern PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC
+    t_glBindMetalRasterizationRateMapANGLE;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLACQUIRETEXTURESANGLEPROC t_glAcquireTexturesANGLE;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLRELEASETEXTURESANGLEPROC t_glReleaseTexturesANGLE;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC
@@ -1792,6 +1796,8 @@ ANGLE_TRACE_LOADER_EXPORT extern PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLENDTILINGQCOMPROC t_glEndTilingQCOM;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLSTARTTILINGQCOMPROC t_glStartTilingQCOM;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLBLENDEQUATIONOESPROC t_glBlendEquationOES;
+ANGLE_TRACE_LOADER_EXPORT extern PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    t_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLDRAWTEXFOESPROC t_glDrawTexfOES;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLDRAWTEXFVOESPROC t_glDrawTexfvOES;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLDRAWTEXIOESPROC t_glDrawTexiOES;
diff --git a/util/capture/trace_interpreter_autogen.cpp b/util/capture/trace_interpreter_autogen.cpp
index 826307ea7e569d7fd27be893b8b95b6b908cf5dd..8527ce2b5a0de225ae51f06a9a42a62cf6893faf 100644
--- a/util/capture/trace_interpreter_autogen.cpp
+++ b/util/capture/trace_interpreter_autogen.cpp
@@ -917,6 +917,13 @@ CallCapture ParseCallCapture(const Token &nameToken,
             paramTokens, strings);
         return CallCapture(EntryPoint::GLBindImageTexture, std::move(params));
     }
+    if (strcmp(nameToken, "glBindMetalRasterizationRateMapANGLE") == 0)
+    {
+        ParamBuffer params =
+            ParseParameters<std::remove_pointer<PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC>::type>(
+                paramTokens, strings);
+        return CallCapture(EntryPoint::GLBindMetalRasterizationRateMapANGLE, std::move(params));
+    }
     if (strcmp(nameToken, "glBindProgramPipeline") == 0)
     {
         ParamBuffer params =
@@ -2324,6 +2331,13 @@ CallCapture ParseCallCapture(const Token &nameToken,
                 paramTokens, strings);
         return CallCapture(EntryPoint::GLFramebufferRenderbufferOES, std::move(params));
     }
+    if (strcmp(nameToken, "glFramebufferResolveRenderbufferWEBKIT") == 0)
+    {
+        ParamBuffer params = ParseParameters<
+            std::remove_pointer<PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC>::type>(paramTokens,
+                                                                                      strings);
+        return CallCapture(EntryPoint::GLFramebufferResolveRenderbufferWEBKIT, std::move(params));
+    }
     if (strcmp(nameToken, "glFramebufferTexture") == 0)
     {
         ParamBuffer params =
diff --git a/util/gles_loader_autogen.cpp b/util/gles_loader_autogen.cpp
index 50bf87098fec088166b626a353cc0a6962814923..37fd945e4b3167e2c0f3f0ddebb12e7df6d8c427 100644
--- a/util/gles_loader_autogen.cpp
+++ b/util/gles_loader_autogen.cpp
@@ -609,6 +609,8 @@ ANGLE_UTIL_EXPORT PFNGLGETMULTISAMPLEFVANGLEPROC l_glGetMultisamplefvANGLE;
 ANGLE_UTIL_EXPORT PFNGLSAMPLEMASKIANGLEPROC l_glSampleMaskiANGLE;
 ANGLE_UTIL_EXPORT PFNGLTEXSTORAGE2DMULTISAMPLEANGLEPROC l_glTexStorage2DMultisampleANGLE;
 ANGLE_UTIL_EXPORT PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC l_glGetTranslatedShaderSourceANGLE;
+ANGLE_UTIL_EXPORT PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC
+    l_glBindMetalRasterizationRateMapANGLE;
 ANGLE_UTIL_EXPORT PFNGLACQUIRETEXTURESANGLEPROC l_glAcquireTexturesANGLE;
 ANGLE_UTIL_EXPORT PFNGLRELEASETEXTURESANGLEPROC l_glReleaseTexturesANGLE;
 ANGLE_UTIL_EXPORT PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC l_glBindUniformLocationCHROMIUM;
@@ -852,6 +854,8 @@ ANGLE_UTIL_EXPORT PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC l_glTextureFoveationPa
 ANGLE_UTIL_EXPORT PFNGLENDTILINGQCOMPROC l_glEndTilingQCOM;
 ANGLE_UTIL_EXPORT PFNGLSTARTTILINGQCOMPROC l_glStartTilingQCOM;
 ANGLE_UTIL_EXPORT PFNGLBLENDEQUATIONOESPROC l_glBlendEquationOES;
+ANGLE_UTIL_EXPORT PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    l_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_UTIL_EXPORT PFNGLDRAWTEXFOESPROC l_glDrawTexfOES;
 ANGLE_UTIL_EXPORT PFNGLDRAWTEXFVOESPROC l_glDrawTexfvOES;
 ANGLE_UTIL_EXPORT PFNGLDRAWTEXIOESPROC l_glDrawTexiOES;
@@ -1821,6 +1825,9 @@ void LoadUtilGLES(LoadProc loadProc)
         loadProc("glTexStorage2DMultisampleANGLE"));
     l_glGetTranslatedShaderSourceANGLE = reinterpret_cast<PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC>(
         loadProc("glGetTranslatedShaderSourceANGLE"));
+    l_glBindMetalRasterizationRateMapANGLE =
+        reinterpret_cast<PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC>(
+            loadProc("glBindMetalRasterizationRateMapANGLE"));
     l_glAcquireTexturesANGLE =
         reinterpret_cast<PFNGLACQUIRETEXTURESANGLEPROC>(loadProc("glAcquireTexturesANGLE"));
     l_glReleaseTexturesANGLE =
@@ -2256,6 +2263,9 @@ void LoadUtilGLES(LoadProc loadProc)
     l_glStartTilingQCOM = reinterpret_cast<PFNGLSTARTTILINGQCOMPROC>(loadProc("glStartTilingQCOM"));
     l_glBlendEquationOES =
         reinterpret_cast<PFNGLBLENDEQUATIONOESPROC>(loadProc("glBlendEquationOES"));
+    l_glFramebufferResolveRenderbufferWEBKIT =
+        reinterpret_cast<PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC>(
+            loadProc("glFramebufferResolveRenderbufferWEBKIT"));
     l_glDrawTexfOES  = reinterpret_cast<PFNGLDRAWTEXFOESPROC>(loadProc("glDrawTexfOES"));
     l_glDrawTexfvOES = reinterpret_cast<PFNGLDRAWTEXFVOESPROC>(loadProc("glDrawTexfvOES"));
     l_glDrawTexiOES  = reinterpret_cast<PFNGLDRAWTEXIOESPROC>(loadProc("glDrawTexiOES"));
diff --git a/util/gles_loader_autogen.h b/util/gles_loader_autogen.h
index 24b2133c9f1c0070e45797852e7270d36c4d75a6..06eb80ce7f693f19256a328c44ffb165328e667c 100644
--- a/util/gles_loader_autogen.h
+++ b/util/gles_loader_autogen.h
@@ -597,6 +597,7 @@
 #define glSampleMaskiANGLE l_glSampleMaskiANGLE
 #define glTexStorage2DMultisampleANGLE l_glTexStorage2DMultisampleANGLE
 #define glGetTranslatedShaderSourceANGLE l_glGetTranslatedShaderSourceANGLE
+#define glBindMetalRasterizationRateMapANGLE l_glBindMetalRasterizationRateMapANGLE
 #define glAcquireTexturesANGLE l_glAcquireTexturesANGLE
 #define glReleaseTexturesANGLE l_glReleaseTexturesANGLE
 #define glBindUniformLocationCHROMIUM l_glBindUniformLocationCHROMIUM
@@ -832,6 +833,7 @@
 #define glEndTilingQCOM l_glEndTilingQCOM
 #define glStartTilingQCOM l_glStartTilingQCOM
 #define glBlendEquationOES l_glBlendEquationOES
+#define glFramebufferResolveRenderbufferWEBKIT l_glFramebufferResolveRenderbufferWEBKIT
 #define glDrawTexfOES l_glDrawTexfOES
 #define glDrawTexfvOES l_glDrawTexfvOES
 #define glDrawTexiOES l_glDrawTexiOES
@@ -1487,6 +1489,8 @@ ANGLE_UTIL_EXPORT extern PFNGLGETMULTISAMPLEFVANGLEPROC l_glGetMultisamplefvANGL
 ANGLE_UTIL_EXPORT extern PFNGLSAMPLEMASKIANGLEPROC l_glSampleMaskiANGLE;
 ANGLE_UTIL_EXPORT extern PFNGLTEXSTORAGE2DMULTISAMPLEANGLEPROC l_glTexStorage2DMultisampleANGLE;
 ANGLE_UTIL_EXPORT extern PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC l_glGetTranslatedShaderSourceANGLE;
+ANGLE_UTIL_EXPORT extern PFNGLBINDMETALRASTERIZATIONRATEMAPANGLEPROC
+    l_glBindMetalRasterizationRateMapANGLE;
 ANGLE_UTIL_EXPORT extern PFNGLACQUIRETEXTURESANGLEPROC l_glAcquireTexturesANGLE;
 ANGLE_UTIL_EXPORT extern PFNGLRELEASETEXTURESANGLEPROC l_glReleaseTexturesANGLE;
 ANGLE_UTIL_EXPORT extern PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC l_glBindUniformLocationCHROMIUM;
@@ -1732,6 +1736,8 @@ ANGLE_UTIL_EXPORT extern PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC l_glTextureFove
 ANGLE_UTIL_EXPORT extern PFNGLENDTILINGQCOMPROC l_glEndTilingQCOM;
 ANGLE_UTIL_EXPORT extern PFNGLSTARTTILINGQCOMPROC l_glStartTilingQCOM;
 ANGLE_UTIL_EXPORT extern PFNGLBLENDEQUATIONOESPROC l_glBlendEquationOES;
+ANGLE_UTIL_EXPORT extern PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    l_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_UTIL_EXPORT extern PFNGLDRAWTEXFOESPROC l_glDrawTexfOES;
 ANGLE_UTIL_EXPORT extern PFNGLDRAWTEXFVOESPROC l_glDrawTexfvOES;
 ANGLE_UTIL_EXPORT extern PFNGLDRAWTEXIOESPROC l_glDrawTexiOES;
