diff --git a/doc/ExtensionSupport.md b/doc/ExtensionSupport.md
index debc7ae6b2505f78cdd3345b140f001eab0bcdeb..cd1fd742775a2e93810407ba4313a36d4a630c92 100644
--- a/doc/ExtensionSupport.md
+++ b/doc/ExtensionSupport.md
@@ -234,6 +234,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; |
@@ -270,6 +271,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 46b52b05a9f490f1ea686d1915aef7508878d77a..7ac187ffe7048404f7f3fd16bb93cfe74f4b3eed 100644
--- a/include/GLES2/gl2ext_angle.h
+++ b/include/GLES2/gl2ext_angle.h
@@ -743,5 +743,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 3efcc4506a4b77ca33015eabf90434970dfa5c79..0131400374a4393cb055900e57bae4411b3478e2 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 386e3a83132a2e7f2a5e7e9df5f51e51fe10b614..d4f403aa893f883b5c4adc7f041103cc2491443b 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/code_generation_hashes/Extension_files.json b/scripts/code_generation_hashes/Extension_files.json
index 2f81cef84a38699e55858241f7d83ea9e0519cf3..3c51e5d55647af40ae29996f870fb1fb8a89b4b4 100644
--- a/scripts/code_generation_hashes/Extension_files.json
+++ b/scripts/code_generation_hashes/Extension_files.json
@@ -1,6 +1,6 @@
 {
   "doc/ExtensionSupport.md":
-    "2a3cd7639ef7544e90ee7bcc76494486",
+    "5ea311c0ec376ceacb980bd2e30d2f5b",
   "scripts/egl_angle_ext.xml":
     "8389749098fae1d5c832a06b5be51dc1",
   "scripts/extension_data/intel_630_linux.json":
@@ -20,15 +20,15 @@
   "scripts/extension_data/swiftshader_win10_gles1.json":
     "bea8e2106d62e1ea0e8938f150865a37",
   "scripts/gl_angle_ext.xml":
-    "da4ecccdd77635f1b0e9d4664f856706",
+    "2cba159af592da788a15449eae941cb4",
   "scripts/registry_xml.py":
-    "15b29345de1215203addd0a2a7af530e",
+    "3d16f678c0485ddb6ba7c69761a4fc8f",
   "src/libANGLE/gen_extensions.py":
     "dc4727460d1ece9f98a2ae47bf15ddb3",
   "src/libANGLE/gles_extensions_autogen.cpp":
-    "9d344a1265f383e809181a884f600c36",
+    "0d8b077da31d4df05497354bcbbfb99c",
   "src/libANGLE/gles_extensions_autogen.h":
-    "915ed943382db70d8c065e8ed1c7295f",
+    "8285ca6a15b45782d7f4df5c9e421a2f",
   "third_party/EGL-Registry/src/api/egl.xml":
     "2056d54ea07156f1988ca1366bdee21a",
   "third_party/OpenCL-Docs/src/xml/cl.xml":
diff --git a/scripts/code_generation_hashes/GL_EGL_WGL_loader.json b/scripts/code_generation_hashes/GL_EGL_WGL_loader.json
index 36c024e620d5812fedd61f1be9828919faa676ec..83717e0d1e4c7b81b9df8372eefc8daf9c23c576 100644
--- a/scripts/code_generation_hashes/GL_EGL_WGL_loader.json
+++ b/scripts/code_generation_hashes/GL_EGL_WGL_loader.json
@@ -4,9 +4,9 @@
   "scripts/generate_loader.py":
     "93c78a8d11323fa311fed5118fbcf083",
   "scripts/gl_angle_ext.xml":
-    "da4ecccdd77635f1b0e9d4664f856706",
+    "2cba159af592da788a15449eae941cb4",
   "scripts/registry_xml.py":
-    "15b29345de1215203addd0a2a7af530e",
+    "3d16f678c0485ddb6ba7c69761a4fc8f",
   "src/libEGL/egl_loader_autogen.cpp":
     "2aca2a57c51fc2b1c7e1da0a7ccf6107",
   "src/libEGL/egl_loader_autogen.h":
@@ -26,17 +26,17 @@
   "util/capture/trace_egl_loader_autogen.h":
     "9adc81af729078b16b36647f02401342",
   "util/capture/trace_gles_loader_autogen.cpp":
-    "16cb9f0cdf7ee7dbf4d12085f4a2ae1e",
+    "7dbfa63a3fa955eabc4fcbb25b7f10f2",
   "util/capture/trace_gles_loader_autogen.h":
-    "1a94e35a444206870daafa18412debe3",
+    "757429264980274769e2247af262df9a",
   "util/egl_loader_autogen.cpp":
     "ae6abfc6c2c0a997ad59258dfc0339ce",
   "util/egl_loader_autogen.h":
     "ea5f73048616a6fc80eaa7e93ef5f0ce",
   "util/gles_loader_autogen.cpp":
-    "adef140eeb58bc8d6bde25700d3c23ca",
+    "ea86a10d2d24087237bb848d190a925d",
   "util/gles_loader_autogen.h":
-    "29cd08fade2c117cd6bfb31036054e71",
+    "3d694e6a0b96deffd537065f2590b17b",
   "util/windows/wgl_loader_autogen.cpp":
     "373b062587eab8a163121255f54597dc",
   "util/windows/wgl_loader_autogen.h":
diff --git a/scripts/code_generation_hashes/GLenum_value_to_string_map.json b/scripts/code_generation_hashes/GLenum_value_to_string_map.json
index 4fe774d175449221b88ec2e63f1628d84cec8e52..f5f621c954ea8cd7f2990ed56b532a2f1872d7a3 100644
--- a/scripts/code_generation_hashes/GLenum_value_to_string_map.json
+++ b/scripts/code_generation_hashes/GLenum_value_to_string_map.json
@@ -2,11 +2,11 @@
   "scripts/gen_gl_enum_utils.py":
     "3ec60ab12923f4825b57fe183f2152b2",
   "scripts/gl_angle_ext.xml":
-    "da4ecccdd77635f1b0e9d4664f856706",
+    "2cba159af592da788a15449eae941cb4",
   "scripts/registry_xml.py":
-    "15b29345de1215203addd0a2a7af530e",
+    "3d16f678c0485ddb6ba7c69761a4fc8f",
   "src/common/gl_enum_utils_autogen.cpp":
-    "8de8ea5f7586a4e6d5764fa8c18a06f1",
+    "74f2cc3b092053f5832fd7b2c1fd015b",
   "src/common/gl_enum_utils_autogen.h":
     "e3dc3844be83f15653389012e4c21b6b",
   "third_party/OpenGL-Registry/src/xml/gl.xml":
diff --git a/scripts/code_generation_hashes/interpreter_utils.json b/scripts/code_generation_hashes/interpreter_utils.json
index cb66f94c7cbbc1c5ffd06147bb38110acb59a62e..df0571a38ec42bef78a458d32d58a4c999ed63ee 100644
--- a/scripts/code_generation_hashes/interpreter_utils.json
+++ b/scripts/code_generation_hashes/interpreter_utils.json
@@ -4,9 +4,9 @@
   "scripts/gen_interpreter_utils.py":
     "c525953cf6fb2294d489e9c22cbabdb8",
   "scripts/gl_angle_ext.xml":
-    "da4ecccdd77635f1b0e9d4664f856706",
+    "2cba159af592da788a15449eae941cb4",
   "scripts/registry_xml.py":
-    "15b29345de1215203addd0a2a7af530e",
+    "3d16f678c0485ddb6ba7c69761a4fc8f",
   "third_party/EGL-Registry/src/api/egl.xml":
     "2056d54ea07156f1988ca1366bdee21a",
   "third_party/OpenCL-Docs/src/xml/cl.xml":
@@ -20,5 +20,5 @@
   "util/capture/trace_fixture.h":
     "4a0c7fde0a41217d377a49c2499b2e2a",
   "util/capture/trace_interpreter_autogen.cpp":
-    "3fcaa813c0bdffa9b79a904d077a793c"
+    "818e43252740380f90cef73be060bbdb"
 }
diff --git a/scripts/code_generation_hashes/proc_table.json b/scripts/code_generation_hashes/proc_table.json
index a20612d26fdabf79d1da66592474477145d793d1..0cbcb631416d7c704e29e060bc4ddfc0aace0df6 100644
--- a/scripts/code_generation_hashes/proc_table.json
+++ b/scripts/code_generation_hashes/proc_table.json
@@ -4,11 +4,11 @@
   "scripts/gen_proc_table.py":
     "23ebf460dda78d2c21625e0d41d3cb97",
   "scripts/gl_angle_ext.xml":
-    "da4ecccdd77635f1b0e9d4664f856706",
+    "2cba159af592da788a15449eae941cb4",
   "scripts/registry_xml.py":
-    "15b29345de1215203addd0a2a7af530e",
+    "3d16f678c0485ddb6ba7c69761a4fc8f",
   "src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp":
-    "d394fb57688dc5eb89c19aeae23d8bf3",
+    "e9b8cdb847756179dd2e6f5a1a534f2f",
   "src/libGLESv2/proc_table_cl_autogen.cpp":
     "ed003b0f041aaaa35b67d3fe07e61f91",
   "src/libOpenCL/libOpenCL_autogen.map":
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 0b0de8b841f45f4842651a25a82e0377156f53d5..9c232ca7aed35acb38c931d763e2cbcaf3ca693f 100755
--- a/scripts/generate_entry_points.py
+++ b/scripts/generate_entry_points.py
@@ -1000,6 +1000,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 0389244dd9232e3b4cd18bfd7d8a03721643eeb8..40eb16fc1619a3e63d6befd5b574c1216901bbf9 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 -->
@@ -1467,6 +1480,18 @@
                 <enum name="GL_SHADER_BINARY_ANGLE"/>
             </require>
         </extension>
+        <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">
             <require>
                 <command name="glBlobCacheCallbacksANGLE"/>
@@ -1656,4 +1681,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 8ff3cb9867d2da49339ff1abe331dcbc56550584..676734d2b93c15498d2b3f67f4cf6aaeedee889f 100644
--- a/scripts/registry_xml.py
+++ b/scripts/registry_xml.py
@@ -74,11 +74,13 @@ angle_requestable_extensions = [
     "GL_ANGLE_texture_external_update",
     "GL_ANGLE_texture_multisample",
     "GL_ANGLE_texture_rectangle",
+    "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_WEBKIT_explicit_resolve_target",
 ]
 
 gles_requestable_extensions = [
diff --git a/src/common/PoolAlloc.cpp b/src/common/PoolAlloc.cpp
index d5ca4ffe42cc013448d9c189bba861fdea800710..5415f6adcd1e81712e5da8085c4ce852b7c48579 100644
--- a/src/common/PoolAlloc.cpp
+++ b/src/common/PoolAlloc.cpp
@@ -12,6 +12,7 @@
 #include <assert.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <utility>
 
 #include "common/mathutil.h"
 #include "common/platform.h"
diff --git a/src/common/debug.cpp b/src/common/debug.cpp
index b9c3c57e460c648a7bcd497aefaec42e177e3e52..22698dd5a5104707e920f4361a942ef9d2af429f 100644
--- a/src/common/debug.cpp
+++ b/src/common/debug.cpp
@@ -36,6 +36,14 @@
 #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/debug.h b/src/common/debug.h
index 48a1979e5edb3165d659c000c789f9a705809923..0ae642f624bca7ae94c595327058fb76bb6c8e50 100644
--- a/src/common/debug.h
+++ b/src/common/debug.h
@@ -177,4 +177,15 @@ angle::SimpleMutex &GetDebugMutex();
 #    define ANGLE_REENABLE_UNUSED_FUNCTION_WARNING
 #endif
 
+// clang-format off
+#if defined(__clang__)
+#    define ANGLE_DISABLE_NONLITERAL_FORMAT_WARNING \
+        _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wformat-nonliteral\"")
+#    define ANGLE_REENABLE_NONLITERAL_FORMAT_WARNING _Pragma("clang diagnostic pop")
+#else
+#    define ANGLE_DISABLE_NONLITERAL_FORMAT_WARNING
+#    define ANGLE_REENABLE_NONLITERAL_FORMAT_WARNING
+#endif
+// clang-format on
+
 #endif  // COMMON_DEBUG_H_
diff --git a/src/common/entry_points_enum_autogen.cpp b/src/common/entry_points_enum_autogen.cpp
index f64714754ab0d2e4f898885587840cf9ae75fae3..e9faf4637e7e99532e08b70d61d07058701d8e5a 100644
--- a/src/common/entry_points_enum_autogen.cpp
+++ b/src/common/entry_points_enum_autogen.cpp
@@ -522,6 +522,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:
@@ -950,6 +952,8 @@ const char *GetEntryPointName(EntryPoint ep)
             return "glFramebufferRenderbuffer";
         case EntryPoint::GLFramebufferRenderbufferOES:
             return "glFramebufferRenderbufferOES";
+        case EntryPoint::GLFramebufferResolveRenderbufferWEBKIT:
+            return "glFramebufferResolveRenderbufferWEBKIT";
         case EntryPoint::GLFramebufferShadingRateEXT:
             return "glFramebufferShadingRateEXT";
         case EntryPoint::GLFramebufferTexture:
diff --git a/src/common/entry_points_enum_autogen.h b/src/common/entry_points_enum_autogen.h
index 504283e89b04408619337e33bf73f2ef3e94f2b8..5ea63a43c9abec5e41d4b5d59c6e44d12de3f22e 100644
--- a/src/common/entry_points_enum_autogen.h
+++ b/src/common/entry_points_enum_autogen.h
@@ -267,6 +267,7 @@ enum class EntryPoint
     GLBindFramebuffer,
     GLBindFramebufferOES,
     GLBindImageTexture,
+    GLBindMetalRasterizationRateMapANGLE,
     GLBindProgramPipeline,
     GLBindProgramPipelineEXT,
     GLBindRenderbuffer,
@@ -481,6 +482,7 @@ enum class EntryPoint
     GLFramebufferPixelLocalStorageRestoreANGLE,
     GLFramebufferRenderbuffer,
     GLFramebufferRenderbufferOES,
+    GLFramebufferResolveRenderbufferWEBKIT,
     GLFramebufferShadingRateEXT,
     GLFramebufferTexture,
     GLFramebufferTexture2D,
diff --git a/src/common/frame_capture_utils_autogen.cpp b/src/common/frame_capture_utils_autogen.cpp
index 13a3b3e472f11b67629c901034080b0c506e7310..6d2c39414ba8010cd2ca2c302bdd797d2ecac7cb 100644
--- a/src/common/frame_capture_utils_autogen.cpp
+++ b/src/common/frame_capture_utils_autogen.cpp
@@ -222,6 +222,10 @@ void WriteParamCaptureReplay(std::ostream &os, const CallCapture &call, const Pa
             WriteParamValueReplay<ParamType::TGLGETBLOBPROCANGLE>(
                 os, call, param.value.GLGETBLOBPROCANGLEVal);
             break;
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            WriteParamValueReplay<ParamType::TGLMTLRasterizationRateMapANGLE>(
+                os, call, param.value.GLMTLRasterizationRateMapANGLEVal);
+            break;
         case ParamType::TGLSETBLOBPROCANGLE:
             WriteParamValueReplay<ParamType::TGLSETBLOBPROCANGLE>(
                 os, call, param.value.GLSETBLOBPROCANGLEVal);
@@ -995,6 +999,8 @@ const char *ParamTypeToString(ParamType paramType)
             return "GLDEBUGPROCKHR";
         case ParamType::TGLGETBLOBPROCANGLE:
             return "GLGETBLOBPROCANGLE";
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            return "GLMTLRasterizationRateMapANGLE";
         case ParamType::TGLSETBLOBPROCANGLE:
             return "GLSETBLOBPROCANGLE";
         case ParamType::TGLbitfield:
diff --git a/src/common/frame_capture_utils_autogen.h b/src/common/frame_capture_utils_autogen.h
index aba4c7679065ec3e56a9ba1128edeffebd8408a0..7a55f33cb847a2ac4ff3c4ca345a2e898c8760df 100644
--- a/src/common/frame_capture_utils_autogen.h
+++ b/src/common/frame_capture_utils_autogen.h
@@ -100,6 +100,7 @@ enum class ParamType
     TGLDEBUGPROC,
     TGLDEBUGPROCKHR,
     TGLGETBLOBPROCANGLE,
+    TGLMTLRasterizationRateMapANGLE,
     TGLSETBLOBPROCANGLE,
     TGLbitfield,
     TGLboolean,
@@ -271,7 +272,7 @@ enum class ParamType
     TvoidPointerPointer,
 };
 
