diff --git a/doc/ExtensionSupport.md b/doc/ExtensionSupport.md
index 128feb6dc8e8f54700c347773a7482b8b857495c..0e7c26b19dca917879380ca46337f6f5b67a1191 100644
--- a/doc/ExtensionSupport.md
+++ b/doc/ExtensionSupport.md
@@ -235,6 +235,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; |
@@ -271,6 +272,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/ANGLE_shader_preprocessor.json b/scripts/code_generation_hashes/ANGLE_shader_preprocessor.json
index de0ca8f77984dacc4db9aa7b3600ab517648a70f..08f8365da45eba2fd96a637ba818ae640222e422 100644
--- a/scripts/code_generation_hashes/ANGLE_shader_preprocessor.json
+++ b/scripts/code_generation_hashes/ANGLE_shader_preprocessor.json
@@ -1,6 +1,6 @@
 {
   "src/compiler/generate_parser_tools.py":
-    "bb5b9d7e3b890ad55fe9dba470cd86fe",
+    "b2d9f7155fabd2391545e23d96b8094f",
   "src/compiler/preprocessor/generate_parser.py":
     "9a4588fdf009298fe49c52b9252789c7",
   "src/compiler/preprocessor/preprocessor.l":
@@ -10,7 +10,7 @@
   "src/compiler/preprocessor/preprocessor_lex_autogen.cpp":
     "c58edcc71ed87ab9613539a3acd5e194",
   "src/compiler/preprocessor/preprocessor_tab_autogen.cpp":
-    "000f93c6b244dcae862efa319a737a7f",
+    "ff3120300799cd96965e4c424b2d2ffb",
   "tools/flex-bison/linux/bison.sha1":
     "dfc9209e0c76eddd9bed0601c6c189e5",
   "tools/flex-bison/linux/flex.sha1":
diff --git a/scripts/code_generation_hashes/ANGLE_shader_translator.json b/scripts/code_generation_hashes/ANGLE_shader_translator.json
index bd722e0a9f9a24072e292d1f31cc8aa1190c35f0..25a972ec41f7443c497904038bc6410c31cb77f6 100644
--- a/scripts/code_generation_hashes/ANGLE_shader_translator.json
+++ b/scripts/code_generation_hashes/ANGLE_shader_translator.json
@@ -1,6 +1,6 @@
 {
   "src/compiler/generate_parser_tools.py":
-    "bb5b9d7e3b890ad55fe9dba470cd86fe",
+    "b2d9f7155fabd2391545e23d96b8094f",
   "src/compiler/translator/generate_parser.py":
     "ad919972a040d9b3b4aa5dc547fadc75",
   "src/compiler/translator/glslang.l":
@@ -10,9 +10,9 @@
   "src/compiler/translator/glslang_lex_autogen.cpp":
     "14c0f0593c173936c5be7f87a05a20b9",
   "src/compiler/translator/glslang_tab_autogen.cpp":
-    "b3a90dde9dea633233d929586571a487",
+    "217d186699b4fdd8a688137526c84322",
   "src/compiler/translator/glslang_tab_autogen.h":
-    "028bdaebf359aefbcdaafae466993ebe",
+    "11aef96205adbc2809c6652e099ccaa7",
   "tools/flex-bison/linux/bison.sha1":
     "dfc9209e0c76eddd9bed0601c6c189e5",
   "tools/flex-bison/linux/flex.sha1":
diff --git a/scripts/code_generation_hashes/Extension_files.json b/scripts/code_generation_hashes/Extension_files.json
index b745ed3e6af1eaa9104284e11b70587737b70b69..6c1a743f4cd137c4c547acda47937f2d000d9b19 100644
--- a/scripts/code_generation_hashes/Extension_files.json
+++ b/scripts/code_generation_hashes/Extension_files.json
@@ -1,6 +1,6 @@
 {
   "doc/ExtensionSupport.md":
-    "6a19f6bfb8a9b60fda0811822cc68982",
+    "4d970d444b6f5b531a383aefb5348db1",
   "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":
-    "4824b51dafdbdbf6eb109a4d0c5b86b8",
+    "cf2238984047673faa30d75bdfc3f6e7",
   "src/libANGLE/gen_extensions.py":
     "dc4727460d1ece9f98a2ae47bf15ddb3",
   "src/libANGLE/gles_extensions_autogen.cpp":
-    "1fc44732ce9af3e33ebbf508748494c8",
+    "44adef75b9f3d699727fa1a589b3aa4c",
   "src/libANGLE/gles_extensions_autogen.h":
-    "256c663cc00d5ef2ad89536f263ef6ab",
+    "2178701d90b5cc3a37d7095b14697925",
   "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 e5e57532446fadaca63ead55d6cde7cc14b3e3d0..9ce61b4755792eb355f7d0176ff911caa182fdb1 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":
-    "4824b51dafdbdbf6eb109a4d0c5b86b8",
+    "cf2238984047673faa30d75bdfc3f6e7",
   "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/GL_EGL_entry_points.json b/scripts/code_generation_hashes/GL_EGL_entry_points.json
index 266f857f229d495559384ee1e02a0e258552c2c7..903ba3966142a3b971dfbcbee9993bcc5b1544c3 100644
--- a/scripts/code_generation_hashes/GL_EGL_entry_points.json
+++ b/scripts/code_generation_hashes/GL_EGL_entry_points.json
@@ -4,21 +4,21 @@
   "scripts/entry_point_packed_egl_enums.json":
     "a72ae855c6b403912103b519139951a1",
   "scripts/entry_point_packed_gl_enums.json":
-    "be374e5742cda78e3189bbeddf9ab6f7",
+    "8b8f78d43f213f3c52049f7d974d488d",
   "scripts/generate_entry_points.py":
-    "8fe4cc91467ea2f644923527aba3139f",
+    "7b4237598219fcbea82eebb8c9a5dbbc",
   "scripts/gl_angle_ext.xml":
-    "da4ecccdd77635f1b0e9d4664f856706",
+    "2cba159af592da788a15449eae941cb4",
   "scripts/registry_xml.py":
-    "4824b51dafdbdbf6eb109a4d0c5b86b8",
+    "cf2238984047673faa30d75bdfc3f6e7",
   "src/common/entry_points_enum_autogen.cpp":
-    "0b8ff1d91b8893bcdbf9fdc4fd0f79d8",
+    "4f3df9ac717cbee53bedf3c271917b92",
   "src/common/entry_points_enum_autogen.h":
-    "50d36abe85f61158cf0dba3affbcfc74",
+    "2aacb58b77c4e8c07de6ebbda3507325",
   "src/common/frame_capture_utils_autogen.cpp":
-    "740c827e5fa32ac4b6bce98a9225907e",
+    "3db48981ed6af73bf8ba4c4c269df74d",
   "src/common/frame_capture_utils_autogen.h":
-    "92492e0b08610fe09af507a3075fe203",
+    "7175e2b92f1a3d9b9819d83ca4228bbe",
   "src/libANGLE/Context_gles_1_0_autogen.h":
     "cb1cfe652972d301a5a98b4f233fcc4f",
   "src/libANGLE/Context_gles_2_0_autogen.h":
@@ -30,7 +30,7 @@
   "src/libANGLE/Context_gles_3_2_autogen.h":
     "cf9900e0067812fc6773e8ae4664da33",
   "src/libANGLE/Context_gles_ext_autogen.h":
-    "7c55a082377ba3dc7eb738e67bd33e1a",
+    "5dd6184e2a1c14157de92a257acc4e8c",
   "src/libANGLE/capture/capture_cl_autogen.cpp":
     "e07bd3ced621b3c2c23da910ef912036",
   "src/libANGLE/capture/capture_cl_autogen.h":
@@ -60,9 +60,9 @@
   "src/libANGLE/capture/capture_gles_3_2_autogen.h":
     "5e956a19d9e3ba697fb05049c4be9936",
   "src/libANGLE/capture/capture_gles_ext_autogen.cpp":
-    "bbd218a49ad64826c2c0f21172b0b2d6",
+    "d5f6b37d76c3ab5c64550aa86d74e034",
   "src/libANGLE/capture/capture_gles_ext_autogen.h":
-    "546b398a83720dd0b4cc363ab83eadd1",
+    "dd99ae9ceb99a114d422e957774a51ab",
   "src/libANGLE/context_private_call_autogen.h":
     "b12e4b57d792fda9b12600082d6d960f",
   "src/libANGLE/validationCL_autogen.h":
@@ -80,7 +80,7 @@
   "src/libANGLE/validationES3_autogen.h":
     "8d5b4de6479691eb63a687246491182f",
   "src/libANGLE/validationESEXT_autogen.h":
-    "835bfb5c77246922435f5f0e6c47ea2c",
+    "323b194412b5efda9f38efb3a442e4da",
   "src/libEGL/libEGL_autogen.cpp":
     "77d82db4a45c2f08108929ef7fe8b698",
   "src/libEGL/libEGL_autogen.def":
@@ -130,19 +130,19 @@
   "src/libGLESv2/entry_points_gles_3_2_autogen.h":
     "647f932a299cdb4726b60bbba059f0d2",
   "src/libGLESv2/entry_points_gles_ext_autogen.cpp":
-    "29916b3f787723badd54fe4cc36b7ebf",
+    "6a7f9f0a47c9d375471332f2acac76d3",
   "src/libGLESv2/entry_points_gles_ext_autogen.h":
-    "5b573c79cc90490671b76f4e7a337209",
+    "2caecc2adf08a3576d104985f4b15e30",
   "src/libGLESv2/libGLESv2_autogen.cpp":
-    "7122ecb20b43aa3c64ff4c328b2cc57b",
+    "8e377deee26cd73f110779bd78b8c1aa",
   "src/libGLESv2/libGLESv2_autogen.def":
-    "45a89ae956e11715533654d16d15ffad",
+    "bad23ed8d95bd2f5d90c5f7af266c5e3",
   "src/libGLESv2/libGLESv2_no_capture_autogen.def":
-    "23ca160e2450347f2db2ef6b94a6c578",
+    "6aee07f80f6c6d5f3eef22f027f9ebb3",
   "src/libGLESv2/libGLESv2_vulkan_secondaries_autogen.def":
-    "1cee6dd4fa82e361586e4c691cc057d6",
+    "ae881b1b6622aa0d35c45c4e15f618fb",
   "src/libGLESv2/libGLESv2_with_capture_autogen.def":
-    "b91e41c6c54beedabbefeb8aece0e192",
+    "c853232047a646e25d7da78fa74fa6ce",
   "src/libOpenCL/libOpenCL_autogen.cpp":
     "0248415990d5c99ba8e9d41aecd0e3ab",
   "third_party/EGL-Registry/src/api/egl.xml":
@@ -156,5 +156,5 @@
   "third_party/OpenGL-Registry/src/xml/wgl.xml":
     "eae784bf4d1b983a42af5671b140b7c4",
   "util/capture/frame_capture_replay_autogen.cpp":
-    "281ce957461793303bc4b62efaf8e445"
+    "fd2c06f322d5abf1765261362fe3676f"
 }
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 1bd2d3e15061c82f435d9daeec495f25e29778b6..7c53f81b3ed31f78e2bd9858ecf76ab77f79743b 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":
-    "4824b51dafdbdbf6eb109a4d0c5b86b8",
+    "cf2238984047673faa30d75bdfc3f6e7",
   "src/common/gl_enum_utils_autogen.cpp":
-    "b411692252eaf6daad1917f8f5624fd7",
+    "3e9c6ac81b1279017d75d2b7d09c5352",
   "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 1a9feae5b1bbe1b66fde54c7e85356d8cdd8fc00..2d76d1d8e544c1d1c655e2205037615148e7ce31 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":
     "7c21cda140a45527c02ddd003ebe7914",
   "scripts/gl_angle_ext.xml":
-    "da4ecccdd77635f1b0e9d4664f856706",
+    "2cba159af592da788a15449eae941cb4",
   "scripts/registry_xml.py":
-    "4824b51dafdbdbf6eb109a4d0c5b86b8",
+    "cf2238984047673faa30d75bdfc3f6e7",
   "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":
     "c9183d0fbb0418e0630ff25f377cc822",
   "util/capture/trace_interpreter_autogen.cpp":
-    "bc405d620aae95dc997337872f4d94c0"
+    "7a1e6c76e485ecca695b7720a36e0b5c"
 }
diff --git a/scripts/code_generation_hashes/proc_table.json b/scripts/code_generation_hashes/proc_table.json
index b3e213de8c77d7f00ffdf5ad3de2c173d5142f52..189cae07d8b59b1f5944e4d9507a51cc80b84294 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":
-    "4824b51dafdbdbf6eb109a4d0c5b86b8",
+    "cf2238984047673faa30d75bdfc3f6e7",
   "src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp":
-    "d394fb57688dc5eb89c19aeae23d8bf3",
+    "e9b8cdb847756179dd2e6f5a1a534f2f",
   "src/libGLESv2/proc_table_cl_autogen.cpp":
     "f863172e26c6a463f6f23df217b23a48",
   "src/libOpenCL/libOpenCL_autogen.map":
diff --git a/scripts/entry_point_packed_gl_enums.json b/scripts/entry_point_packed_gl_enums.json
index 285c52f14ec233b3636194679c1d6b6f2f631933..4680e75892b8f118fc30e6fa3d5317dfd21603f7 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 1f45174cefd2237294a434403fae8dad6a67df6a..e3a6760f8cd51f73c975f1cab109e080e60351e9 100755
--- a/scripts/generate_entry_points.py
+++ b/scripts/generate_entry_points.py
@@ -1017,6 +1017,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 e70a9e42f1bcfc4a54791a8f45aebe0a1a48493d..144a9df4e795103f58bf45cfc98c6b153ac81777 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 a512771e918885deed5e0a1c226c2c9f86e88bfe..45b7526c48d7e03d734934f28c27fd3fc942cd7f 100644
--- a/src/common/PoolAlloc.cpp
+++ b/src/common/PoolAlloc.cpp
@@ -4,7 +4,7 @@
 // found in the LICENSE file.
 //
 // PoolAlloc.cpp:
-//    Implements the class methods for PoolAllocator and Allocation classes.
+//    Implements the PoolAllocator.
 //
 
 #ifdef UNSAFE_BUFFERS_BUILD
@@ -13,16 +13,10 @@
 
 #include "common/PoolAlloc.h"
 
-#include <assert.h>
 #include <stdint.h>
-#include <stdio.h>
-#include <utility>
 
-#include <utility>
-
-#include "common/mathutil.h"
+#include "common/aligned_memory.h"
 #include "common/platform.h"
-#include "common/tls.h"
 
 #if defined(ANGLE_WITH_ASAN)
 #    include <sanitizer/asan_interface.h>
@@ -30,395 +24,153 @@
 
 namespace angle
 {
-// If we are using guard blocks, we must track each individual allocation.  If we aren't using guard
-// blocks, these never get instantiated, so won't have any impact.
 
-class Allocation
-{
-  public:
-    Allocation(size_t size, unsigned char *mem, Allocation *prev = 0)
-        : mSize(size), mMem(mem), mPrevAlloc(prev)
-    {
-        // Allocations are bracketed:
-        //
-        //    [allocationHeader][initialGuardBlock][userData][finalGuardBlock]
-        //
-        // This would be cleaner with if (kGuardBlockSize)..., but that makes the compiler print
-        // warnings about 0 length memsets, even with the if() protecting them.
+// The PoolAllocator memory is aligned by starting with an aligned pointer
+// and reserving aligned size amount of memory.
+// This is, as opposed to aligning the current pointer and reserving the
+// exact amount.
+// The layout is:
+//   [client][pad][client][pad]...
+// With ANGLE_POOL_ALLOC_GUARD_BLOCKS, the layout is:
+//   [guard][client][pad/guard][guard][client][pad/guard]
+// ANGLE_POOL_ALLOC_GUARD_BLOCKS asserts that guards and pads are not overwritten
+// by the client.
+
 #if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
-        memset(preGuard(), kGuardBlockBeginVal, kGuardBlockSize);
-        memset(data(), kUserDataFill, mSize);
-        memset(postGuard(), kGuardBlockEndVal, kGuardBlockSize);
+constexpr uint8_t kGuardFillValue = 0xfe;
 #endif
-    }
-
-    void checkAllocList() const;
 
-    static size_t AlignedHeaderSize(uint8_t *allocationBasePtr, size_t alignment)
+class PoolAllocator::Segment
+{
+  public:
+    Segment() = default;
+    explicit Segment(Span<uint8_t> data) : mData(data) {}
+    Segment(Segment &&other) : mData(std::exchange(other.mData, {})) {}
+    ~Segment()
     {
-        // Make sure that the data offset after the header is aligned to the given alignment.
-        size_t base = reinterpret_cast<size_t>(allocationBasePtr);
-        return rx::roundUpPow2(base + kGuardBlockSize + HeaderSize(), alignment) - base;
-    }
-
-    // Return total size needed to accommodate user buffer of 'size',
-    // plus our tracking data and any necessary alignments.
-    static size_t AllocationSize(uint8_t *allocationBasePtr,
-                                 size_t size,
-                                 size_t alignment,
-                                 size_t *preAllocationPaddingOut)
+        uint8_t *data = mData.data();
+        if (data != nullptr)
         {
-        // The allocation will be laid out as such:
-        //
-        //                      Aligned to |alignment|
-        //                               ^
-        //   preAllocationPaddingOut     |
-        //        ___^___                |
-        //       /       \               |
-        //       <padding>[header][guard][data][guard]
-        //       \___________ __________/
-        //                   V
-        //              dataOffset
-        //
-        // Note that alignment is at least as much as a pointer alignment, so the pointers in the
-        // header are also necessarily aligned appropriately.
-        //
-        size_t dataOffset        = AlignedHeaderSize(allocationBasePtr, alignment);
-        *preAllocationPaddingOut = dataOffset - HeaderSize() - kGuardBlockSize;
-
-        return dataOffset + size + kGuardBlockSize;
+            size_t size = mData.size();
+            ANGLE_UNUSED_VARIABLE(size);
+            ANGLE_ALLOC_PROFILE(POOL_DEALLOCATION, data, size, size > kSegmentSize);
+            AlignedFree(data);
         }
-
-    // Given memory pointing to |header|, returns |data|.
-    static uint8_t *GetDataPointer(uint8_t *memory, size_t alignment)
+    }
+    Segment &operator=(Segment &&other)
     {
-        uint8_t *alignedPtr = memory + kGuardBlockSize + HeaderSize();
-
-        // |memory| must be aligned already such that user data is aligned to |alignment|.
-        ASSERT((reinterpret_cast<uintptr_t>(alignedPtr) & (alignment - 1)) == 0);
-
-        return alignedPtr;
+        mData = std::exchange(other.mData, {});
+        return *this;
     }
-
-  private:
-    void checkGuardBlock(unsigned char *blockMem, unsigned char val, const char *locText) const;
-
-    void checkAlloc() const
+    static Segment Allocate(size_t size)
+    {
+        ANGLE_ALLOC_PROFILE(POOL_ALLOCATION, size, size > kSegmentSize);
+        uint8_t *result = reinterpret_cast<uint8_t *>(AlignedAlloc(size, kAlignment));
+        if (ANGLE_UNLIKELY(result == nullptr))
         {
-        checkGuardBlock(preGuard(), kGuardBlockBeginVal, "before");
-        checkGuardBlock(postGuard(), kGuardBlockEndVal, "after");
+            return {};
         }
+        return Segment{{result, size}};
+    }
+    uint8_t *data() const { return mData.data(); }
 
-    // Find offsets to pre and post guard blocks, and user data buffer
-    unsigned char *preGuard() const { return mMem + HeaderSize(); }
-    unsigned char *data() const { return preGuard() + kGuardBlockSize; }
-    unsigned char *postGuard() const { return data() + mSize; }
-    size_t mSize;            // size of the user data area
-    unsigned char *mMem;     // beginning of our allocation (points to header)
-    Allocation *mPrevAlloc;  // prior allocation in the chain
-
-    static constexpr unsigned char kGuardBlockBeginVal = 0xfb;
-    static constexpr unsigned char kGuardBlockEndVal   = 0xfe;
-    static constexpr unsigned char kUserDataFill       = 0xcd;
-#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
-    static constexpr size_t kGuardBlockSize = 16;
-    static constexpr size_t HeaderSize() { return sizeof(Allocation); }
-#else
-    static constexpr size_t kGuardBlockSize = 0;
-    static constexpr size_t HeaderSize() { return 0; }
-#endif
+  private:
+    Span<uint8_t> mData;
 };
 
-#if !defined(ANGLE_DISABLE_POOL_ALLOC)
-class PageHeader
+PoolAllocator::PoolAllocator() = default;
+
+PoolAllocator::~PoolAllocator()
 {
-  public:
-    PageHeader(PageHeader *nextPage, size_t pageCount)
-        : nextPage(nextPage),
-          pageCount(pageCount)
-    {
-    }
+    reset();
+}
 
-    PageHeader *nextPage;
-    size_t pageCount;
-#    if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
-    Allocation *lastAllocation = nullptr;
-#    endif
-};
-#endif
+void PoolAllocator::lock()
+{
+    ASSERT(!mLocked);
+    mLocked = true;
+}
 
-//
-// Implement the functionality of the PoolAllocator class, which
-// is documented in PoolAlloc.h.
-//
-PoolAllocator::PoolAllocator(int growthIncrement, int allocationAlignment)
-    : mAlignment(allocationAlignment),
-#if !defined(ANGLE_DISABLE_POOL_ALLOC)
-      mPageSize(growthIncrement),
-      mFreeList(nullptr),
-      mInUseList(nullptr),
-      mNumCalls(0),
-      mTotalBytes(0),
-#endif
-      mLocked(false)
+void PoolAllocator::unlock()
 {
-#if !defined(ANGLE_DISABLE_POOL_ALLOC)
-    mPageHeaderSkip = sizeof(PageHeader);
+    ASSERT(mLocked);
+    mLocked = false;
+}
 
-    // Alignment == 1 is a special fast-path where fastAllocate() is enabled
-    if (mAlignment != 1)
-    {
-#endif
-        // Adjust mAlignment to be at least pointer aligned and
-        // power of 2.
-        //
-        size_t minAlign = sizeof(void *);
-        if (mAlignment < minAlign)
-        {
-            mAlignment = minAlign;
-        }
-        mAlignment = gl::ceilPow2(static_cast<unsigned int>(mAlignment));
-#if !defined(ANGLE_DISABLE_POOL_ALLOC)
-    }
-    //
-    // Don't allow page sizes we know are smaller than all common
-    // OS page sizes.
-    //
-    if (mPageSize < 4 * 1024)
-    {
-        mPageSize = 4 * 1024;
-    }
+#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
 
-    //
-    // A large mCurrentPageOffset indicates a new page needs to
-    // be obtained to allocate memory.
-    //
-    mCurrentPageOffset = mPageSize;
-#endif
+void PoolAllocator::addGuard(Span<uint8_t> guardData)
+{
+    memset(guardData.data(), kGuardFillValue, guardData.size());
+    mGuards.push_back(guardData);
 }
 
-PoolAllocator::~PoolAllocator()
-{
-    reset();
-#if !defined(ANGLE_DISABLE_POOL_ALLOC)
-    while (mFreeList)
-    {
-        PageHeader *next = mFreeList->nextPage;
-        delete[] reinterpret_cast<char *>(mFreeList);
-        mFreeList = next;
-    }
 #endif
-}
 
-//
-// Check a single guard block for damage
-//
-void Allocation::checkGuardBlock(unsigned char *blockMem,
-                                 unsigned char val,
-                                 const char *locText) const
+void PoolAllocator::reset()
 {
 #if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
-    for (size_t x = 0; x < kGuardBlockSize; x++)
+    for (Span<uint8_t> guard : mGuards)
     {
-        if (blockMem[x] != val)
+        for (uint8_t value : guard)
         {
-            char assertMsg[80];
-            // We don't print the assert message.  It's here just to be helpful.
-            snprintf(assertMsg, sizeof(assertMsg),
-                     "PoolAlloc: Damage %s %zu byte allocation at 0x%p\n", locText, mSize, data());
-            assert(0 && "PoolAlloc: Damage in guard block");
+            ASSERT(value == kGuardFillValue);
         }
     }
+    mGuards.clear();
 #endif
-}
-
-void PoolAllocator::reset()
-{
 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
-    mNumCalls   = 0;
-    mTotalBytes = 0;
-
-    mCurrentPageOffset = mPageSize;
-    PageHeader *page   = std::exchange(mInUseList, nullptr);
-    while (page)
-    {
-        const size_t pageCount = page->pageCount;
-        PageHeader *nextInUse  = page->nextPage;
-
-#    if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
-        if (page->lastAllocation)
-        {
-            Allocation *allocations = std::exchange(page->lastAllocation, nullptr);
-            allocations->checkAllocList();
-        }
-#    endif
-
-        if (pageCount > 1)
-        {
-            delete[] reinterpret_cast<uint8_t *>(page);
-        }
-        else
-        {
+    mCurrentPool    = {};
+    mUnusedSegments = std::exchange(mPoolSegments, {});
 #    if defined(ANGLE_WITH_ASAN)
+    for (auto &segment : mUnusedSegments)
+    {
         // Clear any container annotations left over from when the memory
         // was last used. (crbug.com/1419798)
-            __asan_unpoison_memory_region(page, mPageSize);
-#    endif
-            page->nextPage = mFreeList;
-            mFreeList      = page;
+        __asan_unpoison_memory_region(segment.data(), kSegmentSize);
     }
-        page = nextInUse;
-    }
-#else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
-    mStack.clear();
+#    endif
 #endif
+    mSingleObjectSegments.clear();
 }
 
-void *PoolAllocator::allocate(size_t numBytes)
-{
-    ASSERT(!mLocked);
-
 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
-    //
-    // Just keep some interesting statistics.
-    //
-    ++mNumCalls;
-    mTotalBytes += numBytes;
-
-    uint8_t *currentPagePtr = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
-
-    size_t preAllocationPadding = 0;
-    size_t allocationSize =
-        Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
-
-    // Integer overflow is unexpected.
-    ASSERT(allocationSize >= numBytes);
-
-    // Do the allocation, most likely case first, for efficiency.
-    if (allocationSize <= mPageSize - mCurrentPageOffset)
-    {
-        // There is enough room to allocate from the current page at mCurrentPageOffset.
-        uint8_t *memory = currentPagePtr + preAllocationPadding;
-        mCurrentPageOffset += allocationSize;
-
-        return initializeAllocation(memory, numBytes);
-    }
-
-    if (allocationSize > mPageSize - mPageHeaderSkip)
-    {
-        // If the allocation is larger than a whole page, do a multi-page allocation.  These are not
-        // mixed with the others.  The OS is efficient in allocating and freeing multiple pages.
-
-        // We don't know what the alignment of the new allocated memory will be, so conservatively
-        // allocate enough memory for up to alignment extra bytes being needed.
-        allocationSize = Allocation::AllocationSize(reinterpret_cast<uint8_t *>(mPageHeaderSkip),
-                                                    numBytes, mAlignment, &preAllocationPadding);
-
-        size_t numBytesToAlloc = allocationSize + mPageHeaderSkip + mAlignment;
-
-        // Integer overflow is unexpected.
-        ASSERT(numBytesToAlloc >= allocationSize);
 
-        uint8_t *memory = new (std::nothrow) uint8_t[numBytesToAlloc];
-        if (memory == nullptr)
-        {
-            return nullptr;
-        }
-        mInUseList =
-            new (memory) PageHeader(mInUseList, (numBytesToAlloc + mPageSize - 1) / mPageSize);
-
-        // Make next allocation come from a new page
-        mCurrentPageOffset = mPageSize;
-
-        // Now that we actually have the pointer, make sure the data pointer will be aligned.
-        currentPagePtr = reinterpret_cast<uint8_t *>(mInUseList) + mPageHeaderSkip;
-        Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
-
-        return initializeAllocation(currentPagePtr + preAllocationPadding, numBytes);
-    }
-
-    uint8_t *newPageAddr = allocateNewPage(numBytes);
-    return initializeAllocation(newPageAddr, numBytes);
-
-#else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
-
-    uint8_t *alloc = new (std::nothrow) uint8_t[numBytes + mAlignment - 1];
-    mStack.emplace_back(std::unique_ptr<uint8_t[]>(alloc));
-
-    intptr_t intAlloc = reinterpret_cast<intptr_t>(alloc);
-    intAlloc          = rx::roundUpPow2<intptr_t>(intAlloc, mAlignment);
-    return reinterpret_cast<void *>(intAlloc);
-#endif
-}
-
-#if !defined(ANGLE_DISABLE_POOL_ALLOC)
-uint8_t *PoolAllocator::allocateNewPage(size_t numBytes)
+bool PoolAllocator::allocateNewPoolSegment()
 {
-    // Need a simple page to allocate from.  Pick a page from the free list, if any.  Otherwise need
-    // to make the allocation.
-    if (mFreeList)
+    Segment segment;
+    if (!mUnusedSegments.empty())
     {
-        PageHeader *page = mFreeList;
-        mFreeList = mFreeList->nextPage;
-        page->nextPage   = mInUseList;
-        mInUseList       = page;
+        segment = std::move(mUnusedSegments.back());
+        mUnusedSegments.pop_back();
     }
     else
     {
-        uint8_t *memory = new (std::nothrow) uint8_t[mPageSize];
-        if (memory == nullptr)
+        segment = Segment::Allocate(kSegmentSize);
+        if (ANGLE_UNLIKELY(segment.data() == nullptr))
         {
-            return nullptr;
+            return false;
         }
-        mInUseList = new (memory) PageHeader(mInUseList, 1);
     }
 
-    // Leave room for the page header.
-    mCurrentPageOffset      = mPageHeaderSkip;
-    uint8_t *currentPagePtr = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
-
-    size_t preAllocationPadding = 0;
-    size_t allocationSize =
-        Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
-
-    mCurrentPageOffset += allocationSize;
-
-    // The new allocation is made after the page header and any alignment required before it.
-    return reinterpret_cast<uint8_t *>(mInUseList) + mPageHeaderSkip + preAllocationPadding;
+    mCurrentPool = {segment.data(), kSegmentSize};
+    mPoolSegments.push_back(std::move(segment));
+    return true;
 }
 
-void *PoolAllocator::initializeAllocation(uint8_t *memory, size_t numBytes)
-{
-#    if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
-    mInUseList->lastAllocation =
-        new (memory) Allocation(numBytes, memory, mInUseList->lastAllocation);
-#    endif
-
-    return Allocation::GetDataPointer(memory, mAlignment);
-}
 #endif
 
-void PoolAllocator::lock()
-{
-    ASSERT(!mLocked);
-    mLocked = true;
-}
-
-void PoolAllocator::unlock()
-{
-    ASSERT(mLocked);
-    mLocked = false;
-}
-
-//
-// Check all allocations in a list for damage by calling check on each.
-//
-void Allocation::checkAllocList() const
+Span<uint8_t> PoolAllocator::allocateSingleObject(size_t size)
 {
-    for (const Allocation *alloc = this; alloc != nullptr; alloc = alloc->mPrevAlloc)
+    Segment segment = Segment::Allocate(size);
+    if (ANGLE_UNLIKELY(segment.data() == nullptr))
     {
-        alloc->checkAlloc();
+        return {};
     }
+    Span<uint8_t> result{segment.data(), size};
+    mSingleObjectSegments.push_back(std::move(segment));
+    ANGLE_ALLOC_PROFILE(LOCAL_BUMP_ALLOCATION, result, true);
+    return result;
 }
 
 }  // namespace angle
diff --git a/src/common/PoolAlloc.h b/src/common/PoolAlloc.h
index dab10d9c23f9d9c7ac40c6b8298c5fa26f3f8692..0adba197ea0b9722f83d3cb58c5c886555d68d39 100644
--- a/src/common/PoolAlloc.h
+++ b/src/common/PoolAlloc.h
@@ -37,74 +37,41 @@
 
 #include <stdint.h>
 
+#include <memory>
+#include <utility>
+#include <vector>
+
 #include "common/angleutils.h"
 #include "common/log_utils.h"
+#include "common/mathutil.h"
+#include "common/span.h"
+#ifdef ANGLE_PLATFORM_APPLE
+#    if __has_include(<WebKitAdditions/ANGLEAllocProfile.h>)
+#        include <WebKitAdditions/ANGLEAllocProfile.h>
+#    endif
+#endif
 
-#if defined(ANGLE_DISABLE_POOL_ALLOC)
-#    include <memory>
-#    include <vector>
+#if !defined(ANGLE_ALLOC_PROFILE)
+#define ANGLE_ALLOC_PROFILE(kind, ...)
+#define ANGLE_ALLOC_PROFILE_ALIGNMENT(x) (x)
 #endif
 
 namespace angle
 {
-class PageHeader;
 
-// Pages are linked together with a simple header at the beginning
-// of each allocation obtained from the underlying OS.
-// The "page size" used is not, nor must it match, the underlying OS
-// page size.  But, having it be about that size or equal to a set of
-// pages is likely most optimal.
-//
+// Allocator that allocates memory aligned to kAlignment and releases it when the instance is
+// destroyed.
 class PoolAllocator : angle::NonCopyable
 {
   public:
-
-    static const int kDefaultAlignment = sizeof(void *);
-    //
-    // Create PoolAllocator. If alignment is set to 1 byte then fastAllocate()
-    //  function can be used to make allocations with less overhead.
-    //
-    PoolAllocator(int growthIncrement = 8 * 1024, int allocationAlignment = kDefaultAlignment);
+    PoolAllocator();
     ~PoolAllocator();
 
-    // Marks all allocated memory as unused. The memory will be reused.
-    void reset();
-
-    // Call allocate() to actually acquire memory.  Returns 0 if no memory
-    // available, otherwise a properly aligned pointer to 'numBytes' of memory.
-    //
+    // Returns aligned pointer to 'numBytes' of memory or nullptr on allocation failure.
     void *allocate(size_t numBytes);
 
-    //
-    // Call fastAllocate() for a faster allocate function that does minimal bookkeeping
-    // preCondition: Allocator must have been created w/ alignment of 1
-    ANGLE_INLINE uint8_t *fastAllocate(size_t numBytes)
-    {
-#if defined(ANGLE_DISABLE_POOL_ALLOC)
-        return reinterpret_cast<uint8_t *>(allocate(numBytes));
-#else
-        ASSERT(mAlignment == 1);
-        // No multi-page allocations
-        ASSERT(numBytes <= (mPageSize - mPageHeaderSkip));
-        //
-        // Do the allocation, most likely case inline first, for efficiency.
-        //
-        if (numBytes <= mPageSize - mCurrentPageOffset)
-        {
-            //
-            // Safe to allocate from mCurrentPageOffset.
-            //
-            uint8_t *memory = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
-            mCurrentPageOffset += numBytes;
-            return memory;
-        }
-        return allocateNewPage(numBytes);
-#endif
-    }
-
-    // There is no deallocate.  The point of this class is that deallocation can be skipped by the
-    // user of it, as the model of use is to simultaneously deallocate everything at once by
-    // destroying the instance or reset().
+    // Marks all allocated memory as unused. The memory will be reused.
+    void reset();
 
     // Catch unwanted allocations.
     // TODO(jmadill): Remove this when we remove the global allocator.
@@ -112,40 +79,78 @@ class PoolAllocator : angle::NonCopyable
     void unlock();
 
   private:
-    size_t mAlignment;  // all returned allocations will be aligned at
-                        // this granularity, which will be a power of 2
+    static constexpr size_t kAlignment = ANGLE_ALLOC_PROFILE_ALIGNMENT(sizeof(void *));
+    Span<uint8_t> allocateSingleObject(size_t size);
+    class Segment;
+    std::vector<Segment> mSingleObjectSegments;  // Large objects.
+
 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
-    // Slow path of allocation when we have to get a new page.
-    uint8_t *allocateNewPage(size_t numBytes);
-    // Track allocations if and only if we're using guard blocks
-    void *initializeAllocation(uint8_t *memory, size_t numBytes);
-
-    // Granularity of allocation from the OS
-    size_t mPageSize;
-    // Amount of memory to skip to make room for the page header (which is the size of the page
-    // header, or PageHeader in PoolAlloc.cpp)
-    size_t mPageHeaderSkip;
-    // Next offset in top of inUseList to allocate from.  This offset is not necessarily aligned to
-    // anything.  When an allocation is made, the data is aligned to mAlignment, and the header (if
-    // any) will align to pointer size by extension (since mAlignment is made aligned to at least
-    // pointer size).
-    size_t mCurrentPageOffset;
-    // List of unused memory.
-    PageHeader *mFreeList;
-    // List of all memory currently being used.  The head of this list is where allocations are
-    // currently being made from.
-    PageHeader *mInUseList;
-
-    int mNumCalls;       // just an interesting statistic
-    size_t mTotalBytes;  // just an interesting statistic
-
-#else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
-    std::vector<std::unique_ptr<uint8_t[]>> mStack;
+    static constexpr size_t kSegmentSize = 32768;
+    bool allocateNewPoolSegment();
+
+    Span<uint8_t> mCurrentPool;  // The unused part of memory in last entry of mPoolSegments.
+    std::vector<Segment> mPoolSegments;    // List of currently in use memory allocations.
+    std::vector<Segment> mUnusedSegments;  // List of unused allocations after reset().
 #endif
 
-    bool mLocked;
+#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
+    void addGuard(Span<uint8_t> guardData);
+
+    std::vector<Span<uint8_t>> mGuards;  // Guards, memory which is asserted to stay prestine.
+#endif
+    bool mLocked = false;
 };
 
+inline void *PoolAllocator::allocate(size_t size)
+{
+    ASSERT(!mLocked);
+    Span<uint8_t> data;
+
+    size_t extent = size;
+#if !defined(ANGLE_DISABLE_POOL_ALLOC)
+    // Allocate with kAlignment granularity to keep the next allocation aligned.
+    extent = rx::roundUpPow2(extent, kAlignment);
+#endif
+#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
+    // Add space for guard block before. Add space for guard block after if there is no alignment
+    // padding, else use the padding as the guard block after.
+    extent += kAlignment + (extent == size ? kAlignment : 0);
+#endif
+#if !defined(ANGLE_DISABLE_POOL_ALLOC)
+    if (extent <= mCurrentPool.size())
+    {
+        data         = mCurrentPool.first(extent);
+        mCurrentPool = mCurrentPool.subspan(extent);
+        ANGLE_ALLOC_PROFILE(LOCAL_BUMP_ALLOCATION, data, false);
+    }
+    else if (extent < kSegmentSize)
+    {
+        if (ANGLE_UNLIKELY(!allocateNewPoolSegment()))
+        {
+            return nullptr;
+        }
+        data         = mCurrentPool.first(extent);
+        mCurrentPool = mCurrentPool.subspan(extent);
+        ANGLE_ALLOC_PROFILE(LOCAL_BUMP_ALLOCATION, data, false);
+    }
+    else
+#endif
+    {
+        data = allocateSingleObject(extent);
+        if (data.empty())
+        {
+            return nullptr;
+        }
+    }
+
+#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
+    addGuard(data.first(kAlignment));
+    data = data.subspan(kAlignment);
+    addGuard(data.subspan(size));
+#endif
+    return data.first(size).data();
+}
+
 }  // namespace angle
 
 #endif  // COMMON_POOLALLOC_H_
diff --git a/src/common/PoolAlloc_unittest.cpp b/src/common/PoolAlloc_unittest.cpp
index 118adbaa010da5f7437b5401b8951522b349b754..09adaa262762643221de4ae7ca141645e6b21951 100644
--- a/src/common/PoolAlloc_unittest.cpp
+++ b/src/common/PoolAlloc_unittest.cpp
@@ -17,8 +17,16 @@
 
 namespace angle
 {
+
+class PoolAllocatorTest : public testing::Test
+{
+  protected:
+    static constexpr size_t kPoolAllocatorPageSize  = 32768;
+    static constexpr size_t kPoolAllocatorAlignment = sizeof(void *);
+};
+
 // Verify the public interface of PoolAllocator class
-TEST(PoolAllocatorTest, Interface)
+TEST_F(PoolAllocatorTest, Interface)
 {
     size_t numBytes               = 1024;
     constexpr uint32_t kTestValue = 0xbaadbeef;
@@ -42,42 +50,108 @@ TEST(PoolAllocatorTest, Interface)
     // Verify first allocation still has data
     EXPECT_EQ(kTestValue, *writePtr);
     // Make a bunch of allocations
+    for (uint32_t j = 0; j < 100; ++j)
+    {
         for (uint32_t i = 0; i < 1000; ++i)
         {
-        numBytes   = (rand() % (1024 * 4)) + 1;
+            numBytes   = (rand() % kPoolAllocatorPageSize * 3) + 1;
             allocation = poolAllocator.allocate(numBytes);
             EXPECT_NE(nullptr, allocation);
             // Write data into full allocation. In debug case if we
             //  overwrite any other allocation we get error.
             memset(allocation, 0xb8, numBytes);
         }
+        poolAllocator.reset();
+    }
 }
 
-#if !defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
-// Verify allocations are correctly aligned for different alignments
-class PoolAllocatorAlignmentTest : public testing::TestWithParam<int>
-{};
-
-TEST_P(PoolAllocatorAlignmentTest, Alignment)
+// Tests that PoolAllocator returns pointers with expected alignment.
+TEST_F(PoolAllocatorTest, Alignment)
 {
-    int alignment = GetParam();
-    // Create a pool allocator to allocate from
-    PoolAllocator poolAllocator(4096, alignment);
-    // Test a number of allocation sizes for each alignment
+    PoolAllocator poolAllocator;
+    for (uint32_t j = 0; j < 10; ++j)
+    {
         for (uint32_t i = 0; i < 100; ++i)
         {
-        // Vary the allocation size around 4k to hit some multi-page allocations
-        const size_t numBytes = rand() % (1024 * 4) + 1;
+            // Vary the allocation size to hit some large object allocations.
+            const size_t numBytes = (rand() % kPoolAllocatorPageSize * 3) + 1;
             void *allocation      = poolAllocator.allocate(numBytes);
             // Verify alignment of allocation matches expected default
-        EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(allocation) % alignment)
-            << "Iteration " << i << " allocating " << numBytes;
+            EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(allocation) % kPoolAllocatorAlignment)
+                << "Iteration " << j << ", " << i << " allocating " << numBytes
+                << " got: " << allocation;
+            memset(allocation, i, numBytes);
         }
+        poolAllocator.reset();
+    }
+}
+
+#if !defined(ANGLE_DISABLE_POOL_ALLOC)
+
+// Test that reset recycles memory.
+TEST_F(PoolAllocatorTest, ResetRecyclesMemory)
+{
+    PoolAllocator poolAllocator;
+    void *allocation1 = poolAllocator.allocate(1);
+    void *allocation2 = poolAllocator.allocate(2);
+    memset(allocation1, 11, 1);
+    memset(allocation2, 12, 2);
+    ANGLE_ALLOC_PROFILE(POINTER, allocation1);
+    ANGLE_ALLOC_PROFILE(POINTER, allocation2);
+    poolAllocator.reset();
+    void *allocation3 = poolAllocator.allocate(1);
+    void *allocation4 = poolAllocator.allocate(2);
+    memset(allocation3, 21, 1);
+    memset(allocation4, 22, 2);
+    ANGLE_ALLOC_PROFILE(POINTER, allocation3);
+    ANGLE_ALLOC_PROFILE(POINTER, allocation4);
+    EXPECT_NE(allocation1, nullptr);
+    EXPECT_NE(allocation2, nullptr);
+    EXPECT_NE(allocation1, allocation2);
+    EXPECT_EQ(allocation1, allocation3);
+    EXPECT_EQ(allocation2, allocation4);
 }
 