-constexpr uint32_t kParamTypeCount = 234;
+constexpr uint32_t kParamTypeCount = 235;
 
 union ParamValue
 {
@@ -334,6 +335,7 @@ union ParamValue
     GLDEBUGPROC GLDEBUGPROCVal;
     GLDEBUGPROCKHR GLDEBUGPROCKHRVal;
     GLGETBLOBPROCANGLE GLGETBLOBPROCANGLEVal;
+    GLMTLRasterizationRateMapANGLE GLMTLRasterizationRateMapANGLEVal;
     GLSETBLOBPROCANGLE GLSETBLOBPROCANGLEVal;
     GLbitfield GLbitfieldVal;
     GLboolean GLbooleanVal;
@@ -891,6 +893,14 @@ inline GLGETBLOBPROCANGLE GetParamVal<ParamType::TGLGETBLOBPROCANGLE, GLGETBLOBP
     return value.GLGETBLOBPROCANGLEVal;
 }
 
+template <>
+inline GLMTLRasterizationRateMapANGLE
+GetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE, GLMTLRasterizationRateMapANGLE>(
+    const ParamValue &value)
+{
+    return value.GLMTLRasterizationRateMapANGLEVal;
+}
+
 template <>
 inline GLSETBLOBPROCANGLE GetParamVal<ParamType::TGLSETBLOBPROCANGLE, GLSETBLOBPROCANGLE>(
     const ParamValue &value)
@@ -2211,6 +2221,8 @@ T AccessParamValue(ParamType paramType, const ParamValue &value)
             return GetParamVal<ParamType::TGLDEBUGPROCKHR, T>(value);
         case ParamType::TGLGETBLOBPROCANGLE:
             return GetParamVal<ParamType::TGLGETBLOBPROCANGLE, T>(value);
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            return GetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE, T>(value);
         case ParamType::TGLSETBLOBPROCANGLE:
             return GetParamVal<ParamType::TGLSETBLOBPROCANGLE, T>(value);
         case ParamType::TGLbitfield:
@@ -2917,6 +2929,14 @@ inline void SetParamVal<ParamType::TGLGETBLOBPROCANGLE>(GLGETBLOBPROCANGLE value
     valueOut->GLGETBLOBPROCANGLEVal = valueIn;
 }
 
+template <>
+inline void SetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE>(
+    GLMTLRasterizationRateMapANGLE valueIn,
+    ParamValue *valueOut)
+{
+    valueOut->GLMTLRasterizationRateMapANGLEVal = valueIn;
+}
+
 template <>
 inline void SetParamVal<ParamType::TGLSETBLOBPROCANGLE>(GLSETBLOBPROCANGLE valueIn,
                                                         ParamValue *valueOut)
@@ -4276,6 +4296,9 @@ void InitParamValue(ParamType paramType, T valueIn, ParamValue *valueOut)
         case ParamType::TGLGETBLOBPROCANGLE:
             SetParamVal<ParamType::TGLGETBLOBPROCANGLE>(valueIn, valueOut);
             break;
+        case ParamType::TGLMTLRasterizationRateMapANGLE:
+            SetParamVal<ParamType::TGLMTLRasterizationRateMapANGLE>(valueIn, valueOut);
+            break;
         case ParamType::TGLSETBLOBPROCANGLE:
             SetParamVal<ParamType::TGLSETBLOBPROCANGLE>(valueIn, valueOut);
             break;
diff --git a/src/common/gl_enum_utils_autogen.cpp b/src/common/gl_enum_utils_autogen.cpp
index 731d3d360e4350a3a288ca93bf5efea7bc6322ce..a7891485c231c046b6e597ac44f0a40d482b4b25 100644
--- a/src/common/gl_enum_utils_autogen.cpp
+++ b/src/common/gl_enum_utils_autogen.cpp
@@ -2826,6 +2826,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:
@@ -22429,6 +22433,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},
@@ -25098,6 +25103,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 fc1aa2cccb169a6580cff7b2f144fe1768dbf6c5..de2ccdab42b3a77f3104e03c0715be22e8df2264 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)                                               \
-                    : (FATAL() << "\t! Assert failed in " << __FUNCTION__ << " (" << __FILE__ \
-                               << ":" << __LINE__ << "): " << #expression))
+                    : (!AreAssertionsEnabled()                                           \
+                           ? static_cast<void>(0)                                        \
+                           : (FATAL() << "\t! Assert failed in " << __FUNCTION__ << " (" \
+                                      << __FILE__ << ":" << __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 268ef72b2e82923b99b88e6e1b6d480c4d5e206a..32e264e9f3102354ccc65d8cd49453f7ac1b708e 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 1087a5d6b4c332e8fe3a062a251b49433915f6ae..3104d0eae383abe5b2c1aa15dfa7ff36896576ee 100644
--- a/src/compiler/fuzz/translator_fuzzer.cpp
+++ b/src/compiler/fuzz/translator_fuzzer.cpp
@@ -151,6 +151,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
     }
 
     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);
@@ -166,6 +167,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)
     {
@@ -197,6 +202,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 
         if (translator == nullptr)
         {
+            sh::Finalize();
             return 0;
         }
 
@@ -231,6 +237,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 
         if (!translator->Init(resources))
         {
+            sh::Finalize();
             return 0;
         }
 
@@ -243,5 +250,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/generate_parser_tools.py b/src/compiler/generate_parser_tools.py
index 71b370065e00ed5cb20738f977ac0243d2c9c4c6..3422ace60c834f8b3064a5251886e80965444e31 100644
--- a/src/compiler/generate_parser_tools.py
+++ b/src/compiler/generate_parser_tools.py
@@ -16,6 +16,10 @@ is_linux = platform.system() == 'Linux'
 is_mac = platform.system() == 'Darwin'
 is_windows = platform.system() == 'Windows'
 
+license_note = """/* Apple Note: For the avoidance of doubt, Apple elects to distribute this file under the terms of
+ * the BSD license. */
+
+"""
 
 def get_tool_path_platform(tool_name, platform):
     exe_path = os.path.join(sys.path[0], '..', '..', '..', 'tools', 'flex-bison', platform)
@@ -122,6 +126,15 @@ def run_bison(basename, generate_header):
 
     process = subprocess.Popen(bison_args, env=bison_env, cwd=sys.path[0])
     process.communicate()
+
+    for fn in [output_source] + ([output_header] if generate_header else []):
+        with open(fn, 'r') as output_file:
+            text = output_file.read()
+
+        with open(fn, 'w') as output_file:
+            output_file.write(license_note)
+            output_file.write(text)
+
     return process.returncode
 
 
diff --git a/src/compiler/preprocessor/preprocessor_tab_autogen.cpp b/src/compiler/preprocessor/preprocessor_tab_autogen.cpp
index b5c187ed9105cfa8237ecd89008f5b4907915efb..0b06c75af942fb2d1b1cef73550e53c4bc907652 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/InfoSink.h b/src/compiler/translator/InfoSink.h
index 8d3b1c0cf16e7ad912263ca4f3b37e6f0e808523..4cb10620fdab07e6896640fbf4675f78ad8072eb 100644
--- a/src/compiler/translator/InfoSink.h
+++ b/src/compiler/translator/InfoSink.h
@@ -21,7 +21,11 @@ class TSymbol;
 class TType;
 
 // Returns the fractional part of the given floating-point number.
-inline float fractionalPart(float f)
+#ifdef WK_WORKAROUND_RDAR_145268301_ASAN_STACK_USE_AFTER_SCOPE
+__attribute__((no_sanitize_address))
+#endif
+inline float
+fractionalPart(float f)
 {
     float intPart = 0.0f;
     return modff(f, &intPart);
diff --git a/src/compiler/translator/Types.h b/src/compiler/translator/Types.h
index c312bcbb09583a15577186b0ec592fdf3282e722..4f23c4d22a1dc3556800511cb6c6b1012294dfc0 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 type == EbtInt; }
 
     bool canBeConstructed() const;
 
diff --git a/src/compiler/translator/glslang_tab_autogen.cpp b/src/compiler/translator/glslang_tab_autogen.cpp
index 15e399b6556026a8ef7f632175607b56eefca55b..f10bb7abc48dfde26c6634daf7af9a5ad2ec1da1 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 104206577525a4db4d4bec0a70f2fbb34eed2f57..d0619656cc637e3f5af3fc5813823b013e908175 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,45 @@ 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 +270,13 @@ 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()
 {
@@ -279,19 +325,9 @@ static const char *GetOperatorString(TOperator op,
         case TOperator::EOpInitialize:
             return "=";
         case TOperator::EOpAddAssign:
-            return "+=";
+            return resultType.isSignedInt() ? "ANGLE_addAssignInt" : "+=";
         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.
+            return resultType.isSignedInt() ? "ANGLE_subAssignInt" : "-=";
         case TOperator::EOpBitwiseAndAssign:
             return "&=";
         case TOperator::EOpBitwiseXorAssign:
@@ -299,19 +335,9 @@ static const char *GetOperatorString(TOperator op,
         case TOperator::EOpBitwiseOrAssign:
             return "|=";
         case TOperator::EOpAdd:
-            return "+";
+            return resultType.isSignedInt() ? "ANGLE_addInt" : "+";
         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.
+            return resultType.isSignedInt() ? "ANGLE_subInt" : "-";
         case TOperator::EOpBitwiseAnd:
             return "&";
         case TOperator::EOpBitwiseXor:
@@ -356,23 +382,19 @@ static const char *GetOperatorString(TOperator op,
         case TOperator::EOpBitwiseNot:
             return "~";
         case TOperator::EOpPostIncrement:
-            return "++";
+            return resultType.isSignedInt() ? "ANGLE_postIncrementInt" : "++";
         case TOperator::EOpPostDecrement:
-            return "--";
+            return resultType.isSignedInt() ? "ANGLE_postDecrementInt" : "--";
         case TOperator::EOpPreIncrement:
-            return "++";
+            return resultType.isSignedInt() ? "ANGLE_preIncrementInt" : "++";
         case TOperator::EOpPreDecrement:
-            return "--";
-        case TOperator::EOpVectorTimesScalarAssign:
-            return "*=";
+            return resultType.isSignedInt() ? "ANGLE_preDecrementInt" : "--";
         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 +408,30 @@ 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::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()))
@@ -884,17 +930,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();
     }
@@ -1815,6 +1858,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();
@@ -2251,7 +2306,31 @@ 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("(", ")");
         }
 
@@ -2376,6 +2455,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())
@@ -2577,6 +2676,8 @@ bool GenMetalTraverser::visitForLoop(TIntermLoop *loopNode)
     TIntermTyped *condNode = loopNode->getCondition();
     TIntermTyped *exprNode = loopNode->getExpression();
 
+    ScopedForwardProgressStore scopedProgress(*this);
+
     mOut << "for (";
 
     if (initNode)
@@ -2619,6 +2720,7 @@ bool GenMetalTraverser::visitWhileLoop(TIntermLoop *loopNode)
     ASSERT(condNode);
     ASSERT(!initNode && !exprNode);
 
+    ScopedForwardProgressStore scopedProgress(*this);
     emitIndentation();
     mOut << "while (";
     condNode->traverse(this);
@@ -2638,6 +2740,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 3ae71d9d83a5bf3203f12b426db3d4d1f6fe5d12..9b8059f5850c3bd639e63b9e3f9152315acd58a4 100644
--- a/src/compiler/translator/msl/ProgramPrelude.cpp
+++ b/src/compiler/translator/msl/ProgramPrelude.cpp
@@ -113,6 +113,13 @@ class ProgramPrelude : public TIntermTraverser
     void degrees();
     void radians();
     void mod();
+    void div();
+    void imod();
+    void imul();
+    void ilshift();
+    void ulshift();
+    void rshift();
+    void ftoi();
     void mixBool();
     void postIncrementMatrix();
     void preIncrementMatrix();
@@ -277,6 +284,14 @@ class ProgramPrelude : public TIntermTraverser
     void interpolateAtCentroid();
     void interpolateAtSample();
     void interpolateAtOffset();
+    void postIncrementInt();
+    void preIncrementInt();
+    void postDecrementInt();
+    void preDecrementInt();
+    void addInt();
+    void addAssignInt();
+    void subInt();
+    void subAssignInt();
     void loopForwardProgress();
 
   private:
@@ -432,6 +447,116 @@ 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 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>
@@ -2769,6 +2894,164 @@ template <typename T>
 ANGLE_ALWAYS_INLINE T ANGLE_interpolateAtOffset(T value, float2) { return value; }
 )")
 
+PROGRAM_PRELUDE_DECLARE(preIncrementInt,
+                        R"(
+ANGLE_ALWAYS_INLINE int ANGLE_preIncrementInt(thread int &a)
+{
+    a = as_type<int>(as_type<metal::uint>(a) + 1u);
+    return a;
+}
+
+ANGLE_ALWAYS_INLINE metal::int2 ANGLE_preIncrementInt(thread metal::int2 &a)
+{
+    a = as_type<metal::int2>(as_type<metal::uint2>(a) + 1u);
+    return a;
+}
+
+ANGLE_ALWAYS_INLINE metal::int3 ANGLE_preIncrementInt(thread metal::int3 &a)
+{
+    a = as_type<metal::int3>(as_type<metal::uint3>(a) + 1u);
+    return a;
+}
+
+ANGLE_ALWAYS_INLINE metal::int4 ANGLE_preIncrementInt(thread metal::int4 &a)
+{
+    a = as_type<metal::int4>(as_type<metal::uint4>(a) + 1u);
+    return a;
+}
+)")
+
+PROGRAM_PRELUDE_DECLARE(postIncrementInt,
+                        R"(
+ANGLE_ALWAYS_INLINE int ANGLE_postIncrementInt(thread int &a)
+{
+    int r = a;
+    a = as_type<int>(as_type<metal::uint>(a) + 1u);
+    return r;
+}
+
+ANGLE_ALWAYS_INLINE metal::int2 ANGLE_postIncrementInt(thread metal::int2 &a)
+{
+    metal::int2 r = a;
+    a = as_type<metal::int2>(as_type<metal::uint2>(a) + 1u);
+    return r;
+}
+
+ANGLE_ALWAYS_INLINE metal::int3 ANGLE_postIncrementInt(thread metal::int3 &a)
+{
+    metal::int3 r = a;
+    a = as_type<metal::int3>(as_type<metal::uint3>(a) + 1u);
+    return r;
+}
+
+ANGLE_ALWAYS_INLINE metal::int4 ANGLE_postIncrementInt(thread metal::int4 &a)
+{
+    metal::int4 r = a;
+    a = as_type<metal::int4>(as_type<metal::uint4>(a) + 1u);
+    return r;
+}
+)")
+
+PROGRAM_PRELUDE_DECLARE(preDecrementInt,
+                        R"(
+ANGLE_ALWAYS_INLINE int ANGLE_preDecrementInt(thread int &a)
+{
+    a = as_type<int>(as_type<metal::uint>(a) - 1u);
+    return a;
+}
+
+ANGLE_ALWAYS_INLINE metal::int2 ANGLE_preDecrementInt(thread metal::int2 &a)
+{
+    a = as_type<metal::int2>(as_type<metal::uint2>(a) - 1u);
+    return a;
+}
+
+ANGLE_ALWAYS_INLINE metal::int3 ANGLE_preDecrementInt(thread metal::int3 &a)
+{
+    a = as_type<metal::int3>(as_type<metal::uint3>(a) - 1u);
+    return a;
+}
+
+ANGLE_ALWAYS_INLINE metal::int4 ANGLE_preDecrementInt(thread metal::int4 &a)
+{
+    a = as_type<metal::int4>(as_type<metal::uint4>(a) - 1u);
+    return a;
+}
+)")
+
+PROGRAM_PRELUDE_DECLARE(postDecrementInt,
+                        R"(
+ANGLE_ALWAYS_INLINE int ANGLE_postDecrementInt(thread int &a)
+{
+    int r = a;
+    a = as_type<int>(as_type<metal::uint>(a) - 1u);
+    return r;
+}
+
+ANGLE_ALWAYS_INLINE metal::int2 ANGLE_postDecrementInt(thread metal::int2 &a)
+{
+    metal::int2 r = a;
+    a = as_type<metal::int2>(as_type<metal::uint2>(a) - 1u);
+    return r;
+}
+
+ANGLE_ALWAYS_INLINE metal::int3 ANGLE_postDecrementInt(thread metal::int3 &a)
+{
+    metal::int3 r = a;
+    a = as_type<metal::int3>(as_type<metal::uint3>(a) - 1u);
+    return r;
+}
+
+ANGLE_ALWAYS_INLINE metal::int4 ANGLE_postDecrementInt(thread metal::int4 &a)
+{
+    metal::int4 r = a;
+    a = as_type<metal::int4>(as_type<metal::uint4>(a) - 1u);
+    return r;
+}
+)")
+
+// Avoid undefined behavior due to integer overflow.
+PROGRAM_PRELUDE_DECLARE(addInt,
+                        R"(
+template<typename X, typename Y, typename Z = metal::conditional_t<metal::is_scalar_v<Y>, X, Y>>
+ANGLE_ALWAYS_INLINE Z ANGLE_addInt(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(addAssignInt,
+                        R"(
+template<typename X, typename Y>
+ANGLE_ALWAYS_INLINE thread X &ANGLE_addAssignInt(thread X &x, Y y)
+{
+    x = as_type<X>(metal::make_unsigned_t<X>(x) + metal::make_unsigned_t<Y>(y));
+    return x;
+}
+)")
+
+// Avoid undefined behavior due to integer underflow.
+PROGRAM_PRELUDE_DECLARE(subInt,
+                        R"(
+template<typename X, typename Y, typename Z = metal::conditional_t<metal::is_scalar_v<Y>, X, Y>>
+ANGLE_ALWAYS_INLINE Z ANGLE_subInt(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(subAssignInt,
+                        R"(
+template<typename X, typename Y>
+ANGLE_ALWAYS_INLINE thread X &ANGLE_subAssignInt(thread X &x, Y y)
+{
+    x = as_type<X>(metal::make_unsigned_t<X>(x) - metal::make_unsigned_t<Y>(y));
+    return x;
+}
+)")
+
 PROGRAM_PRELUDE_DECLARE(loopForwardProgress,
                         R"(
 ANGLE_ALWAYS_INLINE void ANGLE_loopForwardProgress()
@@ -3288,6 +3571,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())
             {
@@ -3439,6 +3726,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 addScalarMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                addInt();
+            }
             break;
 
         case TOperator::EOpAddAssign:
@@ -3446,6 +3737,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 addMatrixScalarAssign();
             }
+            if (argType0->isSignedInt())
+            {
+                addAssignInt();
+            }
             break;
 
         case TOperator::EOpSub:
@@ -3457,6 +3752,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 subScalarMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                subInt();
+            }
             break;
 
         case TOperator::EOpSubAssign:
@@ -3464,9 +3763,28 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 subMatrixScalarAssign();
             }
+            if (argType0->isSignedInt())
+            {
+                subAssignInt();
+            }
+            break;
+
+        case TOperator::EOpMul:
+        case TOperator::EOpMulAssign:
+        case TOperator::EOpVectorTimesScalar:
+        case TOperator::EOpVectorTimesScalarAssign:
+            if (argType0->isSignedInt())
+            {
+                imul();
+            }
+            if (argType0->isSignedInt())
+            {
+                subAssignInt();
+            }
             break;
 
         case TOperator::EOpDiv:
+        case TOperator::EOpDivAssign:
             if (argType1->isMatrix())
             {
                 if (argType0->isMatrix())
@@ -3478,12 +3796,9 @@ void ProgramPrelude::visitOperator(TOperator op,
                     divScalarMatrix();
                 }
             }
-            break;
-
-        case TOperator::EOpDivAssign:
-            if (argType0->isMatrix() && argType1->isMatrix())
+            else
             {
-                componentWiseDivideAssign();
+                div();
             }
             break;
 
@@ -3524,6 +3839,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 preIncrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                preIncrementInt();
+            }
             break;
 
         case TOperator::EOpPostIncrement:
@@ -3531,6 +3850,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 postIncrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                postIncrementInt();
+            }
             break;
 
         case TOperator::EOpPreDecrement:
@@ -3538,6 +3861,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 preDecrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                preDecrementInt();
+            }
             break;
 
         case TOperator::EOpPostDecrement:
@@ -3545,6 +3872,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 postDecrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                postDecrementInt();
+            }
             break;
 
         case TOperator::EOpNegative:
@@ -3554,20 +3885,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:
@@ -3586,10 +3928,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:
@@ -3761,6 +4101,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/compiler/translator/tree_ops/RewriteStructSamplers.cpp b/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp
index 77262ed6e5b598fde791b1b4b9ce9e76afaf288f..4fe85335464b239c04da3f100c57962c10426e46 100644
--- a/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp
+++ b/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp
@@ -39,18 +39,20 @@ using StructureMap        = angle::HashMap<const TStructure *, StructureData>;
 using StructureUniformMap = angle::HashMap<const TVariable *, const TVariable *>;
 using ExtractedSamplerMap = angle::HashMap<std::string, const TVariable *>;
 
-TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
+bool RewriteModifiedStructFieldSelectionExpression(
     TCompiler *compiler,
     TIntermBinary *node,
     const StructureMap &structureMap,
     const StructureUniformMap &structureUniformMap,
-    const ExtractedSamplerMap &extractedSamplers);
+    const ExtractedSamplerMap &extractedSamplers,
+    TIntermTyped **rewritten);
 
-TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
+bool RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
                                                  TIntermBinary *node,
                                                  const StructureMap &structureMap,
                                                  const StructureUniformMap &structureUniformMap,
-                                                 const ExtractedSamplerMap &extractedSamplers)
+                                                 const ExtractedSamplerMap &extractedSamplers,
+                                                 TIntermTyped **rewritten)
 {
     // Only interested in EOpIndex* binary nodes.
     switch (node->getOp())
@@ -61,20 +63,22 @@ TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
         case EOpIndexDirectStruct:
             break;
         default:
-            return nullptr;
+			*rewritten = nullptr;
+            return true;
     }
 
     const TStructure *structure = node->getLeft()->getType().getStruct();
     if (structure == nullptr)
     {
-        return nullptr;
+        return true;
     }
 
     // If the result of the index is not a sampler and the struct is not replaced, there's nothing
     // to do.
     if (!node->getType().isSampler() && structureMap.find(structure) == structureMap.end())
     {
-        return nullptr;
+		*rewritten = nullptr;
+        return true;
     }
 
     // Otherwise, replace the whole expression such that:
@@ -84,8 +88,14 @@ TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
     //   the intermediate nodes would have the correct type (and therefore fields).
     ASSERT(structureMap.find(structure) != structureMap.end());
 
-    return RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap,
-                                                         structureUniformMap, extractedSamplers);
+    if (!RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap,
+                                                       structureUniformMap, extractedSamplers,
+                                                       rewritten))
+    {
+        return false;
+    }
+
+    return true;
 }
 
 // Given an expression, this traverser calculates a new expression where sampler-in-structs are
@@ -103,13 +113,17 @@ class RewriteExpressionTraverser final : public TIntermTraverser
           mCompiler(compiler),
           mStructureMap(structureMap),
           mStructureUniformMap(structureUniformMap),
-          mExtractedSamplers(extractedSamplers)
+          mExtractedSamplers(extractedSamplers),
+          mUnsupportedError(false)
     {}
 
     bool visitBinary(Visit visit, TIntermBinary *node) override
     {
-        TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
-            mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
+        TIntermTyped *rewritten = nullptr;
+        if (!RewriteExpressionVisitBinaryHelper(mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers, &rewritten))
+        {
+            mUnsupportedError = true;
+        }
 
         if (rewritten == nullptr)
         {
@@ -138,6 +152,10 @@ class RewriteExpressionTraverser final : public TIntermTraverser
     const StructureMap &mStructureMap;
     const StructureUniformMap &mStructureUniformMap;
     const ExtractedSamplerMap &mExtractedSamplers;
+
+    // FIXME: Used to communicate that an error occurred during the rewrite process that is currently not
+    // supported so that a failure can be returned to callers of sh::RewriteStructSamplers().
+    bool mUnsupportedError;
 };
 
 // Rewrite the index of an EOpIndexIndirect expression.  The root can never need replacing, because
@@ -179,12 +197,13 @@ void RewriteIndexExpression(TCompiler *compiler,
 //
 // If the expression is not a sampler, it only replaces the struct with the modified one, while
 // still processing the EOpIndexIndirect expressions (which may contain more structs to map).
-TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
+bool RewriteModifiedStructFieldSelectionExpression(
     TCompiler *compiler,
     TIntermBinary *node,
     const StructureMap &structureMap,
     const StructureUniformMap &structureUniformMap,
-    const ExtractedSamplerMap &extractedSamplers)
+    const ExtractedSamplerMap &extractedSamplers,
+    TIntermTyped **rewritten)
 {
     const bool isSampler = node->getType().isSampler();
 
@@ -221,18 +240,17 @@ TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
         iter = iter->getLeft()->getAsBinaryNode();
     }
 
-    TIntermTyped *rewritten = nullptr;
 
     if (isSampler)
     {
         ASSERT(extractedSamplers.find(samplerName) != extractedSamplers.end());
-        rewritten = new TIntermSymbol(extractedSamplers.at(samplerName));
+        *rewritten = new TIntermSymbol(extractedSamplers.at(samplerName));
     }
     else
     {
         const TVariable *baseUniformVar = &baseUniform->variable();
         ASSERT(structureUniformMap.find(baseUniformVar) != structureUniformMap.end());
-        rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar));
+        *rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar));
     }
 
     // Iterate again and build the expression from bottom up.
@@ -245,13 +263,27 @@ TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
             case EOpIndexDirectStruct:
                 if (!isSampler)
                 {
-                    rewritten =
-                        new TIntermBinary(EOpIndexDirectStruct, rewritten, indexNode->getRight());
+                    // FIXME: Fix accessing fields of structs containing other structs that only contain samplers.
+                    // Currently, given the following example definitions:
+                    //   (e.g., struct S1 { sampler2D sampler; }; struct S2 { int i; S1 s; }; uniform S2 uni;)
+                    // an out of bounds access can occur when trying to access uni.s.sampler because s has been stripped
+                    // out of the replacement structure definition for S2. For now, check that indexing into the field
+                    // list of a structure will not result in an out of bounds array access before attempting to
+                    // create the binary operator node.
+                    const TFieldList &fields = (*rewritten)->getType().getStruct()->fields();
+                    const size_t fieldIndex  = indexNode->getRight()->getAsConstantUnion()->getIConst(0);
+                    if (fieldIndex >= fields.size())
+                    {
+                        return false;
+                    }
+
+                    *rewritten =
+                        new TIntermBinary(EOpIndexDirectStruct, *rewritten, indexNode->getRight());
                 }
                 break;
 
             case EOpIndexDirect:
-                rewritten = new TIntermBinary(EOpIndexDirect, rewritten, indexNode->getRight());
+                *rewritten = new TIntermBinary(EOpIndexDirect, *rewritten, indexNode->getRight());
                 break;
 
             case EOpIndexIndirect:
@@ -262,7 +294,7 @@ TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
                 TIntermTyped *indexExpression = indexNode->getRight();
                 RewriteIndexExpression(compiler, indexExpression, structureMap, structureUniformMap,
                                        extractedSamplers);
-                rewritten = new TIntermBinary(EOpIndexIndirect, rewritten, indexExpression);
+                *rewritten = new TIntermBinary(EOpIndexIndirect, *rewritten, indexExpression);
                 break;
             }
 
@@ -272,7 +304,7 @@ TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
         }
     }
 
-    return rewritten;
+    return true;
 }
 
 class RewriteStructSamplersTraverser final : public TIntermTraverser
@@ -281,7 +313,8 @@ class RewriteStructSamplersTraverser final : public TIntermTraverser
     explicit RewriteStructSamplersTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
         : TIntermTraverser(true, false, false, symbolTable),
           mCompiler(compiler),
-          mRemovedUniformsCount(0)
+          mRemovedUniformsCount(0),
+          mUnsupportedError(false)
     {}
 
     int removedUniformsCount() const { return mRemovedUniformsCount; }
@@ -344,8 +377,11 @@ class RewriteStructSamplersTraverser final : public TIntermTraverser
     // Same implementation as in RewriteExpressionTraverser.  That traverser cannot replace root.
     bool visitBinary(Visit visit, TIntermBinary *node) override
     {
-        TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
-            mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
+        TIntermTyped *rewritten = nullptr;
+        if (!RewriteExpressionVisitBinaryHelper(mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers, &rewritten))
+        {
+            mUnsupportedError = true;
+        }
 
         if (rewritten == nullptr)
         {
@@ -369,6 +405,8 @@ class RewriteStructSamplersTraverser final : public TIntermTraverser
         }
     }
 
+    bool hasUnsupportedError() const { return mUnsupportedError; }
+
   private:
     bool isActiveUniform(const ImmutableString &rootStructureName)
     {
@@ -635,6 +673,10 @@ class RewriteStructSamplersTraverser final : public TIntermTraverser
 
     // Caches the names of all inactive uniforms.
     TSet<ImmutableString> *mActiveUniforms = nullptr;
+
+	// FIXME: Used to communicate that an error occurred during the rewrite process that is currently not
+    // supported so that a failure can be returned to callers of sh::RewriteStructSamplers().
+    bool mUnsupportedError;
 };
 }  // anonymous namespace
 
@@ -645,6 +687,8 @@ bool RewriteStructSamplers(TCompiler *compiler,
 {
     RewriteStructSamplersTraverser traverser(compiler, symbolTable);
     root->traverse(&traverser);
+    if (traverser.hasUnsupportedError())
+        return false;
     *removedUniformsCountOut = traverser.removedUniformsCount();
     return traverser.updateTree(compiler, root);
 }
diff --git a/src/compiler/translator/tree_ops/msl/RewriteUnaddressableReferences.cpp b/src/compiler/translator/tree_ops/msl/RewriteUnaddressableReferences.cpp
index 609f7a1f1e3bd96b2c0eb9188354bb207ec76f7c..31602d0cc4e5439a58c86db645667d2f78a11f0f 100644
--- a/src/compiler/translator/tree_ops/msl/RewriteUnaddressableReferences.cpp
+++ b/src/compiler/translator/tree_ops/msl/RewriteUnaddressableReferences.cpp
@@ -135,17 +135,10 @@ bool ReturnsReference(TOperator op)
         case TOperator::EOpBitwiseAndAssign:
         case TOperator::EOpBitwiseXorAssign:
         case TOperator::EOpBitwiseOrAssign:
-
-        case TOperator::EOpPostIncrement:
-        case TOperator::EOpPostDecrement:
-        case TOperator::EOpPreIncrement:
-        case TOperator::EOpPreDecrement:
-
         case TOperator::EOpIndexDirect:
         case TOperator::EOpIndexIndirect:
         case TOperator::EOpIndexDirectStruct:
         case TOperator::EOpIndexDirectInterfaceBlock:
-
             return true;
 
         default:
@@ -153,6 +146,28 @@ bool ReturnsReference(TOperator op)
     }
 }
 
+bool IsLValueUnaryOp(TOperator op)
+{
+    switch (op)
+    {
+        case EOpPostIncrement:
+        case EOpPostDecrement:
+        case EOpPreIncrement:
+        case EOpPreDecrement:
+            return true;
+        case EOpNegative:
+        case EOpPositive:
+        case EOpLogicalNot:
+        case EOpBitwiseNot:
+        case EOpArrayLength:
+            return false;
+        default:
+            break;
+    }
+    // At the time, we cannot use UNREACHABLE(); because builtin function calls might be unary ops.
+    return false;
+}
+
 TIntermTyped &DecomposeCompoundAssignment(TIntermBinary &node)
 {
     TOperator op = node.getOp();
@@ -355,6 +370,21 @@ class Rewriter2 : public TIntermRebuild
                                                 *new TIntermSequence{&newLeft, &newRight}),
                 VisitBits::Neither};
     }
+
+    PreResult visitUnaryPre(TIntermUnary &node) override
+    {
+        // Unary ++, -- operators for signed ints are implemented as functions. These take in a reference. Addressing is needed to
+        // support swizzles. This can be removed when a pass is added to replace the operators with builtin function calls.
+        bool childRequiresAddressing = IsLValueUnaryOp(node.getOp()) && node.getType().isSignedInt();
+        mRequiresAddressingStack.push_back(childRequiresAddressing);
+        return {node, VisitBits::Both};
+    }
+
+    PostResult visitUnaryPost(TIntermUnary &node) override
+    {
+        mRequiresAddressingStack.pop_back();
+        return {node};
+    }
 };
 
 }  // anonymous namespace
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 5535d542dd7122a4517d5156fc913b90c4d028a6..94b390f52cab0c1775db0aca7d5a7ab633fbe2c5 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -9787,6 +9787,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));
@@ -9861,6 +9888,16 @@ 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 ba2930f634a2de4fc71ac8bd5ea82efea635fad1..40fe5cb2892489f8dfe9a0f932d14d7d8bba69f8 100644
--- a/src/libANGLE/Context_gles_ext_autogen.h
+++ b/src/libANGLE/Context_gles_ext_autogen.h
@@ -651,6 +651,8 @@
     void invalidateTexture(TextureType targetPacked);                                              \
     /* GL_ANGLE_texture_multisample */                                                             \
     /* GL_ANGLE_texture_rectangle */                                                               \