-INSTANTIATE_TEST_SUITE_P(,
-                         PoolAllocatorAlignmentTest,
-                         testing::Values(2, 4, 8, 16, 32, 64, 128),
-                         testing::PrintToStringParamName());
 #endif
+
+#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
+
+class PoolAllocatorGuardTest : public PoolAllocatorTest
+{};
+
+// Verify that alignment guard detects overflowing write.
+TEST_F(PoolAllocatorGuardTest, AlignmentGuardDetectsOverflowWrite)
+{
+    auto testOverflowAlignment = []() {
+        PoolAllocator poolAllocator;
+        void *allocation = poolAllocator.allocate(15);
+        memset(allocation, 11, 16);
+    };
+    ASSERT_DEATH(testOverflowAlignment(), "");
+}
+
+// Verify that allocation guard detects overflowing write.
+TEST_F(PoolAllocatorGuardTest, AllocationGuardsDetectsOverflowWrite)
+{
+    auto testOverflow = []() {
+        PoolAllocator poolAllocator;
+        void *allocation1 = poolAllocator.allocate(16);
+        memset(allocation1, 11, 17);
+    };
+    ASSERT_DEATH(testOverflow(), "");
+}
+
+// Verify that allocation guard detects underflowing write.
+TEST_F(PoolAllocatorGuardTest, AllocationGuardsDetectsUnderflowWrite)
+{
+    auto testUnderflow = []() {
+        PoolAllocator poolAllocator;
+        void *allocation1 = poolAllocator.allocate(16);
+        memset(reinterpret_cast<uint8_t *>(allocation1) - 1, 11, 1);
+    };
+    ASSERT_DEATH(testUnderflow(), "");
+}
+
+#endif
+
 }  // namespace angle
diff --git a/src/common/debug.cpp b/src/common/debug.cpp
index 2c7f18b509f77805f2a200c3077931a5347b8548..f4c919fcb364066eb2ab1e5ce39f873c97b4a9b0 100644
--- a/src/common/debug.cpp
+++ b/src/common/debug.cpp
@@ -40,6 +40,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 8335bd85159ab70cfd1dbc8434e5eadcc49d576d..d6f7fa2ff9e674e8b09f788fae56d155721320f5 100644
--- a/src/common/entry_points_enum_autogen.cpp
+++ b/src/common/entry_points_enum_autogen.cpp
@@ -526,6 +526,8 @@ const char *GetEntryPointName(EntryPoint ep)
             return "glBindFramebufferOES";
         case EntryPoint::GLBindImageTexture:
             return "glBindImageTexture";
+        case EntryPoint::GLBindMetalRasterizationRateMapANGLE:
+            return "glBindMetalRasterizationRateMapANGLE";
         case EntryPoint::GLBindProgramPipeline:
             return "glBindProgramPipeline";
         case EntryPoint::GLBindProgramPipelineEXT:
@@ -954,6 +956,8 @@ const char *GetEntryPointName(EntryPoint ep)
             return "glFramebufferRenderbuffer";
         case EntryPoint::GLFramebufferRenderbufferOES:
             return "glFramebufferRenderbufferOES";