+    /* GL_ANGLE_variable_rasterization_rate_metal */                                               \
+    void bindMetalRasterizationRateMap(GLuint framebuffer, GLMTLRasterizationRateMapANGLE map);    \
     /* GL_ANGLE_vulkan_image */                                                                    \
     void acquireTextures(GLuint numTextures, const TextureID *texturesPacked,                      \
                          const GLenum *layouts);                                                   \
@@ -677,6 +679,10 @@
                         GLboolean unpackUnmultiplyAlpha);                                          \
     /* GL_CHROMIUM_framebuffer_mixed_samples */                                                    \
     /* GL_CHROMIUM_lose_context */                                                                 \
-    void loseContext(GraphicsResetStatus currentPacked, GraphicsResetStatus otherPacked);
+    void loseContext(GraphicsResetStatus currentPacked, GraphicsResetStatus otherPacked);          \
+    /* 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/Display.cpp b/src/libANGLE/Display.cpp
index 8bc7ccc74d44f6810f6e3200a97f8528fad18b5c..d2f8d6bcad352a7061d8937e7e378f6b127f1246 100644
--- a/src/libANGLE/Display.cpp
+++ b/src/libANGLE/Display.cpp
@@ -2299,7 +2299,7 @@ Error Display::validateImageClientBuffer(const gl::Context *context,
     return mImplementation->validateImageClientBuffer(context, target, clientBuffer, attribs);
 }
 
-Error Display::valdiatePixmap(const Config *config,
+Error Display::validatePixmap(const Config *config,
                               EGLNativePixmapType pixmap,
                               const AttributeMap &attributes) const
 {
diff --git a/src/libANGLE/Display.h b/src/libANGLE/Display.h
index aea0acc7cb8b630ee9092ffde7bfae8e46503b4b..5c6ff476f4f7328a806d159dac0a531b8b5f6431 100644
--- a/src/libANGLE/Display.h
+++ b/src/libANGLE/Display.h
@@ -217,7 +217,7 @@ class Display final : public LabeledObject,
                                     EGLenum target,
                                     EGLClientBuffer clientBuffer,
                                     const egl::AttributeMap &attribs) const;
-    Error valdiatePixmap(const Config *config,
+    Error validatePixmap(const Config *config,
                          EGLNativePixmapType pixmap,
                          const AttributeMap &attributes) const;
 
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index 3d45a024dc6227b5c7c21ebdd82a702e518499a8..3b8efc4aa8550641319ff1ace6965595ae96291f 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -167,6 +167,48 @@ 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,
@@ -396,6 +438,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_NONE),
       mReadBufferState(GL_BACK),
@@ -419,6 +464,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),
@@ -589,6 +637,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();
@@ -819,6 +888,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)
@@ -853,6 +926,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);
@@ -1147,6 +1229,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());
@@ -1398,6 +1497,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())
     {
@@ -1451,6 +1569,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;
@@ -1506,6 +1638,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
@@ -1976,6 +2122,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,
@@ -2153,11 +2359,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())
@@ -2813,4 +3048,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 5bb706c9a19c72f8c4c37b9bfc6677851a4ef201..2172770ea95bb3d3a6b4bb6b133bf43cf63564b5 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; }
@@ -155,6 +162,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;
 
@@ -237,6 +251,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);
 
@@ -251,9 +275,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; }
@@ -522,6 +553,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 9a565d4f74d3349d1abc30254ee09382ac005953..ca3a2242febb8e18261e3333531333b65b2cd116 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>
@@ -368,6 +371,8 @@ PrivateState::PrivateState(const Version &clientVersion,
       mLogicOp(LogicalOperation::Copy),
       mPatchVertices(3),
       mPixelLocalStorageActivePlanes(0),
+      mVariableRasterizationRateEnabled(false),
+      mVariableRasterizationRateMap(nullptr),
       mNoSimultaneousConstantColorAndAlphaBlendFunc(false),
       mSetBlendIndexedInvoked(false),
       mSetBlendFactorsIndexedInvoked(false),
@@ -1407,6 +1412,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());
@@ -1539,6 +1564,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;
     }
@@ -1705,6 +1733,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 < ES_2_0);
@@ -3642,6 +3672,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 1149f0fbbac548f899afd2aa59d49ba11e5c398c..74b4a868fbeaeab480947f5d7367de0854835044 100644
--- a/src/libANGLE/State.h
+++ b/src/libANGLE/State.h
@@ -174,6 +174,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,
@@ -478,6 +479,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
     DrawBufferMask mPLSDeferredBlendEnables;
     BlendStateExt::ColorMaskStorage::Type mPLSDeferredColorMasks;
 
+    // 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 a2f88c2aa97a91bf2cb69eed384e23e73a357de7..1c2efd86cfbab01adad3e4bfa08158433ac16dc3 100644
--- a/src/libANGLE/capture/capture_gles_ext_autogen.cpp
+++ b/src/libANGLE/capture/capture_gles_ext_autogen.cpp
@@ -5221,6 +5221,20 @@ CallCapture CaptureGetTranslatedShaderSourceANGLE(const State &glState,
     return CallCapture(angle::EntryPoint::GLGetTranslatedShaderSourceANGLE, std::move(paramBuffer));
 }
 
+CallCapture CaptureBindMetalRasterizationRateMapANGLE(const State &glState,
+                                                      bool isCallValid,
+                                                      GLuint framebuffer,
+                                                      GLMTLRasterizationRateMapANGLE map)
+{
+    ParamBuffer paramBuffer;
+
+    paramBuffer.addValueParam("framebuffer", ParamType::TGLuint, framebuffer);
+    paramBuffer.addValueParam("map", ParamType::TGLMTLRasterizationRateMapANGLE, map);
+
+    return CallCapture(angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE,
+                       std::move(paramBuffer));
+}
+
 CallCapture CaptureAcquireTexturesANGLE(const State &glState,
                                         bool isCallValid,
                                         GLuint numTextures,
@@ -12506,4 +12520,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 4c66283484fe4099b5f40d86dae24fdc96e0e558..12a97c955054bcde8ec204f892b1c17057a142ab 100644
--- a/src/libANGLE/capture/capture_gles_ext_autogen.h
+++ b/src/libANGLE/capture/capture_gles_ext_autogen.h
@@ -1007,6 +1007,12 @@ angle::CallCapture CaptureGetTranslatedShaderSourceANGLE(const State &glState,
                                                          GLsizei *length,
                                                          GLchar *source);
 
+// GL_ANGLE_variable_rasterization_rate_metal
+angle::CallCapture CaptureBindMetalRasterizationRateMapANGLE(const State &glState,
+                                                             bool isCallValid,
+                                                             GLuint framebuffer,
+                                                             GLMTLRasterizationRateMapANGLE map);
+
 // GL_ANGLE_vulkan_image
 angle::CallCapture CaptureAcquireTexturesANGLE(const State &glState,
                                                bool isCallValid,
@@ -2954,6 +2960,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 0545f11dbf48a0ddab1de69dea6047d03e9f506f..fc5006a6ee70bfe034cd791a542c28d64e53c43e 100644
--- a/src/libANGLE/formatutils.cpp
+++ b/src/libANGLE/formatutils.cpp
@@ -583,6 +583,7 @@ static GLenum EquivalentBlitInternalFormat(GLenum internalformat)
     // multisampled RGBA8 renderbuffer to a BGRA8 texture). This could
     // be expanded and/or autogenerated if that is found necessary.
     if (internalformat == GL_BGRA_EXT || internalformat == GL_BGRA8_EXT ||
+        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 cec1012e2fc134e4a26c0622de37946f1388f8a1..a2b8875dda9d403091a5b3292472949962d1d5db 100644
--- a/src/libANGLE/gles_extensions_autogen.cpp
+++ b/src/libANGLE/gles_extensions_autogen.cpp
@@ -246,6 +246,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);
@@ -282,6 +283,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"] = enableableExtension(&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 fa94ee6ddfbd2dd33991805cd19a388c722d367a..f74819899da8ce6ad8ca140c5ba88921c93ad0ab 100644
--- a/src/libANGLE/gles_extensions_autogen.h
+++ b/src/libANGLE/gles_extensions_autogen.h
@@ -698,6 +698,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;
 
@@ -806,6 +809,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 5a02f1d07a4145c6bb208495fb65a494a1d91437..4218674eea7b162060593a8714987db7e289e7e1 100644
--- a/src/libANGLE/renderer/ContextImpl.cpp
+++ b/src/libANGLE/renderer/ContextImpl.cpp
@@ -108,4 +108,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 dbfd9c5600f402d462564e4f4f447a82d9482d18..a7e3fa9132f5297bbe2ade4eed37716de6a0b1dd 100644
--- a/src/libANGLE/renderer/ContextImpl.h
+++ b/src/libANGLE/renderer/ContextImpl.h
@@ -280,6 +280,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/DisplayImpl.cpp b/src/libANGLE/renderer/DisplayImpl.cpp
index 8b4573f4d9dccfbeaccedfb8f6f07fb1ad1dc3f3..bf46443c7ed5449ffb31348a433e9c89ff99511c 100644
--- a/src/libANGLE/renderer/DisplayImpl.cpp
+++ b/src/libANGLE/renderer/DisplayImpl.cpp
@@ -102,7 +102,7 @@ egl::Error DisplayImpl::validatePixmap(const egl::Config *config,
                                        const egl::AttributeMap &attributes) const
 {
     UNREACHABLE();
-    return egl::Error(EGL_BAD_DISPLAY, "DisplayImpl::valdiatePixmap unimplemented.");
+    return egl::Error(EGL_BAD_DISPLAY, "DisplayImpl::validatePixmap unimplemented.");
 }
 
 const egl::Caps &DisplayImpl::getCaps() const
diff --git a/src/libANGLE/renderer/gl/FunctionsGL.cpp b/src/libANGLE/renderer/gl/FunctionsGL.cpp
index 7b6aa2a7a1f66dd8c06353959823d23dd17ae9c4..6798c79869bea01ee270764c234d7d216336bb3a 100644
--- a/src/libANGLE/renderer/gl/FunctionsGL.cpp
+++ b/src/libANGLE/renderer/gl/FunctionsGL.cpp
@@ -55,7 +55,11 @@ 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 7a6d3a90e0efdd2a569787301935dd923a6b1fe7..21db7a5a46eabb929187d3098b3524cb038c6e88 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -2512,6 +2512,9 @@ angle::Result StateManagerGL::syncState(const gl::Context *context,
                         case gl::state::EXTENDED_DIRTY_BIT_BLEND_ADVANCED_COHERENT:
                             setBlendAdvancedCoherent(state.isBlendAdvancedCoherentEnabled());
                             break;
+                        case gl::state::EXTENDED_DIRTY_BIT_VARIABLE_RASTERIZATION_RATE:
+                            // Unimplemented extensions.
+                            break;
                         default:
                             UNREACHABLE();
                             break;
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 d7a8d303e14b84a1682efdf7c5f9d178cf0602cd..4f0fe2073edd0ca256ad96c358d7b656fa4181b1 100644
--- a/src/libANGLE/renderer/metal/BufferMtl.mm
+++ b/src/libANGLE/renderer/metal/BufferMtl.mm
@@ -400,7 +400,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 65cde4caf7c08051bd50aac215d5925acb4218e9..503fc7b5213fb5cadd0d64a57b1dcc912448a690 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,
@@ -555,6 +559,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,
@@ -648,6 +653,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 f70c884921da44eb214456f3f42c5a818e7587b2..035aed0ae4802d45f3a970dc1896c2a6c718fcba 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.mm
+++ b/src/libANGLE/renderer/metal/ContextMtl.mm
@@ -246,6 +246,8 @@ void ContextMtl::onDestroy(const gl::Context *context)
     mIncompleteTextures.onDestroy(context);
     mProvokingVertexHelper.onDestroy(this);
     mDummyXFBRenderTexture = nullptr;
+    mRasterizationRateMap.reset();
+    mRasterizationRateMapTexture = nil;
 
     mContextDevice.reset();
 }
@@ -1056,6 +1058,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;
 }
 
@@ -1394,6 +1403,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;
         }
@@ -1648,6 +1660,37 @@ 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,
@@ -2496,8 +2539,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;
 }
@@ -2606,10 +2663,14 @@ 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.
@@ -2634,6 +2695,15 @@ 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 4452019e24bb66cc82b5a76bea4236860df85121..01dc9847aee46b15029653edbcbf2c917d2eef15 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::Error(EGL_BAD_DISPLAY);
+#else
     return egl::NoError();
+#endif
 }
 
 std::string DisplayMtl::getRendererDescription()
@@ -1126,6 +1136,15 @@ 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);
@@ -1197,6 +1216,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));
 
@@ -1387,6 +1408,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 32a297309845f4f9779ffc70f6a61f20c4d4d94b..5520cd467ada81fbd794afcdff8c361cd8d766eb 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,8 @@ 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);
@@ -1823,9 +1904,8 @@ angle::Result FramebufferMtl::unresolveIfNeeded(const gl::Context *context,
         const mtl::RenderPassColorAttachmentDesc &colorAttachment =
             renderPassDesc.colorAttachments[colorIndexGL];
 
-        if (colorAttachment.loadAction != MTLLoadActionLoad ||
-            !colorAttachment.hasImplicitMSTexture() ||
-            !colorAttachment.implicitMSTexture->shouldNotLoadStore())
+        if (colorAttachment.loadAction != MTLLoadActionLoad || !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 29f8f59906801cf6218a1517f9b749ce0b1e1d3e..fe46dfb83c88da315557aba10b607eac21b94ce3 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::Error(EGL_BAD_ATTRIBUTE, "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::Error(EGL_BAD_ATTRIBUTE, "Incompatible format");
     }
@@ -123,18 +125,19 @@ 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);
+        mNativeTexture = 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 362d4c6edfce2ef8eb33fe1e41f2ecd3390ac5e2..ed19811072fcaa783924e6ad0f37f139a7557c64 100644
--- a/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm
+++ b/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm
@@ -876,7 +876,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 04bff707a1088897091e646c0c885f892548e639..49e8ea0e6607a96e00f531f0dc0d637a8a4daec5 100644
--- a/src/libANGLE/renderer/metal/SurfaceMtl.mm
+++ b/src/libANGLE/renderer/metal/SurfaceMtl.mm
@@ -694,8 +694,9 @@ 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/TextureMtl.h b/src/libANGLE/renderer/metal/TextureMtl.h
index 7593d5c591583213ebae9e655ec6ecd82eafa0bc..cc75417723848e9719cfbc4b77400ed3d72b9b9b 100644
--- a/src/libANGLE/renderer/metal/TextureMtl.h
+++ b/src/libANGLE/renderer/metal/TextureMtl.h
@@ -313,7 +313,7 @@ class TextureMtl : public TextureImpl
                                       const uint8_t *pixels,
                                       const mtl::TextureRef &image);
 
-    // Convert pixels to suported format before uploading to texture
+    // Convert pixels to supported format before uploading to texture.
     angle::Result convertAndSetPerSliceSubImage(const gl::Context *context,
                                                 int slice,
                                                 const MTLRegion &mtlArea,
diff --git a/src/libANGLE/renderer/metal/VertexArrayMtl.mm b/src/libANGLE/renderer/metal/VertexArrayMtl.mm
index b94a933c037a11f5c7c422f6f8ac7b720b4f5f47..10677ec356804462347a0f1c46ad242704afb1a8 100644
--- a/src/libANGLE/renderer/metal/VertexArrayMtl.mm
+++ b/src/libANGLE/renderer/metal/VertexArrayMtl.mm
@@ -1086,16 +1086,27 @@ 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 4251aade23897ef52d1bf340885684d28c9d6bac..74db2bf24c298136fde9b303608784709244a863 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,9 @@ 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 +591,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 +600,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 6855e93cfbdcc227790bd7126c73ca7afe104bb4..d817642838d8726335ea79592abce5a7e80fbda7 100644
--- a/src/libANGLE/renderer/metal/mtl_command_buffer.mm
+++ b/src/libANGLE/renderer/metal/mtl_command_buffer.mm
@@ -647,6 +647,18 @@ void CommandQueue::onCommandBufferCompleted(id<MTLCommandBuffer> buf,
               << error.localizedDescription.UTF8String;
         mCmdBufferError.store(static_cast<MTLCommandBufferError>(error.code));
     }
+    MTLCommandBufferStatus status = buf.status;
+    if (status != MTLCommandBufferStatusCompleted)
+    {
+        // 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 +1373,7 @@ void RenderCommandEncoder::reset()
     CommandEncoder::reset();
     mRecording        = false;
     mPipelineStateSet = false;
+    setRasterizationRateMap(nil);
     mCommands.clear();
 }
 
@@ -1396,7 +1409,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 +1553,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 +1837,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 +1851,25 @@ 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 +1881,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(screenSize.width, static_cast<NSUInteger>(roundf(adjustedSize.x)));
+        clampedRect.height = std::min(screenSize.height, static_cast<NSUInteger>(roundf(adjustedSize.y)));
+    }
+
     mCommands.push(CmdType::SetScissorRect).push(clampedRect);
 
     return *this;
@@ -2259,6 +2303,20 @@ 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 +2412,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..4bc6932440e9493ae436f5b17c01400125c49bb7 100644
--- a/src/libANGLE/renderer/metal/mtl_format_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_format_utils.mm
@@ -161,6 +161,34 @@ 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 6244308ff4cbce9567cc562c2fc78a56489577f2..6e40285bc32617781439d9e4de993a17bff6b665 100644
--- a/src/libANGLE/renderer/metal/mtl_render_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_render_utils.mm
@@ -556,7 +556,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)
@@ -636,8 +637,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)
     {
@@ -1114,8 +1116,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;
@@ -2482,8 +2484,8 @@ angle::Result CopyPixelsUtils::unpackPixelsWithDraw(const gl::Context *context,
 
     scissorRect = GetScissorRect(rect);
 
-    cmdEncoder->setViewport(viewport);
-    cmdEncoder->setScissorRect(scissorRect);
+    cmdEncoder->setViewport(viewport, nil);
+    cmdEncoder->setScissorRect(scissorRect, nil);
 
     // uniform
     CopyPixelFromBufferUniforms options;
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 f622b484570562e6eebf6507ed83b20770be42d8..1e8ce4bb456dbfcb48c3728465ea864d466058ff 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 1b97e18840d543d15cdbbeb42db18cb0a06973ec..3befac795680563d14f7ec1b925c9694812cf368 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 6a4300940aaa92f4a5ad0d35111adb30c75b1712..ed13c3068c01d21b9603c1f603d80e13f655539c 100644
--- a/src/libANGLE/renderer/metal/mtl_state_cache.h
+++ b/src/libANGLE/renderer/metal/mtl_state_cache.h
@@ -305,20 +305,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 4d89a6cdafe03400e215513882874faf00a248d4..c8bb7ceaafe9608c100c977f5ca4ea2cdc7b69a5 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;
     }
 
@@ -668,9 +653,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;
@@ -680,7 +667,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 a7475162cac11a0b9bd4c1aabdf0ce0457c25a85..57caa9f36cbf3d874ed3f98de628cbbfc55fba72 100644
--- a/src/libANGLE/renderer/metal/mtl_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_utils.mm
@@ -1396,6 +1396,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
@@ -1441,8 +1459,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 76852328172174fdbf6c2e8ae5ccafc826af4c1a..f6cac226cfd969daec4453ad318ad822deb720dc 100644
--- a/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
@@ -860,6 +860,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 47a2fb1c56b56049f1f83559a5d3bcd8a263088c..bfccb02e183cf5933f1f8737f6b85dab8750da89 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -6052,6 +6052,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/renderer/vulkan/vk_format_utils.cpp b/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
index 01136131aa33732d5c1421e95456d73cc4aaddaa..5a7cd43b2d3bdd1653a25248186b1b3e6d96b38e 100644
--- a/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
@@ -341,7 +341,7 @@ angle::FormatID ExternalFormatTable::getOrAllocExternalFormatID(uint64_t externa
 
     if (mExternalYuvFormats.size() >= kMaxExternalFormatCountSupported)
     {
-        ERR() << "ANGLE only suports maximum " << kMaxExternalFormatCountSupported
+        ERR() << "ANGLE only supports maximum " << kMaxExternalFormatCountSupported
               << " external renderable formats";
         return angle::FormatID::NONE;
     }
diff --git a/src/libANGLE/validationEGL.cpp b/src/libANGLE/validationEGL.cpp
index d1bc7fcd98951fe1e23a43440776665534bf6c6d..1cf4bedb4e60e4ac9f4e5bfaf7424d9e7cfd904d 100644
--- a/src/libANGLE/validationEGL.cpp
+++ b/src/libANGLE/validationEGL.cpp
@@ -3459,11 +3459,11 @@ bool ValidateCreatePixmapSurface(const ValidationContext *val,
 
     if (!(config->surfaceType & EGL_PIXMAP_BIT))
     {
-        val->setError(EGL_BAD_MATCH, "Congfig does not suport pixmaps.");
+        val->setError(EGL_BAD_MATCH, "Config does not support pixmaps.");
         return false;
     }
 
-    ANGLE_EGL_TRY_RETURN(val->eglThread, display->valdiatePixmap(config, pixmap, attributes),
+    ANGLE_EGL_TRY_RETURN(val->eglThread, display->validatePixmap(config, pixmap, attributes),
                          val->entryPoint, val->labeledObject, false);
 
     return true;
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index c56bc9385211bf3517a4a4ce7f482e77ed1d6acb..260e26877868f6f764e84c634ca6779429e1ed82 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -700,6 +700,9 @@ bool ValidCapUncommon(const PrivateState &state, ErrorSet *errors, GLenum cap, b
         case GL_BLEND_ADVANCED_COHERENT_KHR:
             return state.getExtensions().blendEquationAdvancedCoherentKHR;
 
+        case GL_VARIABLE_RASTERIZATION_RATE_ANGLE:
+            return state.getExtensions().variableRasterizationRateMetalANGLE;
+
         default:
             break;
     }
@@ -5910,6 +5913,19 @@ bool ValidateMaxShaderCompilerThreadsKHR(const Context *context,
     return true;
 }
 
+bool ValidateBindMetalRasterizationRateMapANGLE(const Context *context,
+                                                angle::EntryPoint entryPoint,
+                                                GLuint framebuffer,
+                                                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 63d4b9d2e2f231bc9f545924b5f03756e37cab23..48f98e8a1d049a83b7e18f39e6cc331b9a868bf4 100644
--- a/src/libANGLE/validationES32.cpp
+++ b/src/libANGLE/validationES32.cpp
@@ -431,12 +431,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 d7a285e9e6baa860d6832c43115049ae522f3c5f..b5942c52ca91ad824fdaa065dc35aad77c9dfaa9 100644
--- a/src/libANGLE/validationESEXT.cpp
+++ b/src/libANGLE/validationESEXT.cpp
@@ -3833,6 +3833,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 0e2d16ede08fd76ec352a4b7de78c24213b26b56..188239feecd8f5d129b4449ad256d35f0f7a0d0b 100644
--- a/src/libANGLE/validationESEXT_autogen.h
+++ b/src/libANGLE/validationESEXT_autogen.h
@@ -1006,6 +1006,12 @@ 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,
+                                                GLuint framebuffer,
+                                                GLMTLRasterizationRateMapANGLE map);
+
 // GL_ANGLE_vulkan_image
 bool ValidateAcquireTexturesANGLE(const Context *context,
                                   angle::EntryPoint entryPoint,
@@ -2961,6 +2967,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 56107cd5748a045c3708f23f5243169edfd635b8..39d5accee31a28e9986a86d3c4cd48a0b3ee4e71 100644
--- a/src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp
+++ b/src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp
@@ -181,6 +181,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)},
@@ -395,6 +396,7 @@ const ProcEntry g_procTable[] = {
     {"glFramebufferPixelLocalStorageRestoreANGLE", P(GL_FramebufferPixelLocalStorageRestoreANGLE)},
     {"glFramebufferRenderbuffer", P(GL_FramebufferRenderbuffer)},
     {"glFramebufferRenderbufferOES", P(GL_FramebufferRenderbufferOES)},
+    {"glFramebufferResolveRenderbufferWEBKIT", P(GL_FramebufferResolveRenderbufferWEBKIT)},
     {"glFramebufferShadingRateEXT", P(GL_FramebufferShadingRateEXT)},
     {"glFramebufferTexture", P(GL_FramebufferTexture)},
     {"glFramebufferTexture2D", P(GL_FramebufferTexture2D)},
diff --git a/src/libGLESv2/entry_points_gles_ext_autogen.cpp b/src/libGLESv2/entry_points_gles_ext_autogen.cpp
index 9c3c1bd0544b57e53b88e31ac27d51bf117917eb..0f4aad75055fa4607537a142dbbf7a6bca2a03c5 100644
--- a/src/libGLESv2/entry_points_gles_ext_autogen.cpp
+++ b/src/libGLESv2/entry_points_gles_ext_autogen.cpp
@@ -6848,6 +6848,55 @@ void GL_APIENTRY GL_GetTranslatedShaderSourceANGLE(GLuint shader,
     ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
 }
 
+// GL_ANGLE_variable_rasterization_rate_metal
+void GL_APIENTRY GL_BindMetalRasterizationRateMapANGLE(GLuint framebuffer,
+                                                       GLMTLRasterizationRateMapANGLE map)
+{
+    ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
+    Context *context = GetValidGlobalContext();
+    EVENT(context, GLBindMetalRasterizationRateMapANGLE,
+          "context = %d, framebuffer = %u, map = 0x%016" PRIxPTR "", CID(context), framebuffer,
+          (uintptr_t)map);
+
+    if (ANGLE_LIKELY(context != nullptr))
+    {
+        SCOPED_SHARE_CONTEXT_LOCK(context);
+        bool isCallValid = context->skipValidation();
+        if (!isCallValid)
+        {
+            if (ANGLE_LIKELY(context->getExtensions().variableRasterizationRateMetalANGLE))
+            {
+#if defined(ANGLE_ENABLE_ASSERTS)
+                const uint32_t errorCount = context->getPushedErrorCount();
+#endif
+                isCallValid = ValidateBindMetalRasterizationRateMapANGLE(
+                    context, angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE, framebuffer,
+                    map);
+#if defined(ANGLE_ENABLE_ASSERTS)
+                ASSERT(context->getPushedErrorCount() - errorCount == (isCallValid ? 0 : 1));
+#endif
+            }
+            else
+            {
+                RecordVersionErrorESEXT(context,
+                                        angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE);
+            }
+        }
+        if (ANGLE_LIKELY(isCallValid))
+        {
+            context->bindMetalRasterizationRateMap(framebuffer, map);
+        }
+        ANGLE_CAPTURE_GL(BindMetalRasterizationRateMapANGLE, isCallValid, context, framebuffer,
+                         map);
+    }
+    else
+    {
+        GenerateContextLostErrorOnCurrentGlobalContext(
+            angle::EntryPoint::GLBindMetalRasterizationRateMapANGLE);
+    }
+    ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
+}
+
 // GL_ANGLE_vulkan_image
 void GL_APIENTRY GL_AcquireTexturesANGLE(GLuint numTextures,
                                          const GLuint *textures,
@@ -20605,4 +20654,59 @@ 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)
+{
+    ASSERT(!egl::Display::GetCurrentThreadUnlockedTailCall()->any());
+    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 (ANGLE_LIKELY(context != nullptr))
+    {
+        RenderbufferID renderbufferPacked = PackParam<RenderbufferID>(renderbuffer);
+        SCOPED_SHARE_CONTEXT_LOCK(context);
+        bool isCallValid = context->skipValidation();
+        if (!isCallValid)
+        {
+            if (ANGLE_LIKELY(context->getExtensions().explicitResolveTargetWEBKIT))
+            {
+#if defined(ANGLE_ENABLE_ASSERTS)
+                const uint32_t errorCount = context->getPushedErrorCount();
+#endif
+                isCallValid = ValidateFramebufferResolveRenderbufferWEBKIT(
+                    context, angle::EntryPoint::GLFramebufferResolveRenderbufferWEBKIT, target,
+                    attachment, renderbuffertarget, renderbufferPacked);
+#if defined(ANGLE_ENABLE_ASSERTS)
+                ASSERT(context->getPushedErrorCount() - errorCount == (isCallValid ? 0 : 1));
+#endif
+            }
+            else
+            {
+                RecordVersionErrorESEXT(context,
+                                        angle::EntryPoint::GLFramebufferResolveRenderbufferWEBKIT);
+            }
+        }
+        if (ANGLE_LIKELY(isCallValid))
+        {
+            context->framebufferResolveRenderbufferWEBKIT(target, attachment, renderbuffertarget,
+                                                          renderbufferPacked);
+        }
+        ANGLE_CAPTURE_GL(FramebufferResolveRenderbufferWEBKIT, isCallValid, context, target,
+                         attachment, renderbuffertarget, renderbufferPacked);
+    }
+    else
+    {
+        GenerateContextLostErrorOnCurrentGlobalContext(
+            angle::EntryPoint::GLFramebufferResolveRenderbufferWEBKIT);
+    }
+    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 d2cd59977f6706b896b8babc84e26cc3632132fb..c3d4f83d989742640e22b352fd8b868a372b14fb 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,
@@ -1991,6 +1995,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 7ac546bcb9567f8a46ce2d312636494eaa0d8633..b9d32bcfcce252533e10ccbdbd2b7b739a7a8497 100644
--- a/src/libGLESv2/libGLESv2_autogen.cpp
+++ b/src/libGLESv2/libGLESv2_autogen.cpp
@@ -3949,6 +3949,13 @@ void GL_APIENTRY glGetTranslatedShaderSourceANGLE(GLuint shader,
     return GL_GetTranslatedShaderSourceANGLE(shader, bufSize, length, source);
 }
 
+// GL_ANGLE_variable_rasterization_rate_metal
+void GL_APIENTRY glBindMetalRasterizationRateMapANGLE(GLuint framebuffer,
+                                                      GLMTLRasterizationRateMapANGLE map)
+{
+    return GL_BindMetalRasterizationRateMapANGLE(framebuffer, map);
+}
+
 // GL_ANGLE_vulkan_image
 void GL_APIENTRY glAcquireTexturesANGLE(GLuint numTextures,
                                         const GLuint *textures,
@@ -6149,4 +6156,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 f327d2cd3230b736161c675497447f5ad3ac648c..bdaaf655896dd97e30ebec7c9ad3a0aa8558d592 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
@@ -1301,6 +1304,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 f9a5c9a788d8516b15c7421a65c03ce412068f56..6aa2e8fdad35d7bdd2f3b87090f4aac413af9995 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
@@ -1301,6 +1304,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 f17dc1f890ed8d03980c49e8c9a050855df37e78..9879bf722a1728d72d57e30143da49794bced479 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
@@ -1301,6 +1304,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 ff548145eac19fb1339c2d4bb911aadd0dfd6ed7..4ba41d645d92e625b0b72ead9805fda4e699f8b8 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
@@ -1301,6 +1304,9 @@ EXPORTS
     glEndTilingQCOM
     glStartTilingQCOM
 
+    ; GL_WEBKIT_explicit_resolve_target
+    glFramebufferResolveRenderbufferWEBKIT
+
     ; EGL 1.0
     EGL_ChooseConfig
     EGL_CopyBuffers
diff --git a/src/tests/angle_end2end_tests.gni b/src/tests/angle_end2end_tests.gni
index 57d8f60ebf64ba02b7c4cf0c16c4be4a64cba7b4..61921da4926c5e57768ab0ead5580d41d032affe 100644
--- a/src/tests/angle_end2end_tests.gni
+++ b/src/tests/angle_end2end_tests.gni
@@ -89,6 +89,7 @@ angle_end2end_tests_sources = [
   "gl_tests/FramebufferRenderMipmapTest.cpp",
   "gl_tests/FramebufferTest.cpp",
   "gl_tests/GLSLTest.cpp",
+  "gl_tests/GLSLUBTest.cpp",
   "gl_tests/GeometryShaderTest.cpp",
   "gl_tests/GetImageTest.cpp",
   "gl_tests/GetTexLevelParameterTest.cpp",
diff --git a/src/tests/angle_unittest_main.cpp b/src/tests/angle_unittest_main.cpp
index 1356dceac33eb8ff7056822826701b0269b522a2..8c217637b79e486faf1ffb0958de61196e2839f6 100644
--- a/src/tests/angle_unittest_main.cpp
+++ b/src/tests/angle_unittest_main.cpp
@@ -6,7 +6,9 @@
 
 #include "GLSLANG/ShaderLang.h"
 #include "gtest/gtest.h"
-#include "test_utils/runner/TestSuite.h"
+#if defined(ANGLE_HAS_RAPIDJSON)
+#    include "test_utils/runner/TestSuite.h"
+#endif
 
 class CompilerTestEnvironment : public testing::Environment
 {
@@ -40,8 +42,13 @@ int main(int argc, char **argv)
             gVerbose = true;
         }
     }
-
+#if defined(ANGLE_HAS_RAPIDJSON)
     angle::TestSuite testSuite(&argc, argv);
     testing::AddGlobalTestEnvironment(new CompilerTestEnvironment());
     return testSuite.run();
+#else
+    testing::AddGlobalTestEnvironment(new CompilerTestEnvironment());
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+#endif  // defined(ANGLE_HAS_RAPIDJSON)
 }
diff --git a/src/tests/compiler_tests/MSLOutput_test.cpp b/src/tests/compiler_tests/MSLOutput_test.cpp
index b525c5b983d4aeb60ec91730af1a891609787fb8..f5741f6b13e82498d689cade7f7540e16bcf2c3e 100644
--- a/src/tests/compiler_tests/MSLOutput_test.cpp
+++ b/src/tests/compiler_tests/MSLOutput_test.cpp
@@ -11,6 +11,7 @@
 #include "GLSLANG/ShaderLang.h"
 #include "angle_gl.h"
 #include "gtest/gtest.h"
+#include "gmock/gmock.h"
 #include "tests/test_utils/compiler_test.h"
 
 using namespace sh;
@@ -1115,3 +1116,99 @@ TEST_F(MSLOutputTest, EnsureLoopForwardProgressFinite)
     compile(shaderString, options);
     ASSERT_FALSE(foundInCode(SH_MSL_METAL_OUTPUT, "loopForwardProgress();"));
 }
+
+// Tests that uint assignment operators use the expected functions.
+TEST_F(MSLOutputTest, UintAssignmentOperators)
+{
+    const std::string &shaderString =R"(#version 300 es
+precision highp float;
+in vec4 i;
+out vec4 o;
+void main() {
+    ivec4 ii = ivec4(i);
+    ii += 2;
+    ii -= 3;
+    ii *= 4;
+    ii /= 5;
+    ii %= 6;
+    ii &= 7;
+    ii |= 8;
+    ii ^= 9;
+    ii <<= 10;
+    ii >>= 11;
+    ii++;
+    ++ii;
+    ii--;
+    --ii;
+    o = vec4(ii);
+})";
+    const char expected[] = R"(void ANGLE__0_main(thread ANGLE_FragmentOut & ANGLE_fragmentOut, thread ANGLE_FragmentIn & ANGLE_fragmentIn)
+{
+  metal::int4 _uii = ANGLE_ftoi<metal::int4>(ANGLE_fragmentIn._ui);
+  _uii = ANGLE_addAssignInt(_uii, 2);
+  _uii = ANGLE_subAssignInt(_uii, 3);
+  _uii = ANGLE_imul(_uii, 4);
+  _uii = ANGLE_div(_uii, 5);
+  _uii = ANGLE_imod(_uii, 6);
+  _uii &= 7;
+  _uii |= 8;
+  _uii ^= 9;
+  _uii = ANGLE_ilshift(_uii, 10);
+  _uii = ANGLE_rshift(_uii, 11);
+  ANGLE_postIncrementInt(_uii);
+  ANGLE_preIncrementInt(_uii);
+  ANGLE_postDecrementInt(_uii);
+  ANGLE_preDecrementInt(_uii);
+  ANGLE_fragmentOut._uo = metal::float4(_uii);
+})";
+    compile(shaderString);
+    EXPECT_THAT(outputCode(SH_MSL_METAL_OUTPUT), testing::HasSubstr(expected));
+}
+
+// Tests that some uint assignment operators use the swizzle ref helper if the swizzle is in lvalue position in the generated code.
+TEST_F(MSLOutputTest, UintSwizzleAssignmentOperators)
+{
+    const std::string &shaderString =R"(#version 300 es
+precision highp float;
+in vec4 i;
+out vec4 o;
+void main() {
+    ivec4 ii = ivec4(i);
+    ii.x += 2;
+    ii.y -= 3;
+    ii.z *= 4;
+    ii.w /= 5;
+    ii.x %= 6;
+    ii.y &= 7;
+    ii.y |= 8;
+    ii.z ^= 9;
+    ii.y <<= 10;
+    ii.z >>= 11;
+    ii.x++;
+    ++ii.y;
+    ii.x--;
+    --ii.y;
+    o = vec4(ii);
+})";
+    const char expected[] = R"(void ANGLE__0_main(thread ANGLE_FragmentOut & ANGLE_fragmentOut, thread ANGLE_FragmentIn & ANGLE_fragmentIn)
+{
+  metal::int4 _uii = ANGLE_ftoi<metal::int4>(ANGLE_fragmentIn._ui);
+  _uii.x = ANGLE_addInt(_uii.x, 2);
+  _uii.y = ANGLE_subInt(_uii.y, 3);
+  _uii.z = ANGLE_imul(_uii.z, 4);
+  _uii.w = ANGLE_div(_uii.w, 5);
+  _uii.x = ANGLE_imod(_uii.x, 6);
+  _uii.y = (_uii.y & 7);
+  _uii.y = (_uii.y | 8);
+  _uii.z = (_uii.z ^ 9);
+  _uii.y = ANGLE_ilshift(_uii.y, 10);
+  _uii.z = ANGLE_rshift(_uii.z, 11);
+  ANGLE_postIncrementInt(ANGLE_swizzle_ref(_uii, 0u));
+  ANGLE_preIncrementInt(ANGLE_swizzle_ref(_uii, 1u));
+  ANGLE_postDecrementInt(ANGLE_swizzle_ref(_uii, 0u));
+  ANGLE_preDecrementInt(ANGLE_swizzle_ref(_uii, 1u));
+  ANGLE_fragmentOut._uo = metal::float4(_uii);
+})";
+    compile(shaderString);
+    EXPECT_THAT(outputCode(SH_MSL_METAL_OUTPUT), testing::HasSubstr(expected));
+}
diff --git a/src/tests/egl_tests/EGLContextCompatibilityTest.cpp b/src/tests/egl_tests/EGLContextCompatibilityTest.cpp
index 38e7afe9118d00a180477a6d4bcf86d1b29a8f34..53c8599c3223da87b2f34203baf81bcd28803a5d 100644
--- a/src/tests/egl_tests/EGLContextCompatibilityTest.cpp
+++ b/src/tests/egl_tests/EGLContextCompatibilityTest.cpp
@@ -173,8 +173,9 @@ class EGLContextCompatibilityTest : public ANGLETestBase
     void SetUp() final
     {
         ANGLETestBase::ANGLETestSetUp();
+#if !defined(EGL_EGL_PROTOTYPES) || !EGL_EGL_PROTOTYPES
         ASSERT_TRUE(eglGetPlatformDisplay != nullptr);
-
+#endif
         EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, mRenderer, EGL_NONE};
         mDisplay              = eglGetPlatformDisplay(GetEglPlatform(),
                                                       reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
@@ -490,11 +491,13 @@ void RegisterContextCompatibilityTests()
 
     LoadEntryPointsWithUtilLoader(kDefaultGLESDriver);
 
+#if !defined(EGL_EGL_PROTOTYPES) || !EGL_EGL_PROTOTYPES
     if (eglGetPlatformDisplay == nullptr)
     {
         std::cerr << "EGLContextCompatibilityTest: missing eglGetPlatformDisplay\n";
         return;
     }
+#endif
 
     for (EGLint renderer : renderers)
     {
diff --git a/src/tests/egl_tests/EGLDisplayTest.cpp b/src/tests/egl_tests/EGLDisplayTest.cpp
index b51b5f07cba261dddef39cc80115986108722bab..817668c08b7a018ad2525aa9999914c42836ddc7 100644
--- a/src/tests/egl_tests/EGLDisplayTest.cpp
+++ b/src/tests/egl_tests/EGLDisplayTest.cpp
@@ -182,7 +182,9 @@ TEST_P(EGLDisplayTest, GetPlatformDisplayEXT)
     // eglGetPlatformDisplayEXT() requires EGL_EXT_platform_base.
     ANGLE_SKIP_TEST_IF(!IsEGLClientExtensionEnabled("EGL_EXT_platform_base"));
 
+#if !defined(EGL_EGLEXT_PROTOTYPES) || !EGL_EGLEXT_PROTOTYPES
     ASSERT_TRUE(eglGetPlatformDisplayEXT != nullptr);
+#endif
 
     EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE};
     EGLDisplay display = eglGetPlatformDisplayEXT(
diff --git a/src/tests/gl_tests/CopyCompressedTextureTest.cpp b/src/tests/gl_tests/CopyCompressedTextureTest.cpp
index 6ddadba63706defe665858101598abf43d7bf3aa..29c31ecfb445af09eb5c5be7c7e9b5819be3cc2f 100644
--- a/src/tests/gl_tests/CopyCompressedTextureTest.cpp
+++ b/src/tests/gl_tests/CopyCompressedTextureTest.cpp
@@ -67,12 +67,13 @@ class CopyCompressedTextureTest : public ANGLETest<>
             return false;
         }
 
+#if !defined(GL_GLEXT_PROTOTYPES) || !GL_GLEXT_PROTOTYPES
         EXPECT_NE(nullptr, glCompressedCopyTextureCHROMIUM);
         if (glCompressedCopyTextureCHROMIUM == nullptr)
         {
             return false;
         }
-
+#endif
         return true;
     }
 
diff --git a/src/tests/gl_tests/CubeMapTextureTest.cpp b/src/tests/gl_tests/CubeMapTextureTest.cpp
index d279d1c0ceba79f5a2516aea016bf425d1506ea1..ec9b36933d994e425280143d584e62064f24dd8f 100644
--- a/src/tests/gl_tests/CubeMapTextureTest.cpp
+++ b/src/tests/gl_tests/CubeMapTextureTest.cpp
@@ -7,7 +7,6 @@
 #include "test_utils/ANGLETest.h"
 #include "test_utils/angle_test_configs.h"
 #include "test_utils/gl_raii.h"
-#include "util/gles_loader_autogen.h"
 
 using namespace angle;
 
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index a4b141a01ce77aff104d4c3c0ff3191a61e1e37a..6b50daabd315a7464fd566d0eb725d7cf6da0803 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -593,6 +593,29 @@ TEST_P(GLSLTest, SwizzledChainedAssignIncrement)
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(75, 75, 38, 38));
 }
 
+// This shader uses chained assign-equals ops with swizzle, often reusing the same variable
+// as part of a swizzle, ivec variant
+TEST_P(GLSLTest, SwizzledChainedAssignIncrementIvec)
+{
+    constexpr char kFS[] = R"(
+precision mediump float;
+void main() {
+    ivec2 v = ivec2(1,5);
+    // at the end of next statement, values in
+    // v.x = 12, v.y = 12
+    v.xy += v.yx += v.xy;
+    // v1 and v2, both are initialized with (12,12)
+    ivec2 v1 = v, v2 = v;
+    v1.xy += v2.yx += ++(v.xy);  // v1 = 37, v2 = 25 each
+    v1.xy += v2.yx += (v.xy)++;  // v1 = 75, v2 = 38 each
+    gl_FragColor = vec4(vec2(v1),vec2(v2))/255.;  // 75, 75, 38, 38
+})";
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    ASSERT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(75, 75, 38, 38));
+}
+
 TEST_P(GLSLTest, NamelessScopedStructs)
 {
     constexpr char kFS[] = R"(precision mediump float;
@@ -6884,6 +6907,64 @@ void main()
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
 }
 
+// Tests that extracting samplers from structs that are members of other structs
+// will not cause a error when accessing any fields (e.g., uni.b)
+// (This is a modification of the above test "SamplerInStructMemberIndexing").
+TEST_P(GLSLTest, SamplerInStructInStructMemberIndexing)
+{
+    const char kVertexShader[] = R"(attribute vec2 position;
+varying vec2 texCoord;
+void main()
+{
+    gl_Position = vec4(position, 0, 1);
+    texCoord = position * 0.5 + vec2(0.5);
+})";
+
+    const char kFragmentShader[] = R"(precision mediump float;
+struct S1 { sampler2D samp; };
+struct S2 { S1 s1; bool b; };
+uniform S2 uni;
+varying vec2 texCoord;
+void main()
+{
+    uni;
+    if (uni.b)
+    {
+        gl_FragColor = texture2D(uni.s1.samp, texCoord);
+    }
+    else
+    {
+        gl_FragColor = vec4(1, 0, 0, 1);
+    }
+})";
+
+    ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader);
+    glUseProgram(program);
+
+    GLint bLoc = glGetUniformLocation(program, "uni.b");
+    ASSERT_NE(-1, bLoc);
+    GLint sampLoc = glGetUniformLocation(program, "uni.s1.samp");
+    ASSERT_NE(-1, sampLoc);
+
+    glUniform1i(bLoc, 1);
+
+    std::array<GLColor, 4> kGreenPixels = {
+        {GLColor::green, GLColor::green, GLColor::green, GLColor::green}};
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 kGreenPixels.data());
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    drawQuad(program, "position", 0.5f);
+    ASSERT_GL_NO_ERROR();
+
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
 // Tests that rewriting samplers in structs works when passed as function argument.  In this test,
 // the function references another struct, which is not being modified.  Regression test for AST
 // validation applied to a multipass transformation, where references to declarations were attempted
@@ -10884,6 +10965,43 @@ void main()
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
 }
 
+// Test that modf(u, v.x) works correctly.
+TEST_P(GLSLTest_ES3, ModFIntegerToSwizzle)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp float;
+out vec4 o;
+uniform vec2 u;
+bool isEq(vec2 a, vec2 b) { return all(lessThan(abs(a-b), vec2(0.001))); }
+void main()
+{
+    vec4 i1;
+    vec2 r1 = modf(u, i1.xy);
+    vec2 r2;
+    r2.x = modf(u.x, i1.z);
+    r2.y = modf(u.y, i1.w);
+    vec2 i2;
+    vec2 r4 = modf(u, i2.yx);
+
+    o = vec4(0.0, 0.0, 0.0, 1.0);
+    if (isEq(r1, vec2(0.14, 0.15)) && isEq(i1.xy, vec2(3.0, 4.0)))
+        o.r = 1.0;
+    if (isEq(r2, vec2(0.14, 0.15)) && isEq(i1.zw, vec2(3.0, 4.0)))
+        o.g = 1.0;
+    if (isEq(r4, vec2(0.14, 0.15)) && isEq(i2, vec2(4.0, 3.0)))
+        o.b = 1.0;
+})";
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    glUseProgram(program);
+    ASSERT_GL_NO_ERROR();
+    GLint uloc = glGetUniformLocation(program, "u");
+    ASSERT_NE(uloc, -1);
+    glUniform2f(uloc, 3.14f, 4.15f);
+    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
+    ASSERT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
+}
+
 // Test a fragment shader that returns inside if (that being the only branch that actually gets
 // executed). Regression test for http://anglebug.com/42261034
 TEST_P(GLSLTest, IfElseIfAndReturn)
@@ -21310,6 +21428,816 @@ void main() {
 
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
 }
+
+// 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/GLSLUBTest.cpp b/src/tests/gl_tests/GLSLUBTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..07d0c1c6fdb07a6661f086356fbe39fdfaccb57e
--- /dev/null
+++ b/src/tests/gl_tests/GLSLUBTest.cpp
@@ -0,0 +1,619 @@
+//
+// Copyright 2025 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/angle_test_configs.h"
+#include "test_utils/gl_raii.h"
+#include "util/shader_utils.h"
+
+namespace
+{
+using namespace angle;
+
+class GLSLUBTest : public ANGLETest<>
+{
+  protected:
+    GLSLUBTest()
+    {
+        setWindowWidth(128);
+        setWindowHeight(128);
+        setConfigRedBits(8);
+        setConfigGreenBits(8);
+        setConfigBlueBits(8);
+        setConfigAlphaBits(8);
+    }
+};
+
+// grep TEST_P src/tests/gl_tests/GLSLUBTest.cpp
+// For prefixes Add, Sub find:
+//   IntInt
+//   IntIvec
+//   IvecInt
+//   IvecIvec
+//   AssignIvecInt
+//   AssignIvecIvec
+
+// Test int + int with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, AddIntIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    int r0 = u + -1;
+    int r1 = u + 0;
+    int r2 = 2147483646 + u;
+    int r3 = u + 2147483647;
+
+    gl_FragColor.r = r0 == 1 ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == 2 ? 1.0 : 0.0;
+    gl_FragColor.b = r2 == -2147483648 ? 1.0 : 0.0;
+    gl_FragColor.a = r3 == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test int + ivec overflow. Expect wraparound.
+TEST_P(GLSLUBTest, AddIntIvecOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    ivec4 r = u + ivec4(0, -1, 1, 2147483647);
+    gl_FragColor.r = r.x == 2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == 1 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == 3 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test int + ivec overflow. Expect wraparound.
+TEST_P(GLSLUBTest, AddIvecIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, 2147483647) + u;
+    gl_FragColor.r = r.x == 2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == 1 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == 3 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test ivec + ivec, ivec + int overflow. Expect wraparound.
+TEST_P(GLSLUBTest, AddIvecIvecOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform ivec4 u;
+void main() {
+    ivec4 r0 = u + ivec4(0, -1, 1, 2147483647);
+    ivec4 r1 = ivec4(0, -1, 1, 2147483647) + u;
+    gl_FragColor.r = r0 == r1 ? 1.0 : 0.0;
+    gl_FragColor.g = r0.y == 1 ? 1.0 : 0.0;
+    gl_FragColor.b = r0.z == 3 ? 1.0 : 0.0;
+    gl_FragColor.a = r0.w == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4i(u, 2, 2, 2, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp ivec += int overflow. Expect wraparound.
+TEST_P(GLSLUBTest, AddAssignIvecIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, 2147483647);
+    r += u;
+    gl_FragColor.r = r.x == 2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == 1 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == 3 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp ivec += ivec overflow. Expect wraparound.
+TEST_P(GLSLUBTest, AddAssignIvecIvecOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform ivec4 u;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, 2147483647);
+    r += u;
+    gl_FragColor.r = r.x == 2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == 1 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == 3 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4i(u, 2, 2, 2, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test int - int with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, SubIntIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    int r0 = u - (-1);
+    int r1 = u - 0;
+    int r2 = -2147483646 - u;
+    int r3 = u - (-2147483647);
+
+    gl_FragColor.r = r0 == 3 ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == 2 ? 1.0 : 0.0;
+    gl_FragColor.b = r2 == 2147483648 ? 1.0 : 0.0;
+    gl_FragColor.a = r3 == -2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_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 - ivec underflow. Expect wraparound.
+TEST_P(GLSLUBTest, SubIntIvecUnderflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    ivec4 r = u - ivec4(0, -1, 1, 2147483647);
+    gl_FragColor.r = r.x == 2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == 3 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == 1 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == -2147483645 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test highp ivec - int underflow. Expect wraparound.
+TEST_P(GLSLUBTest, SubIvecIntUnderflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, -2147483647) - u;
+    gl_FragColor.r = r.x == -2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == -3 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == -1 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == 2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_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 underflow. Expect wraparound.
+TEST_P(GLSLUBTest, SubAssignIvecIntUnderflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, -2147483647);
+    r -= u;
+    gl_FragColor.r = r.x == -2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == -3 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == -1 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == 2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2);
+    drawQuad(testProgram, essl1_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 underflow. Expect wraparound.
+TEST_P(GLSLUBTest, SubAssignIvecIvecUnderflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform ivec4 u;
+void main() {
+    ivec4 r = ivec4(0, -1, 1, -2147483647);
+    r -= u;
+    gl_FragColor.r = r.x == -2 ? 1.0 : 0.0;
+    gl_FragColor.g = r.y == -3 ? 1.0 : 0.0;
+    gl_FragColor.b = r.z == -1 ? 1.0 : 0.0;
+    gl_FragColor.a = r.w == 2147483647 ? 1.0 : 0.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4i(u, 2, 2, 2, 2);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test ++int with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PreIncrementIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    int r0 = u;
+    int r1 = ++r0;
+
+    gl_FragColor.r = r0 == -2147483648 ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == -2147483648 ? 1.0 : 0.0;
+    gl_FragColor.b = u == 2147483647 ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test int++ with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PostIncrementIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    int r0 = u;
+    int r1 = r0++;
+
+    gl_FragColor.r = r0 == -2147483648 ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == 2147483647 ? 1.0 : 0.0;
+    gl_FragColor.b = u == 2147483647 ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test --int with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PreDecrementIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    int r0 = u;
+    int r1 = --r0;
+
+    gl_FragColor.r = r0 == 2147483648 ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == 2147483648 ? 1.0 : 0.0;
+    gl_FragColor.b = u == -2147483647 ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, -2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test int-- with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PostDecrementIntOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    int r0 = u;
+    int r1 = r0--;
+
+    gl_FragColor.r = r0 == 2147483648 ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == -2147483647 ? 1.0 : 0.0;
+    gl_FragColor.b = u == -2147483647 ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, -2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test ++ivec with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PreIncrementIvecOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform ivec4 u;
+void main() {
+    ivec4 r0 = u;
+    ivec4 r1 = ++r0;
+
+    gl_FragColor.r = r0 == ivec4(1, 2, 3, -2147483648) ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == ivec4(1, 2, 3, -2147483648) ? 1.0 : 0.0;
+    gl_FragColor.b = u == ivec4(0, 1, 2, 2147483647) ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4i(u, 0, 1, 2, 2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+// Test ivec++ with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PostIncrementIvecOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform ivec4 u;
+void main() {
+    ivec4 r0 = u;
+    ivec4 r1 = r0++;
+
+    gl_FragColor.r = r0 == ivec4(1, 2, 3, -2147483648) ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == ivec4(0, 1, 2, 2147483647) ? 1.0 : 0.0;
+    gl_FragColor.b = u == ivec4(0, 1, 2, 2147483647) ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4i(u, 0, 1, 2, 2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test --ivec with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PreDecrementIvecOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform ivec4 u;
+void main() {
+    ivec4 r0 = u;
+    ivec4 r1 = --r0;
+
+    gl_FragColor.r = r0 == ivec4(-1, 0, 1, 2147483648) ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == ivec4(-1, 0, 1, 2147483648) ? 1.0 : 0.0;
+    gl_FragColor.b = u == ivec4(0, 1, 2, -2147483647) ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4i(u, 0, 1, 2, -2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test ivec-- with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PostDecrementIvecOverflow)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform ivec4 u;
+void main() {
+    ivec4 r0 = u;
+    ivec4 r1 = r0--;
+
+    gl_FragColor.r = r0 == ivec4(-1, 0, 1, 2147483648) ? 1.0 : 0.0;
+    gl_FragColor.g = r1 == ivec4(0, 1, 2, -2147483647) ? 1.0 : 0.0;
+    gl_FragColor.b = u == ivec4(0, 1, 2, -2147483647) ? 1.0 : 0.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform4i(u, 0, 1, 2, -2147483647);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test int++ with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PostIncrementIntOverflowInForDynamic)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+uniform int u;
+void main() {
+    int z = 0;
+    for (int i = u; i > 4; i++) {
+        z++;
+    }
+    gl_FragColor.r = z == 7 ? 1.0 : 0.0;
+    gl_FragColor.g = u == 2147483641 ? 1.0 : 0.0;
+    gl_FragColor.b = 1.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    GLint u = glGetUniformLocation(testProgram, "u");
+    EXPECT_NE(-1, u);
+    glUniform1i(u, 2147483641);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test int++ with overflow. Expect wraparound.
+TEST_P(GLSLUBTest, PostIncrementIntOverflowInForStatic)
+{
+    constexpr char kFS[] = R"(
+precision highp int;
+precision highp float;
+void main() {
+    int z = 0;
+    for (int i = 2147483642; i > 4; i++) {
+        z++;
+    }
+    gl_FragColor.r = z == 6 ? 1.0 : 0.0;
+    gl_FragColor.g = 1.0;
+    gl_FragColor.b = 1.0;
+    gl_FragColor.a = 1.0;
+}
+)";
+    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(testProgram);
+    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 255, 255));
+    ASSERT_GL_NO_ERROR();
+}
+
+}  // anonymous namespace
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLUBTest);
+ANGLE_INSTANTIATE_TEST(GLSLUBTest, ES2_METAL(), ES3_METAL());
diff --git a/src/tests/gl_tests/ImageTestMetal.mm b/src/tests/gl_tests/ImageTestMetal.mm
index 170879bf90a74256ced4f403eb8895705592f6ed..28141cb7dcad9ebd2d76ad98d8265dfcf869158e 100644
--- a/src/tests/gl_tests/ImageTestMetal.mm
+++ b/src/tests/gl_tests/ImageTestMetal.mm
@@ -15,7 +15,6 @@
 
 #include <CoreFoundation/CoreFoundation.h>
 #include <Metal/Metal.h>
-#include <gmock/gmock.h>
 #include <span>
 
 namespace angle
diff --git a/src/tests/gl_tests/PbufferTest.cpp b/src/tests/gl_tests/PbufferTest.cpp
index 4a3409a407b16df54f085a5ccc3d63fb6f808a9f..dead2f816e3fb7f93eb5dce24d9491b752884fcd 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/PixelLocalStorageTest.cpp b/src/tests/gl_tests/PixelLocalStorageTest.cpp
index 795d843d5604e56f73e735b280d164cd11473b2c..8bc8040cb4ce38861493ed3c5990cfd7e172b254 100644
--- a/src/tests/gl_tests/PixelLocalStorageTest.cpp
+++ b/src/tests/gl_tests/PixelLocalStorageTest.cpp
@@ -6,6 +6,7 @@
 
 #include <sstream>
 #include <string>
+#include "common/string_utils.h"
 #include "test_utils/ANGLETest.h"
 #include "test_utils/gl_raii.h"
 
@@ -8557,7 +8558,6 @@ TEST_P(PixelLocalStorageCompilerTest, BlendEquationAdvanced_illegal_with_PLS)
     layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;)";
     EXPECT_TRUE(log.compileFragmentShader(kRequireBlendAdvanced));
 
-    bool before = true;
     for (const char *layoutQualifier : {
              "blend_support_multiply",
              "blend_support_screen",
@@ -8577,44 +8577,34 @@ TEST_P(PixelLocalStorageCompilerTest, BlendEquationAdvanced_illegal_with_PLS)
              "blend_support_all_equations",
          })
     {
-        constexpr char kRequireBlendAdvancedBeforePLS[] = R"(#version 300 es
-        #extension GL_ANGLE_shader_pixel_local_storage : require
-        #extension GL_KHR_blend_equation_advanced : require
-        layout(%s) out;
-        void main()
-        {}
-        layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;)";
-
-        constexpr char kRequireBlendAdvancedAfterPLS[] = R"(#version 300 es
-        #extension GL_ANGLE_shader_pixel_local_storage : require
-        #extension GL_KHR_blend_equation_advanced : require
-        layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;
-        layout(%s) out;
-        void main()
-        {})";
+        std::string shaderBefore = R"(#version 300 es
+#extension GL_ANGLE_shader_pixel_local_storage : require
+#extension GL_KHR_blend_equation_advanced : require
+layout(%s) out;
+void main()
+{}
+layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;)";
+        ReplaceSubstring(&shaderBefore, "%s", layoutQualifier);
 
-        const char *formatStr =
-            before ? kRequireBlendAdvancedBeforePLS : kRequireBlendAdvancedAfterPLS;
-        size_t buffSize =
-            snprintf(nullptr, 0, formatStr, layoutQualifier) + 1;  // Extra space for '\0'
-        std::unique_ptr<char[]> shader(new char[buffSize]);
-        std::snprintf(shader.get(), buffSize, formatStr, layoutQualifier);
-        EXPECT_FALSE(log.compileFragmentShader(shader.get()));
-        if (before)
-        {
+        EXPECT_FALSE(log.compileFragmentShader(shaderBefore.c_str()));
         EXPECT_TRUE(
             log.has("ERROR: 0:4: 'layout' : illegal advanced blend equation when pixel local "
                     "storage is declared"));
-        }
-        else
-        {
+
+        std::string shaderAfter = R"(#version 300 es
+#extension GL_ANGLE_shader_pixel_local_storage : require
+#extension GL_KHR_blend_equation_advanced : require
+layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;
+layout(%s) out;
+void main()
+{})";
+        ReplaceSubstring(&shaderAfter, "%s", layoutQualifier);
+
+        EXPECT_FALSE(log.compileFragmentShader(shaderAfter.c_str()));
         EXPECT_TRUE(
             log.has("ERROR: 0:5: 'layout' : illegal advanced blend equation when pixel local "
                     "storage is declared"));
     }
-
-        before = !before;
-    }
 }
 
 // Check proper validation of PLS function arguments.
diff --git a/src/tests/gl_tests/ProvokingVertexTest.cpp b/src/tests/gl_tests/ProvokingVertexTest.cpp
index 739499b04e28e44f9b771d3cf21aa7f785e88df8..db2e5a6b328a1ee84cdb1eb92b30b546729ccd42 100644
--- a/src/tests/gl_tests/ProvokingVertexTest.cpp
+++ b/src/tests/gl_tests/ProvokingVertexTest.cpp
@@ -12,7 +12,6 @@
 #include "GLES2/gl2.h"
 #include "test_utils/ANGLETest.h"
 #include "test_utils/gl_raii.h"
-#include "util/gles_loader_autogen.h"
 
 using namespace angle;
 
diff --git a/src/tests/gl_tests/RobustResourceInitTest.cpp b/src/tests/gl_tests/RobustResourceInitTest.cpp
index a097f23a30c54da4912d5dc6029aeeb38a5e36b4..b635b5d6bd7707a87db2ccc8651ef42b6115037f 100644
--- a/src/tests/gl_tests/RobustResourceInitTest.cpp
+++ b/src/tests/gl_tests/RobustResourceInitTest.cpp
@@ -9,7 +9,6 @@
 
 #include "test_utils/gl_raii.h"
 #include "util/EGLWindow.h"
-#include "util/gles_loader_autogen.h"
 
 namespace angle
 {
diff --git a/src/tests/gl_tests/SamplersTest.cpp b/src/tests/gl_tests/SamplersTest.cpp
index bdd785436212f7ededd0b36be63a1b2b56de54dc..501a3f10c75339f6833e6fc0c652958ad84e0626 100644
--- a/src/tests/gl_tests/SamplersTest.cpp
+++ b/src/tests/gl_tests/SamplersTest.cpp
@@ -11,7 +11,6 @@
 
 #include "test_utils/angle_test_configs.h"
 #include "test_utils/gl_raii.h"
-#include "util/gles_loader_autogen.h"
 #include "util/shader_utils.h"
 
 namespace angle
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index f8ca86a5eb20afca87e09329bbe575439be0041b..2dfc69b94b8085785ee0afe7cd8a0183d026eb17 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -2001,6 +2001,7 @@ void Texture2DDepthStencilTestES3::TestSampleWithDepthStencilMode(GLenum format,
             break;
         default:
             UNREACHABLE();
+            return;
     }
 
     // Set up a color texture.
@@ -10859,7 +10860,7 @@ class TextureLimitsTest : public ANGLETest<>
         initTextures(vertexTextureCount + fragmentTextureCount, 0);
 
         glUseProgram(mProgram);
-        RGBA8 expectedSum = {0};
+        RGBA8 expectedSum{};
         for (GLint texIndex = 0; texIndex < vertexTextureCount; ++texIndex)
         {
             std::stringstream uniformNameStr;
diff --git a/src/tests/gl_tests/TransformFeedbackTest.cpp b/src/tests/gl_tests/TransformFeedbackTest.cpp
index 1b1a168949a1a9b573895f7b82b8fdc2bdb9b115..063053a732cd589db8738fbbccd6ff916d26610c 100644
--- a/src/tests/gl_tests/TransformFeedbackTest.cpp
+++ b/src/tests/gl_tests/TransformFeedbackTest.cpp
@@ -7,7 +7,6 @@
 #include "test_utils/ANGLETest.h"
 #include "test_utils/gl_raii.h"
 #include "util/EGLWindow.h"
-#include "util/gles_loader_autogen.h"
 #include "util/random_utils.h"
 #include "util/test_utils.h"
 
diff --git a/src/tests/gl_tests/UniformTest.cpp b/src/tests/gl_tests/UniformTest.cpp
index 1615b1393cf425c42b3bfd5f6e9ff2a4c042205e..80b7bc85b60e1fc1f2889ffc0e0a9902a6c888df 100644
--- a/src/tests/gl_tests/UniformTest.cpp
+++ b/src/tests/gl_tests/UniformTest.cpp
@@ -9,7 +9,6 @@
 #include "test_utils/angle_test_configs.h"
 #include "test_utils/angle_test_instantiate.h"
 #include "test_utils/gl_raii.h"
-#include "util/gles_loader_autogen.h"
 #include "util/shader_utils.h"
 
 #include <array>
@@ -2389,7 +2388,7 @@ TEST_P(UniformTestES3, MatrixUniformUpload)
         matrixValues[i] = static_cast<GLfloat>(i);
     }
 
-    using UniformMatrixCxRfv = decltype(glUniformMatrix2fv);
+    using UniformMatrixCxRfv = GL_APIENTRY void (*)(GLint, GLsizei, GLboolean, const GLfloat *);
     UniformMatrixCxRfv uniformMatrixCxRfv[kMaxDims + 1][kMaxDims + 1] = {
         {nullptr, nullptr, nullptr, nullptr, nullptr},
         {nullptr, nullptr, nullptr, nullptr, nullptr},
diff --git a/third_party/re2/BUILD.gn b/third_party/re2/BUILD.gn
deleted file mode 100644
index da398564a23289504620f60beef8d0270d797642..0000000000000000000000000000000000000000
--- a/third_party/re2/BUILD.gn
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright 2014 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//testing/libfuzzer/fuzzer_test.gni")
-
-config("re2_config") {
-  include_dirs = [ "src" ]
-}
-
-static_library("re2") {
-  sources = [
-    "src/re2/bitmap256.cc",
-    "src/re2/bitmap256.h",
-    "src/re2/bitstate.cc",
-    "src/re2/compile.cc",
-    "src/re2/dfa.cc",
-    "src/re2/filtered_re2.cc",
-    "src/re2/filtered_re2.h",
-    "src/re2/mimics_pcre.cc",
-    "src/re2/nfa.cc",
-    "src/re2/onepass.cc",
-    "src/re2/parse.cc",
-    "src/re2/perl_groups.cc",
-    "src/re2/prefilter.cc",
-    "src/re2/prefilter.h",
-    "src/re2/prefilter_tree.cc",
-    "src/re2/prefilter_tree.h",
-    "src/re2/prog.cc",
-    "src/re2/prog.h",
-    "src/re2/re2.cc",
-    "src/re2/re2.h",
-    "src/re2/regexp.cc",
-    "src/re2/regexp.h",
-    "src/re2/set.cc",
-    "src/re2/set.h",
-    "src/re2/simplify.cc",
-    "src/re2/sparse_array.h",
-    "src/re2/sparse_set.h",
-    "src/re2/stringpiece.h",
-    "src/re2/tostring.cc",
-    "src/re2/unicode_casefold.cc",
-    "src/re2/unicode_casefold.h",
-    "src/re2/unicode_groups.cc",
-    "src/re2/unicode_groups.h",
-    "src/re2/walker-inl.h",
-    "src/util/rune.cc",
-    "src/util/strutil.cc",
-    "src/util/strutil.h",
-    "src/util/utf.h",
-  ]
-
-  configs -= [ "//build/config/compiler:chromium_code" ]
-  configs += [ "//build/config/compiler:no_chromium_code" ]
-  public_configs = [ ":re2_config" ]
-  public_deps = [ "//third_party/abseil-cpp:absl" ]
-}
-
-fuzzer_test("third_party_re2_fuzzer") {
-  sources = [ "src/re2/fuzzing/re2_fuzzer.cc" ]
-  deps = [ ":re2" ]
-}
diff --git a/third_party/re2/DEPS b/third_party/re2/DEPS
deleted file mode 100644
index 82c266c5b6241311da0f83a433d0364f32fc514c..0000000000000000000000000000000000000000
--- a/third_party/re2/DEPS
+++ /dev/null
@@ -1,7 +0,0 @@
-include_rules = [
-  '+base',
-  '+build',
-  '+re2',
-  '+utest',
-  '+util',
-]
diff --git a/third_party/re2/DIR_METADATA b/third_party/re2/DIR_METADATA
deleted file mode 100644
index 45f7798a6860a53fdfe22765e9d0bee9e1d67678..0000000000000000000000000000000000000000
--- a/third_party/re2/DIR_METADATA
+++ /dev/null
@@ -1,6 +0,0 @@
-monorail: {
-  component: "Internals"
-}
-buganizer_public: {
-  component_id: 1456292
-}
diff --git a/third_party/re2/LICENSE b/third_party/re2/LICENSE
deleted file mode 100644
index 09e5ec1c74c187adc8fde6c74308c3492ef31f77..0000000000000000000000000000000000000000
--- a/third_party/re2/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2009 The RE2 Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//    * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//    * 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.
-//    * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
-// OWNER OR 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.
diff --git a/third_party/re2/OWNERS b/third_party/re2/OWNERS
deleted file mode 100644
index e542f4adc50dc2cfa81fc6a1db6f604959eb4ce6..0000000000000000000000000000000000000000
--- a/third_party/re2/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-junyer@chromium.org
-thakis@chromium.org
diff --git a/third_party/re2/README.chromium b/third_party/re2/README.chromium
deleted file mode 100644
index e4e6ef77acc545582c9603b956c53ccfb8472b8c..0000000000000000000000000000000000000000
--- a/third_party/re2/README.chromium
+++ /dev/null
@@ -1,13 +0,0 @@
-Name: re2 - an efficient, principled regular expression library
-Short Name: re2
-URL: https://github.com/google/re2
-Version: 1e44e72d31ddc66b783a545e9d9fcaa876a146b7
-Date: 2023-05-31
-License: BSD-3-Clause
-License File: LICENSE
-Security Critical: yes
-Shipped: yes
-
-Description:
-RE2 is a fast, safe, thread-friendly alternative to backtracking regular
-expression engines like those used in PCRE, Perl, and Python.
diff --git a/util/autogen/angle_features_autogen.cpp b/util/autogen/angle_features_autogen.cpp
index 9276c97e7a89b9fd2e9f6f8937729ba85b599672..459af22a1285953a732d7603a7393324f2825a20 100644
--- a/util/autogen/angle_features_autogen.cpp
+++ b/util/autogen/angle_features_autogen.cpp
@@ -210,6 +210,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 423f33ea0da9936e26c0cd79470250c59f6d5cb7..1e724f1d4505b55ed973066164ac6c44ffedf961 100644
--- a/util/autogen/angle_features_autogen.h
+++ b/util/autogen/angle_features_autogen.h
@@ -210,6 +210,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 d963c6dbbdb39003c44ee97996593c90e479516b..7f4bf9d219ffb13fc1b78bc7c220fb48fb3e6e7d 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.GLuintVal, captures[1].value.GLMTLRasterizationRateMapANGLEVal);
+            break;
         case angle::EntryPoint::GLBindProgramPipeline:
             glBindProgramPipeline(gProgramPipelineMap[captures[0].value.GLuintVal]);
             break;
@@ -1055,6 +1059,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::GLFramebufferShadingRateEXT:
             glFramebufferShadingRateEXT(captures[0].value.GLenumVal, captures[1].value.GLenumVal,
                                         captures[2].value.GLuintVal, captures[3].value.GLintVal,
diff --git a/util/capture/trace_gles_loader_autogen.cpp b/util/capture/trace_gles_loader_autogen.cpp
index 75fe6e0b4b9ebab89464b249ff6a2addfde93727..2f317f49ec0b0628407913ca33ba25de6501df1c 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;
@@ -885,6 +887,8 @@ ANGLE_TRACE_LOADER_EXPORT PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC
     t_glTextureFoveationParametersQCOM;
 ANGLE_TRACE_LOADER_EXPORT PFNGLENDTILINGQCOMPROC t_glEndTilingQCOM;
 ANGLE_TRACE_LOADER_EXPORT PFNGLSTARTTILINGQCOMPROC t_glStartTilingQCOM;
+ANGLE_TRACE_LOADER_EXPORT PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    t_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_TRACE_LOADER_EXPORT PFNGLBLENDEQUATIONOESPROC t_glBlendEquationOES;
 ANGLE_TRACE_LOADER_EXPORT PFNGLDRAWTEXFOESPROC t_glDrawTexfOES;
 ANGLE_TRACE_LOADER_EXPORT PFNGLDRAWTEXFVOESPROC t_glDrawTexfvOES;
@@ -1856,6 +1860,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 =
@@ -2294,6 +2301,9 @@ void LoadTraceGLES(LoadProc loadProc)
         loadProc("glTextureFoveationParametersQCOM"));
     t_glEndTilingQCOM   = reinterpret_cast<PFNGLENDTILINGQCOMPROC>(loadProc("glEndTilingQCOM"));
     t_glStartTilingQCOM = reinterpret_cast<PFNGLSTARTTILINGQCOMPROC>(loadProc("glStartTilingQCOM"));
+    t_glFramebufferResolveRenderbufferWEBKIT =
+        reinterpret_cast<PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC>(
+            loadProc("glFramebufferResolveRenderbufferWEBKIT"));
     t_glBlendEquationOES =
         reinterpret_cast<PFNGLBLENDEQUATIONOESPROC>(loadProc("glBlendEquationOES"));
     t_glDrawTexfOES  = reinterpret_cast<PFNGLDRAWTEXFOESPROC>(loadProc("glDrawTexfOES"));
diff --git a/util/capture/trace_gles_loader_autogen.h b/util/capture/trace_gles_loader_autogen.h
index a86bd89a09353c60e90f99177846ceb78e29a545..16a7f95f8c8fa01ce1220247fca9e046a358bb76 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
@@ -834,6 +835,7 @@
 #define glTextureFoveationParametersQCOM t_glTextureFoveationParametersQCOM
 #define glEndTilingQCOM t_glEndTilingQCOM
 #define glStartTilingQCOM t_glStartTilingQCOM
+#define glFramebufferResolveRenderbufferWEBKIT t_glFramebufferResolveRenderbufferWEBKIT
 #define glBlendEquationOES t_glBlendEquationOES
 #define glDrawTexfOES t_glDrawTexfOES
 #define glDrawTexfvOES t_glDrawTexfvOES
@@ -1525,6 +1527,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
@@ -1795,6 +1799,8 @@ ANGLE_TRACE_LOADER_EXPORT extern PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC
     t_glTextureFoveationParametersQCOM;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLENDTILINGQCOMPROC t_glEndTilingQCOM;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLSTARTTILINGQCOMPROC t_glStartTilingQCOM;
+ANGLE_TRACE_LOADER_EXPORT extern PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    t_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLBLENDEQUATIONOESPROC t_glBlendEquationOES;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLDRAWTEXFOESPROC t_glDrawTexfOES;
 ANGLE_TRACE_LOADER_EXPORT extern PFNGLDRAWTEXFVOESPROC t_glDrawTexfvOES;
diff --git a/util/capture/trace_interpreter_autogen.cpp b/util/capture/trace_interpreter_autogen.cpp
index a8b3cecd00b54719a623bb4436ff8052950f67de..cbaceed2fe9f440dd5ea3968f3489c792016b53e 100644
--- a/util/capture/trace_interpreter_autogen.cpp
+++ b/util/capture/trace_interpreter_autogen.cpp
@@ -910,6 +910,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 =
@@ -2310,6 +2317,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, "glFramebufferShadingRateEXT") == 0)
     {
         ParamBuffer params =
diff --git a/util/gles_loader_autogen.cpp b/util/gles_loader_autogen.cpp
index eb645669d37a83516f6d7fe5dffc1fb856809b2b..54024068b0061eb92d72abb419517a46583b4d81 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;
@@ -854,6 +856,8 @@ ANGLE_UTIL_EXPORT PFNGLSHADINGRATEQCOMPROC l_glShadingRateQCOM;
 ANGLE_UTIL_EXPORT PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC l_glTextureFoveationParametersQCOM;
 ANGLE_UTIL_EXPORT PFNGLENDTILINGQCOMPROC l_glEndTilingQCOM;
 ANGLE_UTIL_EXPORT PFNGLSTARTTILINGQCOMPROC l_glStartTilingQCOM;
+ANGLE_UTIL_EXPORT PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    l_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_UTIL_EXPORT PFNGLBLENDEQUATIONOESPROC l_glBlendEquationOES;
 ANGLE_UTIL_EXPORT PFNGLDRAWTEXFOESPROC l_glDrawTexfOES;
 ANGLE_UTIL_EXPORT PFNGLDRAWTEXFVOESPROC l_glDrawTexfvOES;
@@ -1824,6 +1828,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 =
@@ -2262,6 +2269,9 @@ void LoadUtilGLES(LoadProc loadProc)
         loadProc("glTextureFoveationParametersQCOM"));
     l_glEndTilingQCOM   = reinterpret_cast<PFNGLENDTILINGQCOMPROC>(loadProc("glEndTilingQCOM"));
     l_glStartTilingQCOM = reinterpret_cast<PFNGLSTARTTILINGQCOMPROC>(loadProc("glStartTilingQCOM"));
+    l_glFramebufferResolveRenderbufferWEBKIT =
+        reinterpret_cast<PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC>(
+            loadProc("glFramebufferResolveRenderbufferWEBKIT"));
     l_glBlendEquationOES =
         reinterpret_cast<PFNGLBLENDEQUATIONOESPROC>(loadProc("glBlendEquationOES"));
     l_glDrawTexfOES  = reinterpret_cast<PFNGLDRAWTEXFOESPROC>(loadProc("glDrawTexfOES"));
diff --git a/util/gles_loader_autogen.h b/util/gles_loader_autogen.h
index ebf31ebca3dbece1e2ced9e3900c1bdd5952972b..7b5a493ebb9c246528730198bfa21636dfbf4921 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
@@ -834,6 +835,7 @@
 #define glTextureFoveationParametersQCOM l_glTextureFoveationParametersQCOM
 #define glEndTilingQCOM l_glEndTilingQCOM
 #define glStartTilingQCOM l_glStartTilingQCOM
+#define glFramebufferResolveRenderbufferWEBKIT l_glFramebufferResolveRenderbufferWEBKIT
 #define glBlendEquationOES l_glBlendEquationOES
 #define glDrawTexfOES l_glDrawTexfOES
 #define glDrawTexfvOES l_glDrawTexfvOES
@@ -1490,6 +1492,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;
@@ -1736,6 +1740,8 @@ ANGLE_UTIL_EXPORT extern PFNGLSHADINGRATEQCOMPROC l_glShadingRateQCOM;
 ANGLE_UTIL_EXPORT extern PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC l_glTextureFoveationParametersQCOM;
 ANGLE_UTIL_EXPORT extern PFNGLENDTILINGQCOMPROC l_glEndTilingQCOM;
 ANGLE_UTIL_EXPORT extern PFNGLSTARTTILINGQCOMPROC l_glStartTilingQCOM;
+ANGLE_UTIL_EXPORT extern PFNGLFRAMEBUFFERRESOLVERENDERBUFFERWEBKITPROC
+    l_glFramebufferResolveRenderbufferWEBKIT;
 ANGLE_UTIL_EXPORT extern PFNGLBLENDEQUATIONOESPROC l_glBlendEquationOES;
 ANGLE_UTIL_EXPORT extern PFNGLDRAWTEXFOESPROC l_glDrawTexfOES;
 ANGLE_UTIL_EXPORT extern PFNGLDRAWTEXFVOESPROC l_glDrawTexfvOES;
diff --git a/util/ios/ios_main.mm b/util/ios/ios_main.mm
index 6df44200413599cbee17741ac8d74380f694bdff..1c8cdb539081b41661c1d96b9058969fcba8ecc0 100644
--- a/util/ios/ios_main.mm
+++ b/util/ios/ios_main.mm
@@ -12,9 +12,9 @@
 #include <stdio.h>
 
 static int original_argc;
-static char **original_argv;
+static char *_Nullable *_Nullable original_argv = nullptr;
 
-int main(int argc, char **argv);
+int main(int argc, char *_Nullable *_Nullable argv);
 
 @interface AngleUtilAppDelegate : UIResponder <UIApplicationDelegate>
 
@@ -31,8 +31,8 @@ int main(int argc, char **argv);
     exit(main(original_argc, original_argv));
 }
 
-- (BOOL)application:(UIApplication *)application
-    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+- (BOOL)application:(UIApplication *_Nonnull)application
+    didFinishLaunchingWithOptions:(NSDictionary *_Nullable)launchOptions
 {
     self.window                    = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
     self.window.rootViewController = [[UIViewController alloc] initWithNibName:nil bundle:nil];
@@ -49,7 +49,7 @@ int main(int argc, char **argv);
 
 @end
 
-extern "C" int ios_main(int argc, char **argv)
+extern "C" int ios_main(int argc, char *_Nonnull *_Nonnull argv)
 {
     original_argc = argc;
     original_argv = argv;
diff --git a/util/posix/test_utils_posix.cpp b/util/posix/test_utils_posix.cpp
index 9432ace6388e2349dfe8178cca9ebd8975fc1153..b6146ceec4a2d1f8e38b9d2e664074dbe604c563 100644
--- a/util/posix/test_utils_posix.cpp
+++ b/util/posix/test_utils_posix.cpp
@@ -356,7 +356,9 @@ void WriteDebugMessage(const char *format, ...)
 {
     va_list vararg;
     va_start(vararg, format);
+ANGLE_DISABLE_NONLITERAL_FORMAT_WARNING
     vfprintf(stderr, format, vararg);
+ANGLE_REENABLE_NONLITERAL_FORMAT_WARNING
     va_end(vararg);
 }
 