+        case EntryPoint::GLFramebufferResolveRenderbufferWEBKIT:
+            return "glFramebufferResolveRenderbufferWEBKIT";
         case EntryPoint::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 7245b9b2ccedb8e7274c5f91980b72e22eac21ff..41e23a2601855adce0f4db1daab6e0c46dd3cad9 100644
--- a/src/common/entry_points_enum_autogen.h
+++ b/src/common/entry_points_enum_autogen.h
@@ -269,6 +269,7 @@ enum class EntryPoint
     GLBindFramebuffer,
     GLBindFramebufferOES,
     GLBindImageTexture,
+    GLBindMetalRasterizationRateMapANGLE,
     GLBindProgramPipeline,
     GLBindProgramPipelineEXT,
     GLBindRenderbuffer,
@@ -483,6 +484,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 3311a744f3f878b7db0ab228592567a8f247d05b..ef73a62ff41cee2772f20563dee84c203a1361d1 100644
--- a/src/common/frame_capture_utils_autogen.cpp
+++ b/src/common/frame_capture_utils_autogen.cpp
@@ -225,6 +225,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);
@@ -1003,6 +1007,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 4b4287dd7b3b53dd6d0857be5de713f2254ea80d..436a9027cc6a5044fcd8492d2c4b7362077fb142 100644
--- a/src/common/frame_capture_utils_autogen.h
+++ b/src/common/frame_capture_utils_autogen.h
@@ -101,6 +101,7 @@ enum class ParamType
     TGLDEBUGPROC,
     TGLDEBUGPROCKHR,
     TGLGETBLOBPROCANGLE,
+    TGLMTLRasterizationRateMapANGLE,
     TGLSETBLOBPROCANGLE,
     TGLbitfield,
     TGLboolean,
@@ -273,7 +274,7 @@ enum class ParamType
     TvoidPointerPointer,
 };
 
-constexpr uint32_t kParamTypeCount = 236;
+constexpr uint32_t kParamTypeCount = 237;
 
 union ParamValue
 {
@@ -337,6 +338,7 @@ union ParamValue
     GLDEBUGPROC GLDEBUGPROCVal;
     GLDEBUGPROCKHR GLDEBUGPROCKHRVal;
     GLGETBLOBPROCANGLE GLGETBLOBPROCANGLEVal;
+    GLMTLRasterizationRateMapANGLE GLMTLRasterizationRateMapANGLEVal;
     GLSETBLOBPROCANGLE GLSETBLOBPROCANGLEVal;
     GLbitfield GLbitfieldVal;
     GLboolean GLbooleanVal;
@@ -901,6 +903,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)
@@ -2230,6 +2240,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:
@@ -2944,6 +2956,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)
@@ -4312,6 +4332,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 f35e1ad6a1b5fb3c6670f1347c3af5d092bfd7e5..3600e32a1343805c10751a3f81de9451d6ca4d4d 100644
--- a/src/common/gl_enum_utils_autogen.cpp
+++ b/src/common/gl_enum_utils_autogen.cpp
@@ -2828,6 +2828,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:
@@ -22439,6 +22443,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},
@@ -25108,6 +25113,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/span.h b/src/common/span.h
index a2ebec2c0d8a8a59a38d568aedd149c889fcafa2..fd6e9f573dd804a2ccdc183c6b815ef73e834519 100644
--- a/src/common/span.h
+++ b/src/common/span.h
@@ -106,6 +106,12 @@ class Span
         return count == 0 ? Span() : Span(mData + offset, count);
     }
 
+    constexpr Span subspan(size_type offset) const
+    {
+        ASSERT(offset <= mSize);
+        return offset == mSize ? Span() : Span(mData + offset, mSize - offset);
+    }
+
   private:
     T *mData     = nullptr;
     size_t mSize = 0;
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 47c454edb6813f269f12ffa8ebe52b04bba3ec59..fe0d347a937e68e90a5d5d006f0464007f505041 100644
--- a/src/common/utilities.cpp
+++ b/src/common/utilities.cpp
@@ -10,6 +10,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 53c7fe8b2cc2608122fb5a4ac8cf568d95f6d960..e442e0d6601b49a8fadf9b83a7a191fec4c60a9a 100644
--- a/src/compiler/fuzz/translator_fuzzer.cpp
+++ b/src/compiler/fuzz/translator_fuzzer.cpp
@@ -159,6 +159,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);
@@ -174,6 +175,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)
     {
@@ -205,6 +210,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 
         if (translator == nullptr)
         {
+            sh::Finalize();
             return 0;
         }
 
@@ -239,6 +245,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 
         if (!translator->Init(resources))
         {
+            sh::Finalize();
             return 0;
         }
 
@@ -251,5 +258,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 cfb4bc1b2a0113b0d6ea863be78fbbd60afd922c..8702d01fef25765c4778172a2a45cd5e54addbd8 100644
--- a/src/compiler/preprocessor/preprocessor_tab_autogen.cpp
+++ b/src/compiler/preprocessor/preprocessor_tab_autogen.cpp
@@ -1,3 +1,6 @@
+/* Apple Note: For the avoidance of doubt, Apple elects to distribute this file under the terms of
+ * the BSD license. */
+
 /* A Bison parser, made by GNU Bison 3.8.2.  */
 
 /* Bison implementation for Yacc-like parsers in C
diff --git a/src/compiler/translator/InfoSink.h b/src/compiler/translator/InfoSink.h
index af8914a52e3a838afed41b98da82b2ed07e90420..ba40416d77dcd9aa9d6623f971637dc8b2a9f9a2 100644
--- a/src/compiler/translator/InfoSink.h
+++ b/src/compiler/translator/InfoSink.h
@@ -22,7 +22,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 4ff73f4f667c6b1a0fffeca528ef7a15faebc104..b4ff27f8388adadcab0c89af78020cc536a1a5e2 100644
--- a/src/compiler/translator/Types.h
+++ b/src/compiler/translator/Types.h
@@ -255,6 +255,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..9f292fd48d12adbe48f83695a971de6096f5a147 100644
--- a/src/compiler/translator/glslang_tab_autogen.cpp
+++ b/src/compiler/translator/glslang_tab_autogen.cpp
@@ -1,3 +1,6 @@
+/* Apple Note: For the avoidance of doubt, Apple elects to distribute this file under the terms of
+ * the BSD license. */
+
 /* A Bison parser, made by GNU Bison 3.8.2.  */
 
 /* Bison implementation for Yacc-like parsers in C
diff --git a/src/compiler/translator/glslang_tab_autogen.h b/src/compiler/translator/glslang_tab_autogen.h
index d5a48edd40f382359db76acf1a1ce75b6a6dc6d5..817bb46f9ca4b6fe0294c0fec72878a567c32ef6 100644
--- a/src/compiler/translator/glslang_tab_autogen.h
+++ b/src/compiler/translator/glslang_tab_autogen.h
@@ -1,3 +1,6 @@
+/* Apple Note: For the avoidance of doubt, Apple elects to distribute this file under the terms of
+ * the BSD license. */
+
 /* A Bison parser, made by GNU Bison 3.8.2.  */
 
 /* Bison interface for Yacc-like parsers in C
diff --git a/src/compiler/translator/msl/EmitMetal.cpp b/src/compiler/translator/msl/EmitMetal.cpp
index c0c1084f1b852d7eb851dc0ccaf8f306b708d89b..330f7d683a11b3709e3a78ccd9a061b126938a5e 100644
--- a/src/compiler/translator/msl/EmitMetal.cpp
+++ b/src/compiler/translator/msl/EmitMetal.cpp
@@ -185,12 +185,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;
@@ -205,8 +210,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()
@@ -232,9 +274,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()
 {
@@ -283,19 +329,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:
@@ -303,19 +339,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:
@@ -360,23 +386,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:
@@ -390,6 +412,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()))
@@ -888,17 +934,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();
     }
@@ -1819,6 +1862,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();
@@ -2255,7 +2310,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("(", ")");
         }
 
@@ -2380,6 +2459,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())
@@ -2581,6 +2680,8 @@ bool GenMetalTraverser::visitForLoop(TIntermLoop *loopNode)
     TIntermTyped *condNode = loopNode->getCondition();
     TIntermTyped *exprNode = loopNode->getExpression();
 
+    ScopedForwardProgressStore scopedProgress(*this);
+
     mOut << "for (";
 
     if (initNode)
@@ -2623,6 +2724,7 @@ bool GenMetalTraverser::visitWhileLoop(TIntermLoop *loopNode)
     ASSERT(condNode);
     ASSERT(!initNode && !exprNode);
 
+    ScopedForwardProgressStore scopedProgress(*this);
     emitIndentation();
     mOut << "while (";
     condNode->traverse(this);
@@ -2642,6 +2744,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 8b3b1d2d2be8c852c0b765cd282745a031e02208..b4eb7117e7d00d18452859f685895ba4b85b5b4e 100644
--- a/src/compiler/translator/msl/ProgramPrelude.cpp
+++ b/src/compiler/translator/msl/ProgramPrelude.cpp
@@ -117,6 +117,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();
@@ -281,6 +288,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:
@@ -436,6 +451,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>
@@ -2773,6 +2898,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()
@@ -3292,6 +3575,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())
             {
@@ -3443,6 +3730,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 addScalarMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                addInt();
+            }
             break;
 
         case TOperator::EOpAddAssign:
@@ -3450,6 +3741,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 addMatrixScalarAssign();
             }
+            if (argType0->isSignedInt())
+            {
+                addAssignInt();
+            }
             break;
 
         case TOperator::EOpSub:
@@ -3461,6 +3756,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 subScalarMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                subInt();
+            }
             break;
 
         case TOperator::EOpSubAssign:
@@ -3468,9 +3767,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())
@@ -3482,12 +3800,9 @@ void ProgramPrelude::visitOperator(TOperator op,
                     divScalarMatrix();
                 }
             }
-            break;
-
-        case TOperator::EOpDivAssign:
-            if (argType0->isMatrix() && argType1->isMatrix())
+            else
             {
-                componentWiseDivideAssign();
+                div();
             }
             break;
 
@@ -3528,6 +3843,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 preIncrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                preIncrementInt();
+            }
             break;
 
         case TOperator::EOpPostIncrement:
@@ -3535,6 +3854,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 postIncrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                postIncrementInt();
+            }
             break;
 
         case TOperator::EOpPreDecrement:
@@ -3542,6 +3865,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 preDecrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                preDecrementInt();
+            }
             break;
 
         case TOperator::EOpPostDecrement:
@@ -3549,6 +3876,10 @@ void ProgramPrelude::visitOperator(TOperator op,
             {
                 postDecrementMatrix();
             }
+            if (argType0->isSignedInt())
+            {
+                postDecrementInt();
+            }
             break;
 
         case TOperator::EOpNegative:
@@ -3558,20 +3889,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:
@@ -3590,10 +3932,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:
@@ -3765,6 +4105,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 9177909b54a4b3bffe10842b4856732443212dab..372619c911eef9d1eb6b255594a15cee57fea076 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -9819,6 +9819,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));
@@ -9893,6 +9920,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 a0923c528a0d1fd1bc3bdc6d64ea7794ecdf5a98..8bb23339bf5748b7bfdf8c51a8643ab7040ddd7e 100644
--- a/src/libANGLE/Context_gles_ext_autogen.h
+++ b/src/libANGLE/Context_gles_ext_autogen.h
@@ -652,6 +652,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);                                                   \
@@ -678,6 +680,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 a724b259ed2148ecef5e8d28d3988c00c15a2d7d..bcbcf5a9e9b1b6dac20e755d2cf15bd7a4a19723 100644
--- a/src/libANGLE/Display.cpp
+++ b/src/libANGLE/Display.cpp
@@ -2303,7 +2303,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 624cb4830c6c59d7e165fd048491096e03f5996b..ce0f6bac1d45b2c90221697185a5c56f04bdcae9 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -171,6 +171,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,
@@ -400,6 +442,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),
@@ -423,6 +468,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),
@@ -593,6 +641,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();
@@ -823,6 +892,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)
@@ -857,6 +930,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);
@@ -1151,6 +1233,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());
@@ -1402,6 +1501,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())
     {
@@ -1455,6 +1573,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;
@@ -1510,6 +1642,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
@@ -1980,6 +2126,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,
@@ -2157,11 +2363,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())
@@ -2817,4 +3052,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 5270fbb0986e6650e17293602cf15087f16e7097..673570b94d3994109e338a4bf223bebe8aed95ca 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -10,6 +10,9 @@
 #    pragma allow_unsafe_buffers
 #endif
 
+// Older clang versions have a false positive on this warning here.
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+
 #include "libANGLE/State.h"
 
 #include <string.h>
@@ -372,6 +375,8 @@ PrivateState::PrivateState(const Version &clientVersion,
       mLogicOp(LogicalOperation::Copy),
       mPatchVertices(3),
       mPixelLocalStorageActivePlanes(0),
+      mVariableRasterizationRateEnabled(false),
+      mVariableRasterizationRateMap(nullptr),
       mNoSimultaneousConstantColorAndAlphaBlendFunc(false),
       mSetBlendIndexedInvoked(false),
       mSetBlendFactorsIndexedInvoked(false),
@@ -1425,6 +1430,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());
@@ -1557,6 +1582,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;
     }
@@ -1723,6 +1751,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);
@@ -3687,6 +3717,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 6d84b9d5a145e4aaa28ce8c1bcdc615e8f3f2c19..b542c239de79d29adc438a0fdac0b790c96b2a36 100644
--- a/src/libANGLE/State.h
+++ b/src/libANGLE/State.h
@@ -175,6 +175,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,
@@ -481,6 +482,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; }
@@ -731,6 +741,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/TransformFeedback.cpp b/src/libANGLE/TransformFeedback.cpp
index c40804720389aeebc4195a40758fbbc4b3a781c9..bf3d00bc12c7b1c264134263bf383d6410026a8f 100644
--- a/src/libANGLE/TransformFeedback.cpp
+++ b/src/libANGLE/TransformFeedback.cpp
@@ -152,26 +152,7 @@ angle::Result TransformFeedback::begin(const Context *context,
 
     // In one of the angle_unittests - "TransformFeedbackTest.SideEffectsOfStartAndStop"
     // there is a code path where <context> is a nullptr, account for that possiblity.
-    const ProgramExecutable *programExecutable =
-        context ? context->getState().getLinkedProgramExecutable(context) : nullptr;
-    if (programExecutable)
-    {
-        // Compute the number of vertices we can draw before overflowing the bound buffers.
-        auto strides = programExecutable->getTransformFeedbackStrides();
-        ASSERT(strides.size() <= mState.mIndexedBuffers.size() && !strides.empty());
-        GLsizeiptr minCapacity = std::numeric_limits<GLsizeiptr>::max();
-        for (size_t index = 0; index < strides.size(); index++)
-        {
-            GLsizeiptr capacity =
-                GetBoundBufferAvailableSize(mState.mIndexedBuffers[index]) / strides[index];
-            minCapacity = std::min(minCapacity, capacity);
-        }
-        mState.mVertexCapacity = minCapacity;
-    }
-    else
-    {
-        mState.mVertexCapacity = 0;
-    }
+    recomputeVertexCapacity(context);
     return angle::Result::Continue;
 }
 
@@ -202,6 +183,7 @@ angle::Result TransformFeedback::resume(const Context *context)
 {
     ANGLE_TRY(mImplementation->resume(context));
     mState.mPaused = false;
+    recomputeVertexCapacity(context);
     return angle::Result::Continue;
 }
 
@@ -255,6 +237,31 @@ void TransformFeedback::bindProgram(const Context *context, Program *program)
     }
 }
 
+void TransformFeedback::recomputeVertexCapacity(const Context *context)
+{
+    const ProgramExecutable *programExecutable =
+        context ? context->getState().getLinkedProgramExecutable(context) : nullptr;
+    if (programExecutable)
+    {
+        // Compute the number of vertices we can draw before overflowing the bound buffers.
+        auto strides = programExecutable->getTransformFeedbackStrides();
+        ASSERT(strides.size() <= mState.mIndexedBuffers.size() && !strides.empty());
+        GLsizeiptr minCapacity = std::numeric_limits<GLsizeiptr>::max();
+        for (size_t index = 0; index < strides.size(); index++)
+        {
+            GLsizeiptr capacity =
+                GetBoundBufferAvailableSize(mState.mIndexedBuffers[index]) / strides[index];
+            minCapacity = std::min(minCapacity, capacity);
+        }
+        mState.mVertexCapacity = minCapacity;
+    }
+    else
+    {
+        mState.mVertexCapacity = 0;
+    }
+    mState.mVerticesDrawn = std::min(mState.mVerticesDrawn, mState.mVertexCapacity);
+}
+
 bool TransformFeedback::hasBoundProgram(ShaderProgramID program) const
 {
     return mState.mProgram != nullptr && mState.mProgram->id().value == program.value;
diff --git a/src/libANGLE/TransformFeedback.h b/src/libANGLE/TransformFeedback.h
index 0c322217794c7494fabc8b77b4ef9ca205d0851d..9eb347930c88f2a17b6c31a9346543145e148c64 100644
--- a/src/libANGLE/TransformFeedback.h
+++ b/src/libANGLE/TransformFeedback.h
@@ -114,6 +114,7 @@ class TransformFeedback final : public RefCountObject<TransformFeedbackID>, publ
 
   private:
     void bindProgram(const Context *context, Program *program);
+    void recomputeVertexCapacity(const Context *context);
 
     TransformFeedbackState mState;
     rx::TransformFeedbackImpl *mImplementation;
diff --git a/src/libANGLE/capture/capture_gles_ext_autogen.cpp b/src/libANGLE/capture/capture_gles_ext_autogen.cpp
index 23d8b5de98f53ec6457df4a4ea1dfdf57d075904..72be6f72b4ca2596dd0d5b7abfd9d230f4273c48 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,
@@ -12504,4 +12518,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 d397d7c519f0f8b63e5d37e652fcffa61bab6c30..9083b01505e51b9545010211cde29cb71d506af8 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,
@@ -2960,6 +2966,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 6f70ea77d97617ce80c83781a708817700db5891..70744adf4522f01b9af890bfe7846ffd8905f2d4 100644
--- a/src/libANGLE/formatutils.cpp
+++ b/src/libANGLE/formatutils.cpp
@@ -587,6 +587,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 2f544eb8937bfc843d51434208e2be79b255fe13..57945e4f1e0ac3a21bf3d74435dabc932d5e4139 100644
--- a/src/libANGLE/gles_extensions_autogen.cpp
+++ b/src/libANGLE/gles_extensions_autogen.cpp
@@ -247,6 +247,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);
@@ -283,6 +284,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 e72c3f283236bc0ced7d01149ffc79a089290bbb..19b436c6f27a7ea647e3f012bd67f9029451e83d 100644
--- a/src/libANGLE/gles_extensions_autogen.h
+++ b/src/libANGLE/gles_extensions_autogen.h
@@ -701,6 +701,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;
 
@@ -809,6 +812,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 8b4a1de3939adf39d1199a35ca2ee97fbd415700..b09e5c64576579d99c07709726ef3272d66dfa63 100644
--- a/src/libANGLE/renderer/ContextImpl.cpp
+++ b/src/libANGLE/renderer/ContextImpl.cpp
@@ -115,4 +115,13 @@ const angle::ShadingRateMap &ContextImpl::getSupportedFragmentShadingRateEXTSamp
     static angle::ShadingRateMap empty;
     return empty;
 }
+
+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 6ead468ab44287e7e02ff6cff9c8a5bc6dc5b453..b6b017138b69b2dd88f1d17370ba37e225636dab 100644
--- a/src/libANGLE/renderer/ContextImpl.h
+++ b/src/libANGLE/renderer/ContextImpl.h
@@ -282,6 +282,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 06f5faae870d706e748a5d66c75a19faf2ac21a8..0a7878520c8bf9602e91bc7b8c6916152a7c28e3 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -2517,6 +2517,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 6ffe10c8a32092fc878bc54c2dfa57dca48a831a..cdcdd54b065aadc728417fe2f34a079f0b01a1fb 100644
--- a/src/libANGLE/renderer/metal/BufferMtl.mm
+++ b/src/libANGLE/renderer/metal/BufferMtl.mm
@@ -404,7 +404,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 b9e9defcfc7710f28bb486f8dbd60430fabf758d..c2e93b49f666b52d88488c1893ee135958a21cbe 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.h
+++ b/src/libANGLE/renderer/metal/ContextMtl.h
@@ -273,6 +273,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,
@@ -556,6 +560,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,
@@ -649,6 +654,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 7fb29be9b780121a8026686d0cc408aadbe87a11..c0386f638df63962854624c09b2f274ec28350fa 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.mm
+++ b/src/libANGLE/renderer/metal/ContextMtl.mm
@@ -250,6 +250,8 @@ void ContextMtl::onDestroy(const gl::Context *context)
     mIncompleteTextures.onDestroy(context);
     mProvokingVertexHelper.onDestroy(this);
     mDummyXFBRenderTexture = nullptr;
+    mRasterizationRateMap.reset();
+    mRasterizationRateMapTexture = nil;
 
     mContextDevice.reset();
 }
@@ -1060,6 +1062,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;
 }
 
@@ -1398,6 +1407,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;
         }
@@ -1653,6 +1665,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,
@@ -2501,8 +2544,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;
 }
@@ -2611,10 +2668,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.
@@ -2639,6 +2700,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 84094329d83b54df93f1a7ad2154e3c26018acd3..a37c875a415bfe5dc2b72f42393dcf48b3c3e509 100644
--- a/src/libANGLE/renderer/metal/DisplayMtl.mm
+++ b/src/libANGLE/renderer/metal/DisplayMtl.mm
@@ -180,12 +180,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()
@@ -1130,6 +1140,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);
@@ -1201,6 +1220,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));
 
@@ -1391,6 +1412,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 41115775407bbd37101f9fa304c8c06e39e0367f..b016de77eb8704e26be220a35c8ba35f9485b656 100644
--- a/src/libANGLE/renderer/metal/FrameBufferMtl.mm
+++ b/src/libANGLE/renderer/metal/FrameBufferMtl.mm
@@ -132,6 +132,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)
 {
@@ -148,6 +151,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;
@@ -947,7 +958,7 @@ void FramebufferMtl::setLoadStoreActionOnRenderPassFirstStart(
         attachment.loadAction = MTLLoadActionLoad;
     }
 
-    if (attachment.hasImplicitMSTexture())
+    if (attachment.hasResolveTexture())
     {
         attachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
     }
@@ -1014,22 +1025,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,
@@ -1071,6 +1107,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
@@ -1078,6 +1117,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 =
@@ -1107,6 +1152,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());
     }
@@ -1118,6 +1169,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());
     }
@@ -1192,7 +1249,7 @@ angle::Result FramebufferMtl::clearWithLoadOpRenderPassNotStarted(
             colorAttachment.loadAction = MTLLoadActionLoad;
         }
 
-        if (colorAttachment.hasImplicitMSTexture())
+        if (colorAttachment.hasResolveTexture())
         {
             colorAttachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
         }
@@ -1212,7 +1269,7 @@ angle::Result FramebufferMtl::clearWithLoadOpRenderPassNotStarted(
         tempDesc.depthAttachment.loadAction = MTLLoadActionLoad;
     }
 
-    if (tempDesc.depthAttachment.hasImplicitMSTexture())
+    if (tempDesc.depthAttachment.hasResolveTexture())
     {
         tempDesc.depthAttachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
     }
@@ -1231,7 +1288,7 @@ angle::Result FramebufferMtl::clearWithLoadOpRenderPassNotStarted(
         tempDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
     }
 
-    if (tempDesc.stencilAttachment.hasImplicitMSTexture())
+    if (tempDesc.stencilAttachment.hasResolveTexture())
     {
         tempDesc.stencilAttachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
     }
@@ -1469,31 +1526,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);
         }
     }
 
@@ -1679,7 +1759,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);
@@ -1827,9 +1908,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;
         }
@@ -1837,9 +1917,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);
@@ -1854,19 +1934,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())
         {
@@ -1876,9 +1955,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 9eddf9f163d0985d89eedac5af09d266c86efafa..6d2ccd768127d4c3b5e9553f84b08dfc7cf73270 100644
--- a/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm
+++ b/src/libANGLE/renderer/metal/ProgramExecutableMtl.mm
@@ -881,7 +881,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 e57fb5018b02aede76376ae68f119fe777d5d2c8..ae55c29a36b1601308808bea4c01cb9094c677ce 100644
--- a/src/libANGLE/renderer/metal/SurfaceMtl.mm
+++ b/src/libANGLE/renderer/metal/SurfaceMtl.mm
@@ -672,8 +672,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 65283cdb644bb5b2bb2ded12addafaf7d4b78c3d..bb473e9e88c73ab2504a21f37df1da3eb6da179b 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 ImageDefinitionMtl &imageDef);
 
-    // 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 b887f5c2dfce5dcc04480660dc8884bb52eeb559..274588cb3d78eaefa26868f0b47cb4615093fece 100644
--- a/src/libANGLE/renderer/metal/VertexArrayMtl.mm
+++ b/src/libANGLE/renderer/metal/VertexArrayMtl.mm
@@ -1080,16 +1080,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 f0eed119c63cf7607771b8c53ec55db3dddcb70c..000d134075badc39959fb98e8471b7e72466cd17 100644
--- a/src/libANGLE/renderer/metal/mtl_command_buffer.h
+++ b/src/libANGLE/renderer/metal/mtl_command_buffer.h
@@ -122,6 +122,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,
@@ -166,6 +168,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
@@ -440,8 +444,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);
 
@@ -590,6 +595,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;
@@ -597,8 +604,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 913099d6f15adba261837de721a56774310b4c93..301b16efe2b8543edced6daf153a46df9e7ddfd7 100644
--- a/src/libANGLE/renderer/metal/mtl_command_buffer.mm
+++ b/src/libANGLE/renderer/metal/mtl_command_buffer.mm
@@ -651,6 +651,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)
     {
@@ -1365,6 +1377,7 @@ void RenderCommandEncoder::reset()
     CommandEncoder::reset();
     mRecording        = false;
     mPipelineStateSet = false;
+    setRasterizationRateMap(nil);
     mCommands.clear();
 }
 
@@ -1400,7 +1413,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())
     {
@@ -1544,13 +1557,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 =
@@ -1828,7 +1841,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)
     {
@@ -1841,12 +1855,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)};
@@ -1858,6 +1885,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;
@@ -2263,6 +2307,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)
 {
@@ -2358,6 +2416,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 193b445b8d699e650b80669ab017a9601bb31eb2..33d78f5582e644816278baaedce7a44840d208e1 100644
--- a/src/libANGLE/renderer/metal/mtl_format_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_format_utils.mm
@@ -165,6 +165,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 c11203e2a3543d10e24ba22655126f2c42acb3a9..4039dfe2b4484abdc5501d9097f5432dc8e42bd4 100644
--- a/src/libANGLE/renderer/metal/mtl_render_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_render_utils.mm
@@ -560,7 +560,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)
@@ -640,8 +641,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)
     {
@@ -1118,8 +1120,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;
@@ -2486,8 +2488,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 2fa27bd06fc7c1d88b4c7917d6ec130b28aa55c6..49805ece99a7ee4cd3fbe2999c52dc61a9052183 100644
--- a/src/libANGLE/renderer/metal/mtl_resources.mm
+++ b/src/libANGLE/renderer/metal/mtl_resources.mm
@@ -82,12 +82,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
     }
 }
 
@@ -709,7 +707,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
     {
@@ -718,7 +718,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();
@@ -727,6 +727,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 6392bda51e826e5346666e623f589a8b31231213..4839de8394d92d8a57060b37f9372679c0a281bc 100644
--- a/src/libANGLE/renderer/metal/mtl_state_cache.h
+++ b/src/libANGLE/renderer/metal/mtl_state_cache.h
@@ -309,20 +309,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 0e4ea318bbe119442322cdbf0de18bcd33bee8a0..9e6bb7a8e3980ae053d0935c28c3c83b92a8e7da 100644
--- a/src/libANGLE/renderer/metal/mtl_state_cache.mm
+++ b/src/libANGLE/renderer/metal/mtl_state_cache.mm
@@ -145,44 +145,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;
     }
 
@@ -672,9 +657,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;
@@ -684,7 +671,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 65060fcc1d9150643270dddd97e779365f5aea9c..5121e234ddab9bacfff2a019277cd4e5476beae5 100644
--- a/src/libANGLE/renderer/metal/mtl_utils.mm
+++ b/src/libANGLE/renderer/metal/mtl_utils.mm
@@ -1400,6 +1400,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
@@ -1445,8 +1463,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/AllocatorHelperPool.cpp b/src/libANGLE/renderer/vulkan/AllocatorHelperPool.cpp
index c7ea21b169cd27baaf9bd62cb320a54642a339f2..086756b248cfaf398524cf1d97f96cc813c512ea 100644
--- a/src/libANGLE/renderer/vulkan/AllocatorHelperPool.cpp
+++ b/src/libANGLE/renderer/vulkan/AllocatorHelperPool.cpp
@@ -49,7 +49,7 @@ bool DedicatedCommandBlockPool::empty() const
 void DedicatedCommandBlockPool::allocateNewBlock(size_t blockSize)
 {
     ASSERT(mAllocator);
-    mCurrentWritePointer   = mAllocator->fastAllocate(blockSize);
+    mCurrentWritePointer   = reinterpret_cast<uint8_t *>(mAllocator->allocate(blockSize));
     mCurrentBytesRemaining = blockSize;
     mCommandBuffer->pushToCommands(mCurrentWritePointer);
 }
diff --git a/src/libANGLE/renderer/vulkan/AllocatorHelperPool.h b/src/libANGLE/renderer/vulkan/AllocatorHelperPool.h
index ea37c1e33040e2c4b6f6870c39efa2f4f14d8cd9..166144cdaf22136e46c1c02de2a15029b0d4c778 100644
--- a/src/libANGLE/renderer/vulkan/AllocatorHelperPool.h
+++ b/src/libANGLE/renderer/vulkan/AllocatorHelperPool.h
@@ -40,10 +40,9 @@ class DedicatedCommandBlockAllocator
     DedicatedCommandMemoryAllocator *getAllocator() { return &mAllocator; }
 
   private:
-    static constexpr size_t kDefaultPoolAllocatorPageSize = 16 * 1024;
     // Using a pool allocator per CBH to avoid threading issues that occur w/ shared allocator
     // between multiple CBHs.
-    DedicatedCommandMemoryAllocator mAllocator{kDefaultPoolAllocatorPageSize, 1};
+    DedicatedCommandMemoryAllocator mAllocator;
 };
 
 // Used in SecondaryCommandBuffer
@@ -61,8 +60,8 @@ class DedicatedCommandBlockPool final
     using CommandHeaderIDType                  = uint16_t;
     // Make sure the size of command header ID type is less than total command header size.
     static_assert(sizeof(CommandHeaderIDType) < kCommandHeaderSize, "Check size of CommandHeader");
-    // Pool Alloc uses 16kB pages w/ 16byte header = 16368bytes. To minimize waste
-    //  using a 16368/12 = 1364. Also better perf than 1024 due to fewer block allocations
+    // PoolAllocator uses 32768 byte pools. To minimize waste using a 32768/24 = 1365.
+    // Also better perf than 1024 due to fewer block allocations.
     static constexpr size_t kBlockSize = 1360;
     // Make sure block size is 8-byte aligned to avoid ASAN errors.
     static_assert((kBlockSize % 8) == 0, "Check kBlockSize alignment");
diff --git a/src/libANGLE/renderer/vulkan/CLProgramVk.cpp b/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
index 1356800718e69009432e05ba2855ab3cde62feb9..d0938bdf079ff0cd84cc0a37ccf98156e5cbb350 100644
--- a/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/CLProgramVk.cpp
@@ -870,6 +870,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 bebab87247d9c9209e32098dd9c80957e2530d18..fb4f6e00358aef6041a4f1b8f9991ff29101a053 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -6072,6 +6072,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 009479275213b09114c4c849486282e40c3ba510..bc5083539a8f8ad1b2255c9cc1a68a15e7201122 100644
--- a/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
@@ -345,7 +345,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";
         ASSERT(false);
         return angle::FormatID::NONE;
diff --git a/src/libANGLE/validationEGL.cpp b/src/libANGLE/validationEGL.cpp
index f69bd954433d00a027f888ce86aa3c31ce7bb2e7..50481c4d6b2ee8619ce7f9ec1a3f66cfe0e44142 100644
--- a/src/libANGLE/validationEGL.cpp
+++ b/src/libANGLE/validationEGL.cpp
@@ -3492,11 +3492,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 ac1149299999daf9e697adde36d821ec81a8ae38..4b9f09f958cd62a108dc5ac2029cba24fb4e6cf3 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -704,6 +704,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;
     }
@@ -5933,6 +5936,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 aac8c8d654371ca359dc38338da67bdf3d68136a..e13641ce7e1f9e67c3eeffe3ca6a0c8d39917564 100644
--- a/src/libANGLE/validationESEXT.cpp
+++ b/src/libANGLE/validationESEXT.cpp
@@ -3913,6 +3913,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 26437c7f65e1daf6ed6e03149e7fcd529333abba..4cb0b89f721c8c9137f14b5bbcc8c475d77a7a2c 100644
--- a/src/libANGLE/validationESEXT_autogen.h
+++ b/src/libANGLE/validationESEXT_autogen.h
@@ -1008,6 +1008,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,
@@ -2966,6 +2972,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 b5905f3ff5cd8237e1780dfd298f631fe9fb260b..39c5116109faeefc4ed6178a0dcb095de8dd3c58 100644
--- a/src/libGLESv2/entry_points_gles_ext_autogen.cpp
+++ b/src/libGLESv2/entry_points_gles_ext_autogen.cpp
@@ -6850,6 +6850,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,
@@ -20617,4 +20666,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 31038dbb5e6e3447a310c9062fdb7e290044a0b8..d4f546aacd85b22007b02168605863320fed106d 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,
@@ -1993,6 +1997,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 c639ba94915155cde44266e86e9ed4208b828e5c..469e47dc38d61fb7c0281eedd6770f78b5eacf1a 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,
@@ -6151,4 +6158,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 e8460365a5fd3749cf6f0b6bbfe134efc531081c..2482ca6c10d9fb080f9f795453cb9c8495eb1c96 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
@@ -1303,6 +1306,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 2699a826a02ca9475a6a9f2a0a1014faf0e4b92a..4f75d2a4c64326758161505503ffa6117336e320 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
@@ -1303,6 +1306,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 b901572edc688f6e03462aab9336f222e96f812e..12a133119032c4847de1f7886c6a4ee881486c0e 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
@@ -1303,6 +1306,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 4a3ef946cb167e227a10f65bae141d7f5052696b..4392305d330e24001de260a0ad9cf40e26fbc6d0 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
@@ -1303,6 +1306,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 ba8174497c9d30f3d371cde8d8e3d5bb7eecb0a3..7b4601fe81f16f78f6cfad7547b0d9485b8c64bd 100644
--- a/src/tests/angle_end2end_tests.gni
+++ b/src/tests/angle_end2end_tests.gni
@@ -90,6 +90,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 31be8e39d1d191618162e559b84b4610b62c1973..9c35afd9c21d6af3a20e206d4637b33fc2523808 100644
--- a/src/tests/angle_unittest_main.cpp
+++ b/src/tests/angle_unittest_main.cpp
@@ -10,7 +10,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
 {
@@ -44,8 +46,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 1cc913612a0c4398b47c087f9dbbec049ce0909d..b1fbf775a1ce943277ecc196a53035247c07efff 100644
--- a/src/tests/gl_tests/CubeMapTextureTest.cpp
+++ b/src/tests/gl_tests/CubeMapTextureTest.cpp
@@ -11,7 +11,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 c9792173ca1729cf74a835d72f9df331833d9c4d..1c8da83d5d4cd44451baf3726ca5281925e754dc 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -597,6 +597,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;
@@ -6966,6 +6989,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
@@ -11042,6 +11123,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)
@@ -21468,6 +21586,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_ES31_AND_ES32(
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 249ac9bd9f61fc861487ebef064ce67aa8326f77..bcbf46da98cd4a5c7367fa020735d7e642bb7b32 100644
--- a/src/tests/gl_tests/ImageTestMetal.mm
+++ b/src/tests/gl_tests/ImageTestMetal.mm
@@ -19,7 +19,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 e16d5e0caefa7caea3e932fa78f21478052af0ff..7801fa39b2a80c452c22752057360efa4524ce6d 100644
--- a/src/tests/gl_tests/PbufferTest.cpp
+++ b/src/tests/gl_tests/PbufferTest.cpp
@@ -306,7 +306,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();
 
@@ -536,7 +535,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 992e31e56a8af614c3a9afafec563693edddaa54..dc6045bd1242f7c29a07f64ba39606a81e0fe854 100644
--- a/src/tests/gl_tests/PixelLocalStorageTest.cpp
+++ b/src/tests/gl_tests/PixelLocalStorageTest.cpp
@@ -10,6 +10,7 @@
 
 #include <sstream>
 #include <string>
+#include "common/string_utils.h"
 #include "test_utils/ANGLETest.h"
 #include "test_utils/gl_raii.h"
 
@@ -8561,7 +8562,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",
@@ -8581,44 +8581,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 521576e38550d6cf4cd03b27c9247e70d609f0c5..f731f1de96a7c808648c3c24eb19378bcdab8b09 100644
--- a/src/tests/gl_tests/ProvokingVertexTest.cpp
+++ b/src/tests/gl_tests/ProvokingVertexTest.cpp
@@ -16,7 +16,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 5beb889e3d6c1cb3146689ea60d943ec1591be88..ad6230076e459c6be9f107d5dfbc6cc6d8d26f22 100644
--- a/src/tests/gl_tests/RobustResourceInitTest.cpp
+++ b/src/tests/gl_tests/RobustResourceInitTest.cpp
@@ -13,7 +13,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 9714891f821e4c1ca01b9e6eebd70690a2f85a8f..e90e9a97754ab5aebcdcf7ad948ae5aa2de6cc12 100644
--- a/src/tests/gl_tests/SamplersTest.cpp
+++ b/src/tests/gl_tests/SamplersTest.cpp
@@ -15,7 +15,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 762324b02b48d46e15851086492f2ca6fed1f610..2ed9936d2f70628ea951d27be86b8ebb0d86ca0e 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -2005,6 +2005,7 @@ void Texture2DDepthStencilTestES3::TestSampleWithDepthStencilMode(GLenum format,
             break;
         default:
             UNREACHABLE();
+            return;
     }
 
     // Set up a color texture.
@@ -11392,7 +11393,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 d8f65dd2a11945044444063284551419a22c7e1e..f85b6d60dc5009dcbd798a9c492c24381abe05d2 100644
--- a/src/tests/gl_tests/TransformFeedbackTest.cpp
+++ b/src/tests/gl_tests/TransformFeedbackTest.cpp
@@ -11,7 +11,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 598669fa9183e15c5af5ae4758cd68ac6b21c6d1..a02865c78f6f5a995d429402e326c03b4260e6ce 100644
--- a/src/tests/gl_tests/UniformTest.cpp
+++ b/src/tests/gl_tests/UniformTest.cpp
@@ -13,7 +13,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>
@@ -2590,7 +2589,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 6f9b7936880fbc4ee4b679e648dd149c32086c82..67200b1a8d1e618828a4e994a56227883b82de8c 100644
--- a/util/autogen/angle_features_autogen.cpp
+++ b/util/autogen/angle_features_autogen.cpp
@@ -214,6 +214,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 efa668f0258ab3b77ed01aed0a97c231f462adee..4973d096498c6b01f246d338746047cc35ca1552 100644
--- a/util/autogen/angle_features_autogen.h
+++ b/util/autogen/angle_features_autogen.h
@@ -214,6 +214,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 caef131a0f80676830e964c7968b18d5a7b0e1ca..8c778e194bce4995254e6b2701240d3fbb78a12f 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 92dfa30ef88288bceddf4d4447ebeac2fa93311c..5ab5a5c9bd9823cea71cadb3a213817a17c8e0ba 100644
--- a/util/posix/test_utils_posix.cpp
+++ b/util/posix/test_utils_posix.cpp
@@ -360,7 +360,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);
 }
 
