Jelajahi Sumber

Adding Variable Rate Shading support to Godot
Improve GI renderer and add VRS support
Implement render device has_feature and move subgroup settings to limit_get

Bastiaan Olij 3 tahun lalu
induk
melakukan
d139131aab
44 mengubah file dengan 1571 tambahan dan 463 penghapusan
  1. 6 0
      doc/classes/ProjectSettings.xml
  2. 1 1
      doc/classes/RenderingDevice.xml
  3. 28 0
      doc/classes/RenderingServer.xml
  4. 18 0
      doc/classes/Viewport.xml
  5. 5 0
      doc/classes/XRInterfaceExtension.xml
  6. 10 0
      drivers/gles3/storage/texture_storage.h
  7. 322 181
      drivers/vulkan/rendering_device_vulkan.cpp
  8. 4 1
      drivers/vulkan/rendering_device_vulkan.h
  9. 103 14
      drivers/vulkan/vulkan_context.cpp
  10. 15 0
      drivers/vulkan/vulkan_context.h
  11. 19 15
      modules/glslang/register_types.cpp
  12. 24 0
      scene/main/scene_tree.cpp
  13. 55 0
      scene/main/viewport.cpp
  14. 21 0
      scene/main/viewport.h
  15. 3 0
      servers/rendering/dummy/storage/texture_storage.h
  16. 5 5
      servers/rendering/renderer_rd/effects/copy_effects.cpp
  17. 171 0
      servers/rendering/renderer_rd/effects/vrs.cpp
  18. 75 0
      servers/rendering/renderer_rd/effects/vrs.h
  19. 1 1
      servers/rendering/renderer_rd/effects_rd.cpp
  20. 120 88
      servers/rendering/renderer_rd/environment/gi.cpp
  21. 15 18
      servers/rendering/renderer_rd/environment/gi.h
  22. 40 62
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
  23. 6 2
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
  24. 27 11
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
  25. 3 1
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
  26. 35 7
      servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
  27. 7 2
      servers/rendering/renderer_rd/renderer_scene_render_rd.h
  28. 5 3
      servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl
  29. 72 0
      servers/rendering/renderer_rd/shaders/effects/vrs.glsl
  30. 125 33
      servers/rendering/renderer_rd/shaders/environment/gi.glsl
  31. 51 2
      servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
  32. 11 0
      servers/rendering/renderer_rd/storage_rd/texture_storage.h
  33. 16 0
      servers/rendering/renderer_viewport.cpp
  34. 3 0
      servers/rendering/renderer_viewport.h
  35. 3 2
      servers/rendering/rendering_device.cpp
  36. 16 12
      servers/rendering/rendering_device.h
  37. 3 0
      servers/rendering/rendering_server_default.h
  38. 3 0
      servers/rendering/storage/texture_storage.h
  39. 8 0
      servers/rendering_server.cpp
  40. 11 0
      servers/rendering_server.h
  41. 86 2
      servers/xr/xr_interface.cpp
  42. 7 0
      servers/xr/xr_interface.h
  43. 10 0
      servers/xr/xr_interface_extension.cpp
  44. 2 0
      servers/xr/xr_interface_extension.h

+ 6 - 0
doc/classes/ProjectSettings.xml

@@ -1968,6 +1968,12 @@
 			If [code]true[/code], the texture importer will import VRAM-compressed textures using the S3 Texture Compression algorithm. This algorithm is only supported on desktop platforms and consoles.
 			[b]Note:[/b] Changing this setting does [i]not[/i] impact textures that were already imported before. To make this setting apply to textures that were already imported, exit the editor, remove the [code].godot/imported/[/code] folder located inside the project folder then restart the editor (see [member application/config/use_hidden_project_data_directory]).
 		</member>
+		<member name="rendering/vrs/mode" type="int" setter="" getter="" default="0">
+			Set the default Variable Rate Shading (VRS) mode for the main viewport. See [member Viewport.vrs_mode] to change this at runtime, and [enum Viewport.VRSMode] for possible values.
+		</member>
+		<member name="rendering/vrs/texture" type="String" setter="" getter="" default="&quot;&quot;">
+			If [member rendering/vrs/mode] is set to texture, this is the path to default texture loaded as the VRS image.
+		</member>
 		<member name="rendering/vulkan/descriptor_pools/max_descriptors_per_pool" type="int" setter="" getter="" default="64">
 		</member>
 		<member name="rendering/vulkan/rendering/back_end" type="int" setter="" getter="" default="0">

+ 1 - 1
doc/classes/RenderingDevice.xml

@@ -395,7 +395,7 @@
 			<description>
 			</description>
 		</method>
-		<method name="limit_get">
+		<method name="limit_get" qualifiers="const">
 			<return type="int" />
 			<argument index="0" name="limit" type="int" enum="RenderingDevice.Limit" />
 			<description>

+ 28 - 0
doc/classes/RenderingServer.xml

@@ -3357,6 +3357,22 @@
 				If [code]true[/code], the viewport uses augmented or virtual reality technologies. See [XRInterface].
 			</description>
 		</method>
+		<method name="viewport_set_vrs_mode">
+			<return type="void" />
+			<argument index="0" name="viewport" type="RID" />
+			<argument index="1" name="mode" type="int" enum="RenderingServer.ViewportVRSMode" />
+			<description>
+				Sets the Variable Rate Shading (VRS) mode for the viewport. Note, if hardware does not support VRS this property is ignored.
+			</description>
+		</method>
+		<method name="viewport_set_vrs_texture">
+			<return type="void" />
+			<argument index="0" name="viewport" type="RID" />
+			<argument index="1" name="texture" type="RID" />
+			<description>
+				Texture to use when the VRS mode is set to [constant RenderingServer.VIEWPORT_VRS_TEXTURE].
+			</description>
+		</method>
 		<method name="visibility_notifier_create">
 			<return type="RID" />
 			<description>
@@ -4116,6 +4132,18 @@
 		</constant>
 		<constant name="VIEWPORT_DEBUG_DRAW_MOTION_VECTORS" value="25" enum="ViewportDebugDraw">
 		</constant>
+		<constant name="VIEWPORT_VRS_DISABLED" value="0" enum="ViewportVRSMode">
+			VRS is disabled.
+		</constant>
+		<constant name="VIEWPORT_VRS_TEXTURE" value="1" enum="ViewportVRSMode">
+			VRS uses a texture. Note, for stereoscopic use a texture atlas with a texture for each view.
+		</constant>
+		<constant name="VIEWPORT_VRS_XR" value="2" enum="ViewportVRSMode">
+			VRS texture is supplied by the primary [XRInterface].
+		</constant>
+		<constant name="VIEWPORT_VRS_MAX" value="3" enum="ViewportVRSMode">
+			Represents the size of the [enum ViewportVRSMode] enum.
+		</constant>
 		<constant name="SKY_MODE_AUTOMATIC" value="0" enum="SkyMode">
 		</constant>
 		<constant name="SKY_MODE_QUALITY" value="1" enum="SkyMode">

+ 18 - 0
doc/classes/Viewport.xml

@@ -286,6 +286,12 @@
 		<member name="use_xr" type="bool" setter="set_use_xr" getter="is_using_xr" default="false">
 			If [code]true[/code], the viewport will use the primary XR interface to render XR output. When applicable this can result in a stereoscopic image and the resulting render being output to a headset.
 		</member>
+		<member name="vrs_mode" type="int" setter="set_vrs_mode" getter="get_vrs_mode" enum="Viewport.VRSMode" default="0">
+			The Variable Rate Shading (VRS) mode that is used for this viewport. Note, if hardware does not support VRS this property is ignored.
+		</member>
+		<member name="vrs_texture" type="Texture2D" setter="set_vrs_texture" getter="get_vrs_texture">
+			Texture to use when [member vrs_mode] is set to [constant Viewport.VRS_TEXTURE].
+		</member>
 		<member name="world_2d" type="World2D" setter="set_world_2d" getter="get_world_2d">
 			The custom [World2D] which can be used as 2D environment source.
 		</member>
@@ -492,5 +498,17 @@
 		</constant>
 		<constant name="SDF_SCALE_MAX" value="3" enum="SDFScale">
 		</constant>
+		<constant name="VRS_DISABLED" value="0" enum="VRSMode">
+			VRS is disabled.
+		</constant>
+		<constant name="VRS_TEXTURE" value="1" enum="VRSMode">
+			VRS uses a texture. Note, for stereoscopic use a texture atlas with a texture for each view.
+		</constant>
+		<constant name="VRS_XR" value="2" enum="VRSMode">
+			VRS texture is supplied by the primary [XRInterface].
+		</constant>
+		<constant name="VRS_MAX" value="3" enum="VRSMode">
+			Represents the size of the [enum VRSMode] enum.
+		</constant>
 	</constants>
 </class>

+ 5 - 0
doc/classes/XRInterfaceExtension.xml

@@ -106,6 +106,11 @@
 				Returns the number of views this interface requires, 1 for mono, 2 for stereoscopic.
 			</description>
 		</method>
+		<method name="_get_vrs_texture" qualifiers="virtual">
+			<return type="RID" />
+			<description>
+			</description>
+		</method>
 		<method name="_initialize" qualifiers="virtual">
 			<return type="bool" />
 			<description>

+ 10 - 0
drivers/gles3/storage/texture_storage.h

@@ -546,6 +546,16 @@ public:
 	void render_target_copy_to_back_buffer(RID p_render_target, const Rect2i &p_region, bool p_gen_mipmaps);
 	void render_target_clear_back_buffer(RID p_render_target, const Rect2i &p_region, const Color &p_color);
 	void render_target_gen_back_buffer_mipmaps(RID p_render_target, const Rect2i &p_region);
+	virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override{};
+	virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override{};
+
+	void bind_framebuffer(GLuint framebuffer) {
+		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+	}
+
+	void bind_framebuffer_system() {
+		glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
+	}
 
 	String get_framebuffer_error(GLenum p_status);
 };

+ 322 - 181
drivers/vulkan/rendering_device_vulkan.cpp

@@ -106,7 +106,7 @@ RenderingDeviceVulkan::Buffer *RenderingDeviceVulkan::_get_buffer_from_owner(RID
 	return buffer;
 }
 
-static void update_external_dependency_for_store(VkSubpassDependency &dependency, bool is_sampled, bool is_storage, bool is_depth) {
+static void update_external_dependency_for_store(VkSubpassDependency2KHR &dependency, bool is_sampled, bool is_storage, bool is_depth) {
 	// Transitioning from write to read, protect the shaders that may use this next
 	// Allow for copies/image layout transitions
 	dependency.dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT;
@@ -1758,6 +1758,10 @@ RID RenderingDeviceVulkan::texture_create(const TextureFormat &p_format, const T
 		image_create_info.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
 	}
 
+	if (p_format.usage_bits & TEXTURE_USAGE_VRS_ATTACHMENT_BIT) {
+		image_create_info.usage |= VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
+	}
+
 	if (p_format.usage_bits & TEXTURE_USAGE_CAN_UPDATE_BIT) {
 		image_create_info.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
 	}
@@ -3362,17 +3366,24 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 			VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
 			VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
 			VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
-			VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; // From Section 7.1 of Vulkan API Spec v1.1.148
+			VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | // From Section 7.1 of Vulkan API Spec v1.1.148
+			VK_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR;
 
 	VkPipelineStageFlags reading_stages = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
-	VkSubpassDependency dependencies[2] = { { VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, default_access_mask, 0 },
-		{ 0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, default_access_mask, 0, 0 } };
-	VkSubpassDependency &dependency_from_external = dependencies[0];
-	VkSubpassDependency &dependency_to_external = dependencies[1];
+	VkSubpassDependency2KHR dependencies[2] = {
+		{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR, nullptr, VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, default_access_mask, 0, 0 },
+		{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR, nullptr, 0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, default_access_mask, 0, 0, 0 }
+	};
+	VkSubpassDependency2KHR &dependency_from_external = dependencies[0];
+	VkSubpassDependency2KHR &dependency_to_external = dependencies[1];
 	LocalVector<int32_t> attachment_last_pass;
 	attachment_last_pass.resize(p_attachments.size());
 
-	Vector<VkAttachmentDescription> attachments;
+	// These are only used if we use multiview but we need to define them in scope.
+	const uint32_t view_mask = (1 << p_view_count) - 1;
+	const uint32_t correlation_mask = (1 << p_view_count) - 1;
+
+	Vector<VkAttachmentDescription2KHR> attachments;
 	Vector<int> attachment_remap;
 
 	for (int i = 0; i < p_attachments.size(); i++) {
@@ -3383,10 +3394,12 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 
 		ERR_FAIL_INDEX_V(p_attachments[i].format, DATA_FORMAT_MAX, VK_NULL_HANDLE);
 		ERR_FAIL_INDEX_V(p_attachments[i].samples, TEXTURE_SAMPLES_MAX, VK_NULL_HANDLE);
-		ERR_FAIL_COND_V_MSG(!(p_attachments[i].usage_flags & (TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | TEXTURE_USAGE_INPUT_ATTACHMENT_BIT)),
+		ERR_FAIL_COND_V_MSG(!(p_attachments[i].usage_flags & (TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | TEXTURE_USAGE_INPUT_ATTACHMENT_BIT | TEXTURE_USAGE_VRS_ATTACHMENT_BIT)),
 				VK_NULL_HANDLE, "Texture format for index (" + itos(i) + ") requires an attachment (color, depth, input or stencil) bit set.");
 
-		VkAttachmentDescription description = {};
+		VkAttachmentDescription2KHR description = {};
+		description.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR;
+		description.pNext = nullptr;
 		description.flags = 0;
 		description.format = vulkan_formats[p_attachments[i].format];
 		description.samples = rasterization_sample_count[p_attachments[i].samples];
@@ -3395,83 +3408,95 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 		bool is_storage = p_attachments[i].usage_flags & TEXTURE_USAGE_STORAGE_BIT;
 		bool is_depth = p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
 
-		// For each UNDEFINED, assume the prior use was a *read*, as we'd be discarding the output of a write
-		// Also, each UNDEFINED will do an immediate layout transition (write), s.t. we must ensure execution synchronization vs.
-		// the read. If this is a performance issue, one could track the actual last accessor of each resource, adding only that
-		// stage
-
-		switch (is_depth ? p_initial_depth_action : p_initial_action) {
-			case INITIAL_ACTION_CLEAR_REGION:
-			case INITIAL_ACTION_CLEAR: {
-				if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
-					description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-				} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
-					description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
-					dependency_from_external.srcStageMask |= reading_stages;
-				} else {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-					dependency_from_external.srcStageMask |= reading_stages;
-				}
-			} break;
-			case INITIAL_ACTION_KEEP: {
-				if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
-					description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-				} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
-					description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
-					dependency_from_external.srcStageMask |= reading_stages;
-				} else {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-					dependency_from_external.srcStageMask |= reading_stages;
-				}
-			} break;
-			case INITIAL_ACTION_DROP: {
-				if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-				} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					dependency_from_external.srcStageMask |= reading_stages;
-				} else {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-					dependency_from_external.srcStageMask |= reading_stages;
-				}
-			} break;
-			case INITIAL_ACTION_CLEAR_REGION_CONTINUE:
-			case INITIAL_ACTION_CONTINUE: {
-				if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
-					description.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-				} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
-					description.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
-				} else {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-					dependency_from_external.srcStageMask |= reading_stages;
+		// We can setup a framebuffer where we write to our VRS texture to set it up.
+		// We make the assumption here that if our texture is actually used as our VRS attachment,
+		// it is used as such for each subpass. This is fairly certain seeing the restrictions on subpasses.
+		bool is_vrs = p_attachments[i].usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT && i == p_passes[0].vrs_attachment;
+
+		if (is_vrs) {
+			// For VRS we only read, there is no writing to this texture
+			description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+			description.initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+		} else {
+			// For each UNDEFINED, assume the prior use was a *read*, as we'd be discarding the output of a write
+			// Also, each UNDEFINED will do an immediate layout transition (write), s.t. we must ensure execution synchronization vs.
+			// the read. If this is a performance issue, one could track the actual last accessor of each resource, adding only that
+			// stage
+
+			switch (is_depth ? p_initial_depth_action : p_initial_action) {
+				case INITIAL_ACTION_CLEAR_REGION:
+				case INITIAL_ACTION_CLEAR: {
+					if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+						description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+					} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+						description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+						dependency_from_external.srcStageMask |= reading_stages;
+					} else {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+						dependency_from_external.srcStageMask |= reading_stages;
+					}
+				} break;
+				case INITIAL_ACTION_KEEP: {
+					if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+						description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+					} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+						description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+						dependency_from_external.srcStageMask |= reading_stages;
+					} else {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+						dependency_from_external.srcStageMask |= reading_stages;
+					}
+				} break;
+				case INITIAL_ACTION_DROP: {
+					if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+					} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						dependency_from_external.srcStageMask |= reading_stages;
+					} else {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+						dependency_from_external.srcStageMask |= reading_stages;
+					}
+				} break;
+				case INITIAL_ACTION_CLEAR_REGION_CONTINUE:
+				case INITIAL_ACTION_CONTINUE: {
+					if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+						description.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+					} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+						description.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+					} else {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+						dependency_from_external.srcStageMask |= reading_stages;
+					}
+				} break;
+				default: {
+					ERR_FAIL_V(VK_NULL_HANDLE); //should never reach here
 				}
-			} break;
-			default: {
-				ERR_FAIL_V(VK_NULL_HANDLE); //should never reach here
 			}
 		}
 
@@ -3485,6 +3510,10 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 				if (p_passes[last_pass].depth_attachment == i) {
 					used_last = true;
 				}
+			} else if (is_vrs) {
+				if (p_passes[last_pass].vrs_attachment == i) {
+					used_last = true;
+				}
 			} else {
 				if (p_passes[last_pass].resolve_attachments.size()) {
 					//if using resolve attachments, check resolve attachments
@@ -3526,58 +3555,69 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 			}
 		}
 
-		switch (is_depth ? final_depth_action : final_action) {
-			case FINAL_ACTION_READ: {
-				if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
-					description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
-					description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
-					description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
-					update_external_dependency_for_store(dependency_to_external, is_sampled, is_storage, false);
-				} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
-					description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
-					description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
-					description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
-					update_external_dependency_for_store(dependency_to_external, is_sampled, is_storage, true);
-				} else {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-					// TODO: What does this mean about the next usage (and thus appropriate dependency masks
-				}
-			} break;
-			case FINAL_ACTION_DISCARD: {
-				if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
-					description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
-					description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
-					description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
-				} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
-					description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
-					description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
-					description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
-				} else {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-				}
-			} break;
-			case FINAL_ACTION_CONTINUE: {
-				if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
-					description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
-					description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
-					description.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-				} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
-					description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
-					description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
-					description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-				} else {
-					description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-					description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
-				}
+		if (is_vrs) {
+			// We don't change our VRS texture during this process
 
-			} break;
-			default: {
-				ERR_FAIL_V(VK_NULL_HANDLE); //should never reach here
+			description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			description.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			// TODO do we need to update our external dependency ?
+			// update_external_dependency_for_store(dependency_to_external, is_sampled, is_storage, false);
+		} else {
+			switch (is_depth ? final_depth_action : final_action) {
+				case FINAL_ACTION_READ: {
+					if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+						update_external_dependency_for_store(dependency_to_external, is_sampled, is_storage, false);
+					} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
+						description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+						update_external_dependency_for_store(dependency_to_external, is_sampled, is_storage, true);
+					} else {
+						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+						description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+						// TODO: What does this mean about the next usage (and thus appropriate dependency masks
+					}
+				} break;
+				case FINAL_ACTION_DISCARD: {
+					if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+					} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+					} else {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+					}
+				} break;
+				case FINAL_ACTION_CONTINUE: {
+					if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+					} else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
+						description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+					} else {
+						description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+						description.finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
+					}
+
+				} break;
+				default: {
+					ERR_FAIL_V(VK_NULL_HANDLE); //should never reach here
+				}
 			}
 		}
 
@@ -3586,12 +3626,14 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 		attachments.push_back(description);
 	}
 
-	LocalVector<VkSubpassDescription> subpasses;
-	LocalVector<LocalVector<VkAttachmentReference>> color_reference_array;
-	LocalVector<LocalVector<VkAttachmentReference>> input_reference_array;
-	LocalVector<LocalVector<VkAttachmentReference>> resolve_reference_array;
+	LocalVector<VkSubpassDescription2KHR> subpasses;
+	LocalVector<LocalVector<VkAttachmentReference2KHR>> color_reference_array;
+	LocalVector<LocalVector<VkAttachmentReference2KHR>> input_reference_array;
+	LocalVector<LocalVector<VkAttachmentReference2KHR>> resolve_reference_array;
 	LocalVector<LocalVector<uint32_t>> preserve_reference_array;
-	LocalVector<VkAttachmentReference> depth_reference_array;
+	LocalVector<VkAttachmentReference2KHR> depth_reference_array;
+	LocalVector<VkAttachmentReference2KHR> vrs_reference_array;
+	LocalVector<VkFragmentShadingRateAttachmentInfoKHR> vrs_attachment_info_array;
 
 	subpasses.resize(p_passes.size());
 	color_reference_array.resize(p_passes.size());
@@ -3599,20 +3641,25 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 	resolve_reference_array.resize(p_passes.size());
 	preserve_reference_array.resize(p_passes.size());
 	depth_reference_array.resize(p_passes.size());
+	vrs_reference_array.resize(p_passes.size());
+	vrs_attachment_info_array.resize(p_passes.size());
 
-	LocalVector<VkSubpassDependency> subpass_dependencies;
+	LocalVector<VkSubpassDependency2KHR> subpass_dependencies;
 
 	for (int i = 0; i < p_passes.size(); i++) {
 		const FramebufferPass *pass = &p_passes[i];
 
-		LocalVector<VkAttachmentReference> &color_references = color_reference_array[i];
+		LocalVector<VkAttachmentReference2KHR> &color_references = color_reference_array[i];
 
 		TextureSamples texture_samples = TEXTURE_SAMPLES_1;
 		bool is_multisample_first = true;
+		void *subpass_nextptr = nullptr;
 
 		for (int j = 0; j < pass->color_attachments.size(); j++) {
 			int32_t attachment = pass->color_attachments[j];
-			VkAttachmentReference reference;
+			VkAttachmentReference2KHR reference;
+			reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+			reference.pNext = nullptr;
 			if (attachment == FramebufferPass::ATTACHMENT_UNUSED) {
 				reference.attachment = VK_ATTACHMENT_UNUSED;
 				reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -3631,14 +3678,17 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 				reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 				attachment_last_pass[attachment] = i;
 			}
+			reference.aspectMask = 0;
 			color_references.push_back(reference);
 		}
 
-		LocalVector<VkAttachmentReference> &input_references = input_reference_array[i];
+		LocalVector<VkAttachmentReference2KHR> &input_references = input_reference_array[i];
 
 		for (int j = 0; j < pass->input_attachments.size(); j++) {
 			int32_t attachment = pass->input_attachments[j];
-			VkAttachmentReference reference;
+			VkAttachmentReference2KHR reference;
+			reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+			reference.pNext = nullptr;
 			if (attachment == FramebufferPass::ATTACHMENT_UNUSED) {
 				reference.attachment = VK_ATTACHMENT_UNUSED;
 				reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -3650,10 +3700,11 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 				reference.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 				attachment_last_pass[attachment] = i;
 			}
+			reference.aspectMask = 0; // TODO we need to set this here, possibly VK_IMAGE_ASPECT_COLOR_BIT ??
 			input_references.push_back(reference);
 		}
 
-		LocalVector<VkAttachmentReference> &resolve_references = resolve_reference_array[i];
+		LocalVector<VkAttachmentReference2KHR> &resolve_references = resolve_reference_array[i];
 
 		if (pass->resolve_attachments.size() > 0) {
 			ERR_FAIL_COND_V_MSG(pass->resolve_attachments.size() != pass->color_attachments.size(), VK_NULL_HANDLE, "The amount of resolve attachments (" + itos(pass->resolve_attachments.size()) + ") must match the number of color attachments (" + itos(pass->color_attachments.size()) + ").");
@@ -3661,7 +3712,9 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 		}
 		for (int j = 0; j < pass->resolve_attachments.size(); j++) {
 			int32_t attachment = pass->resolve_attachments[j];
-			VkAttachmentReference reference;
+			VkAttachmentReference2KHR reference;
+			reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+			reference.pNext = nullptr;
 			if (attachment == FramebufferPass::ATTACHMENT_UNUSED) {
 				reference.attachment = VK_ATTACHMENT_UNUSED;
 				reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -3676,10 +3729,13 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 				reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 				attachment_last_pass[attachment] = i;
 			}
+			reference.aspectMask = 0;
 			resolve_references.push_back(reference);
 		}
 
-		VkAttachmentReference &depth_stencil_reference = depth_reference_array[i];
+		VkAttachmentReference2KHR &depth_stencil_reference = depth_reference_array[i];
+		depth_stencil_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+		depth_stencil_reference.pNext = nullptr;
 
 		if (pass->depth_attachment != FramebufferPass::ATTACHMENT_UNUSED) {
 			int32_t attachment = pass->depth_attachment;
@@ -3688,6 +3744,7 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 			ERR_FAIL_COND_V_MSG(attachment_last_pass[attachment] == i, VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it already was used for something else before in this pass.");
 			depth_stencil_reference.attachment = attachment_remap[attachment];
 			depth_stencil_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+			depth_stencil_reference.aspectMask = 0;
 			attachment_last_pass[attachment] = i;
 
 			if (is_multisample_first) {
@@ -3702,6 +3759,32 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 			depth_stencil_reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
 		}
 
+		if (context->get_vrs_capabilities().attachment_vrs_supported && pass->vrs_attachment != FramebufferPass::ATTACHMENT_UNUSED) {
+			int32_t attachment = pass->vrs_attachment;
+			ERR_FAIL_INDEX_V_MSG(attachment, p_attachments.size(), VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), depth attachment.");
+			ERR_FAIL_COND_V_MSG(!(p_attachments[attachment].usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT), VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it's marked as vrs, but it's not a vrs attachment.");
+			ERR_FAIL_COND_V_MSG(attachment_last_pass[attachment] == i, VK_NULL_HANDLE, "Invalid framebuffer vrs attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it already was used for something else before in this pass.");
+
+			VkAttachmentReference2KHR &vrs_reference = vrs_reference_array[i];
+			vrs_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+			vrs_reference.pNext = nullptr;
+			vrs_reference.attachment = attachment_remap[attachment];
+			vrs_reference.layout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
+			vrs_reference.aspectMask = 0;
+
+			Size2i texel_size = context->get_vrs_capabilities().max_texel_size;
+
+			VkFragmentShadingRateAttachmentInfoKHR &vrs_attachment_info = vrs_attachment_info_array[i];
+			vrs_attachment_info.sType = VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR;
+			vrs_attachment_info.pNext = nullptr;
+			vrs_attachment_info.pFragmentShadingRateAttachment = &vrs_reference;
+			vrs_attachment_info.shadingRateAttachmentTexelSize = { uint32_t(texel_size.x), uint32_t(texel_size.y) };
+
+			attachment_last_pass[attachment] = i;
+
+			subpass_nextptr = &vrs_attachment_info;
+		}
+
 		LocalVector<uint32_t> &preserve_references = preserve_reference_array[i];
 
 		for (int j = 0; j < pass->preserve_attachments.size(); j++) {
@@ -3718,9 +3801,12 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 			}
 		}
 
-		VkSubpassDescription &subpass = subpasses[i];
+		VkSubpassDescription2KHR &subpass = subpasses[i];
+		subpass.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
+		subpass.pNext = subpass_nextptr;
 		subpass.flags = 0;
 		subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass.viewMask = view_mask;
 		subpass.inputAttachmentCount = input_references.size();
 		if (input_references.size()) {
 			subpass.pInputAttachments = input_references.ptr();
@@ -3757,7 +3843,9 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 		}
 
 		if (i > 0) {
-			VkSubpassDependency dependency;
+			VkSubpassDependency2KHR dependency;
+			dependency.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2_KHR;
+			dependency.pNext = nullptr;
 			dependency.srcSubpass = i - 1;
 			dependency.dstSubpass = i;
 			dependency.srcStageMask = 0;
@@ -3767,6 +3855,7 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 			dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
 			dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
 			dependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+			dependency.viewOffset = 0;
 			subpass_dependencies.push_back(dependency);
 		}
 		/*
@@ -3784,10 +3873,11 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 		*/
 	}
 
-	VkRenderPassCreateInfo render_pass_create_info;
-	render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+	VkRenderPassCreateInfo2KHR render_pass_create_info;
+	render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
 	render_pass_create_info.pNext = nullptr;
 	render_pass_create_info.flags = 0;
+
 	render_pass_create_info.attachmentCount = attachments.size();
 	render_pass_create_info.pAttachments = attachments.ptr();
 	render_pass_create_info.subpassCount = subpasses.size();
@@ -3804,13 +3894,15 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 		render_pass_create_info.pDependencies = nullptr;
 	}
 
-	// These are only used if we use multiview but we need to define them in scope.
-	const uint32_t view_mask = (1 << p_view_count) - 1;
-	const uint32_t correlation_mask = (1 << p_view_count) - 1;
+	render_pass_create_info.correlatedViewMaskCount = 1;
+	render_pass_create_info.pCorrelatedViewMasks = &correlation_mask;
+
 	Vector<uint32_t> view_masks;
 	VkRenderPassMultiviewCreateInfo render_pass_multiview_create_info;
 
 	if (p_view_count > 1) {
+		// this may no longer be needed with the new settings already including this
+
 		const VulkanContext::MultiviewCapabilities capabilities = context->get_multiview_capabilities();
 
 		// For now this only works with multiview!
@@ -3837,8 +3929,8 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 	}
 
 	VkRenderPass render_pass;
-	VkResult res = vkCreateRenderPass(device, &render_pass_create_info, nullptr, &render_pass);
-	ERR_FAIL_COND_V_MSG(res, VK_NULL_HANDLE, "vkCreateRenderPass failed with error " + itos(res) + ".");
+	VkResult res = context->vkCreateRenderPass2KHR(device, &render_pass_create_info, nullptr, &render_pass);
+	ERR_FAIL_COND_V_MSG(res, VK_NULL_HANDLE, "vkCreateRenderPass2KHR failed with error " + itos(res) + ".");
 
 	return render_pass;
 }
@@ -3899,7 +3991,9 @@ RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_c
 		return E->get();
 	}
 
-	VkSubpassDescription subpass;
+	VkSubpassDescription2KHR subpass;
+	subpass.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
+	subpass.pNext = nullptr;
 	subpass.flags = 0;
 	subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
 	subpass.inputAttachmentCount = 0; //unsupported for now
@@ -3911,8 +4005,8 @@ RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_c
 	subpass.preserveAttachmentCount = 0;
 	subpass.pPreserveAttachments = nullptr;
 
-	VkRenderPassCreateInfo render_pass_create_info;
-	render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+	VkRenderPassCreateInfo2KHR render_pass_create_info;
+	render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
 	render_pass_create_info.pNext = nullptr;
 	render_pass_create_info.flags = 0;
 	render_pass_create_info.attachmentCount = 0;
@@ -3923,9 +4017,9 @@ RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_c
 	render_pass_create_info.pDependencies = nullptr;
 
 	VkRenderPass render_pass;
-	VkResult res = vkCreateRenderPass(device, &render_pass_create_info, nullptr, &render_pass);
+	VkResult res = context->vkCreateRenderPass2KHR(device, &render_pass_create_info, nullptr, &render_pass);
 
-	ERR_FAIL_COND_V_MSG(res, 0, "vkCreateRenderPass for empty fb failed with error " + itos(res) + ".");
+	ERR_FAIL_COND_V_MSG(res, 0, "vkCreateRenderPass2KHR for empty fb failed with error " + itos(res) + ".");
 
 	if (render_pass == VK_NULL_HANDLE) { //was likely invalid
 		return INVALID_ID;
@@ -3978,6 +4072,8 @@ RID RenderingDeviceVulkan::framebuffer_create(const Vector<RID> &p_texture_attac
 
 		if (texture && texture->usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
 			pass.depth_attachment = i;
+		} else if (texture && texture->usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT) {
+			pass.vrs_attachment = i;
 		} else {
 			pass.color_attachments.push_back(texture ? i : FramebufferPass::ATTACHMENT_UNUSED);
 		}
@@ -4008,6 +4104,10 @@ RID RenderingDeviceVulkan::framebuffer_create_multipass(const Vector<RID> &p_tex
 				size.width = texture->width;
 				size.height = texture->height;
 				size_set = true;
+			} else if (texture->usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT) {
+				// If this is not the first attachement we assume this is used as the VRS attachment
+				// in this case this texture will be 1/16th the size of the color attachement.
+				// So we skip the size check
 			} else {
 				ERR_FAIL_COND_V_MSG((uint32_t)size.width != texture->width || (uint32_t)size.height != texture->height, RID(),
 						"All textures in a framebuffer should be the same size.");
@@ -6552,11 +6652,28 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
 	dynamic_state_create_info.dynamicStateCount = dynamic_states.size();
 	dynamic_state_create_info.pDynamicStates = dynamic_states.ptr();
 
+	void *graphics_pipeline_nextptr = nullptr;
+
+	VkPipelineFragmentShadingRateStateCreateInfoKHR vrs_create_info;
+	if (context->get_vrs_capabilities().attachment_vrs_supported) {
+		// If VRS is used, this defines how the different VRS types are combined.
+		// combinerOps[0] decides how we use the output of pipeline and primitive (drawcall) VRS
+		// combinerOps[1] decides how we use the output of combinerOps[0] and our attachment VRS
+
+		vrs_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_STATE_CREATE_INFO_KHR;
+		vrs_create_info.pNext = nullptr;
+		vrs_create_info.fragmentSize = { 4, 4 };
+		vrs_create_info.combinerOps[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR; // We don't use pipeline/primitive VRS so this really doesn't matter
+		vrs_create_info.combinerOps[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR; // always use the outcome of attachment VRS if enabled
+
+		graphics_pipeline_nextptr = &vrs_create_info;
+	}
+
 	//finally, pipeline create info
 	VkGraphicsPipelineCreateInfo graphics_pipeline_create_info;
 
 	graphics_pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
-	graphics_pipeline_create_info.pNext = nullptr;
+	graphics_pipeline_create_info.pNext = graphics_pipeline_nextptr;
 	graphics_pipeline_create_info.flags = 0;
 
 	Vector<VkPipelineShaderStageCreateInfo> pipeline_stages = shader->pipeline_stages;
@@ -6721,7 +6838,7 @@ RID RenderingDeviceVulkan::compute_pipeline_create(RID p_shader, const Vector<Pi
 				const PipelineSpecializationConstant &psc = p_specialization_constants[j];
 				if (psc.constant_id == sc.constant.constant_id) {
 					ERR_FAIL_COND_V_MSG(psc.type != sc.constant.type, RID(), "Specialization constant provided for id (" + itos(sc.constant.constant_id) + ") is of the wrong type.");
-					data_ptr[i] = sc.constant.int_value;
+					data_ptr[i] = psc.int_value;
 					break;
 				}
 			}
@@ -6905,8 +7022,10 @@ Error RenderingDeviceVulkan::_draw_list_setup_framebuffer(Framebuffer *p_framebu
 			Texture *texture = texture_owner.get_or_null(p_framebuffer->texture_ids[i]);
 			if (texture) {
 				attachments.push_back(texture->view);
-				ERR_FAIL_COND_V(texture->width != p_framebuffer->size.width, ERR_BUG);
-				ERR_FAIL_COND_V(texture->height != p_framebuffer->size.height, ERR_BUG);
+				if (!(texture->usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT)) { // VRS attachment will be a different size.
+					ERR_FAIL_COND_V(texture->width != p_framebuffer->size.width, ERR_BUG);
+					ERR_FAIL_COND_V(texture->height != p_framebuffer->size.height, ERR_BUG);
+				}
 			}
 		}
 		framebuffer_create_info.attachmentCount = attachments.size();
@@ -7134,7 +7253,10 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_begin(RID p_framebu
 		int color_count = 0;
 		for (int i = 0; i < framebuffer->texture_ids.size(); i++) {
 			Texture *texture = texture_owner.get_or_null(framebuffer->texture_ids[i]);
-			if (!texture || !(texture->usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
+			// We only check for our VRS usage bit if this is not the first texture id.
+			// If it is the first we're likely populating our VRS texture.
+			// Bit dirty but..
+			if (!texture || (!(texture->usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) && !(i != 0 && texture->usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT))) {
 				color_count++;
 			}
 		}
@@ -8995,17 +9117,6 @@ void RenderingDeviceVulkan::initialize(VulkanContext *p_context, bool p_local_de
 	{
 		device_capabilities.version_major = p_context->get_vulkan_major();
 		device_capabilities.version_minor = p_context->get_vulkan_minor();
-
-		// get info about subgroups
-		VulkanContext::SubgroupCapabilities subgroup_capabilities = p_context->get_subgroup_capabilities();
-		device_capabilities.subgroup_size = subgroup_capabilities.size;
-		device_capabilities.subgroup_in_shaders = subgroup_capabilities.supported_stages_flags_rd();
-		device_capabilities.subgroup_operations = subgroup_capabilities.supported_operations_flags_rd();
-
-		// get info about further features
-		VulkanContext::MultiviewCapabilities multiview_capabilies = p_context->get_multiview_capabilities();
-		device_capabilities.supports_multiview = multiview_capabilies.is_supported && multiview_capabilies.max_view_count > 1;
-		device_capabilities.supports_fsr_half_float = p_context->get_shader_capabilities().shader_float16_is_supported && p_context->get_storage_buffer_capabilities().storage_buffer_16_bit_access_is_supported;
 	}
 
 	context = p_context;
@@ -9354,7 +9465,7 @@ String RenderingDeviceVulkan::get_captured_timestamp_name(uint32_t p_index) cons
 	return frames[frame].timestamp_result_names[p_index];
 }
 
-uint64_t RenderingDeviceVulkan::limit_get(Limit p_limit) {
+uint64_t RenderingDeviceVulkan::limit_get(Limit p_limit) const {
 	switch (p_limit) {
 		case LIMIT_MAX_BOUND_UNIFORM_SETS:
 			return limits.maxBoundDescriptorSets;
@@ -9424,7 +9535,18 @@ uint64_t RenderingDeviceVulkan::limit_get(Limit p_limit) {
 			return limits.maxComputeWorkGroupSize[1];
 		case LIMIT_MAX_COMPUTE_WORKGROUP_SIZE_Z:
 			return limits.maxComputeWorkGroupSize[2];
-
+		case LIMIT_SUBGROUP_SIZE: {
+			VulkanContext::SubgroupCapabilities subgroup_capabilities = context->get_subgroup_capabilities();
+			return subgroup_capabilities.size;
+		}
+		case LIMIT_SUBGROUP_IN_SHADERS: {
+			VulkanContext::SubgroupCapabilities subgroup_capabilities = context->get_subgroup_capabilities();
+			return subgroup_capabilities.supported_stages_flags_rd();
+		}
+		case LIMIT_SUBGROUP_OPERATIONS: {
+			VulkanContext::SubgroupCapabilities subgroup_capabilities = context->get_subgroup_capabilities();
+			return subgroup_capabilities.supported_operations_flags_rd();
+		}
 		default:
 			ERR_FAIL_V(0);
 	}
@@ -9524,6 +9646,25 @@ RenderingDevice *RenderingDeviceVulkan::create_local_device() {
 	return rd;
 }
 
+bool RenderingDeviceVulkan::has_feature(const Features p_feature) const {
+	switch (p_feature) {
+		case SUPPORTS_MULTIVIEW: {
+			VulkanContext::MultiviewCapabilities multiview_capabilies = context->get_multiview_capabilities();
+			return multiview_capabilies.is_supported && multiview_capabilies.max_view_count > 1;
+		} break;
+		case SUPPORTS_FSR_HALF_FLOAT: {
+			return context->get_shader_capabilities().shader_float16_is_supported && context->get_storage_buffer_capabilities().storage_buffer_16_bit_access_is_supported;
+		} break;
+		case SUPPORTS_ATTACHMENT_VRS: {
+			VulkanContext::VRSCapabilities vrs_capabilities = context->get_vrs_capabilities();
+			return vrs_capabilities.attachment_vrs_supported;
+		} break;
+		default: {
+			return false;
+		}
+	}
+}
+
 RenderingDeviceVulkan::RenderingDeviceVulkan() {
 	device_capabilities.device_family = DEVICE_VULKAN;
 }

+ 4 - 1
drivers/vulkan/rendering_device_vulkan.h

@@ -241,6 +241,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
 		Vector<AttachmentFormat> attachments;
 		Vector<FramebufferPass> passes;
 		uint32_t view_count = 1;
+
 		bool operator<(const FramebufferFormatKey &p_key) const {
 			if (view_count != p_key.view_count) {
 				return view_count < p_key.view_count;
@@ -1203,7 +1204,7 @@ public:
 	/**** Limits ****/
 	/****************/
 
-	virtual uint64_t limit_get(Limit p_limit);
+	virtual uint64_t limit_get(Limit p_limit) const;
 
 	virtual void prepare_screen_for_drawing();
 	void initialize(VulkanContext *p_context, bool p_local_device = false);
@@ -1234,6 +1235,8 @@ public:
 
 	virtual uint64_t get_driver_resource(DriverResource p_resource, RID p_rid = RID(), uint64_t p_index = 0);
 
+	virtual bool has_feature(const Features p_feature) const;
+
 	RenderingDeviceVulkan();
 	~RenderingDeviceVulkan();
 };

+ 103 - 14
drivers/vulkan/vulkan_context.cpp

@@ -48,6 +48,18 @@
 
 VulkanHooks *VulkanContext::vulkan_hooks = nullptr;
 
+VkResult VulkanContext::vkCreateRenderPass2KHR(VkDevice device, const VkRenderPassCreateInfo2 *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkRenderPass *pRenderPass) {
+	if (fpCreateRenderPass2KHR == nullptr) {
+		fpCreateRenderPass2KHR = (PFN_vkCreateRenderPass2KHR)vkGetInstanceProcAddr(inst, "vkCreateRenderPass2KHR");
+	}
+
+	if (fpCreateRenderPass2KHR == nullptr) {
+		return VK_ERROR_EXTENSION_NOT_PRESENT;
+	} else {
+		return (fpCreateRenderPass2KHR)(device, pCreateInfo, pAllocator, pRenderPass);
+	}
+}
+
 VKAPI_ATTR VkBool32 VKAPI_CALL VulkanContext::_debug_messenger_callback(
 		VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
 		VkDebugUtilsMessageTypeFlagsEXT messageType,
@@ -507,6 +519,9 @@ Error VulkanContext::_check_capabilities() {
 	// (note that the desktop loader does a better job here but the android loader doesn't)
 
 	// assume not supported until proven otherwise
+	vrs_capabilities.pipeline_vrs_supported = false;
+	vrs_capabilities.primitive_vrs_supported = false;
+	vrs_capabilities.attachment_vrs_supported = false;
 	multiview_capabilities.is_supported = false;
 	multiview_capabilities.geometry_shader_is_supported = false;
 	multiview_capabilities.tessellation_shader_is_supported = false;
@@ -531,9 +546,17 @@ Error VulkanContext::_check_capabilities() {
 	}
 	if (vkGetPhysicalDeviceFeatures2_func != nullptr) {
 		// check our extended features
+		VkPhysicalDeviceFragmentShadingRateFeaturesKHR vrs_features = {
+			/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR,
+			/*pNext*/ nullptr,
+			/*pipelineFragmentShadingRate*/ false,
+			/*primitiveFragmentShadingRate*/ false,
+			/*attachmentFragmentShadingRate*/ false,
+		};
+
 		VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader_features = {
 			/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR,
-			/*pNext*/ nullptr,
+			/*pNext*/ &vrs_features,
 			/*shaderFloat16*/ false,
 			/*shaderInt8*/ false,
 		};
@@ -561,6 +584,10 @@ Error VulkanContext::_check_capabilities() {
 
 		vkGetPhysicalDeviceFeatures2_func(gpu, &device_features);
 
+		vrs_capabilities.pipeline_vrs_supported = vrs_features.pipelineFragmentShadingRate;
+		vrs_capabilities.primitive_vrs_supported = vrs_features.primitiveFragmentShadingRate;
+		vrs_capabilities.attachment_vrs_supported = vrs_features.attachmentFragmentShadingRate;
+
 		multiview_capabilities.is_supported = multiview_features.multiview;
 		multiview_capabilities.geometry_shader_is_supported = multiview_features.multiviewGeometryShader;
 		multiview_capabilities.tessellation_shader_is_supported = multiview_features.multiviewTessellationShader;
@@ -581,24 +608,33 @@ Error VulkanContext::_check_capabilities() {
 		device_properties_func = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceProperties2KHR");
 	}
 	if (device_properties_func != nullptr) {
+		VkPhysicalDeviceFragmentShadingRatePropertiesKHR vrsProperties;
 		VkPhysicalDeviceMultiviewProperties multiviewProperties;
 		VkPhysicalDeviceSubgroupProperties subgroupProperties;
 		VkPhysicalDeviceProperties2 physicalDeviceProperties;
+		void *nextptr = nullptr;
 
 		subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
-		subgroupProperties.pNext = nullptr;
-
-		physicalDeviceProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+		subgroupProperties.pNext = nextptr;
+		nextptr = &subgroupProperties;
 
 		if (multiview_capabilities.is_supported) {
 			multiviewProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;
-			multiviewProperties.pNext = &subgroupProperties;
+			multiviewProperties.pNext = nextptr;
 
-			physicalDeviceProperties.pNext = &multiviewProperties;
-		} else {
-			physicalDeviceProperties.pNext = &subgroupProperties;
+			nextptr = &multiviewProperties;
 		}
 
+		if (vrs_capabilities.attachment_vrs_supported) {
+			vrsProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
+			vrsProperties.pNext = nextptr;
+
+			nextptr = &vrsProperties;
+		}
+
+		physicalDeviceProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+		physicalDeviceProperties.pNext = nextptr;
+
 		device_properties_func(gpu, &physicalDeviceProperties);
 
 		subgroup_capabilities.size = subgroupProperties.subgroupSize;
@@ -609,6 +645,28 @@ Error VulkanContext::_check_capabilities() {
 		// - supportedOperations has VK_SUBGROUP_FEATURE_QUAD_BIT
 		subgroup_capabilities.quadOperationsInAllStages = subgroupProperties.quadOperationsInAllStages;
 
+		if (vrs_capabilities.pipeline_vrs_supported || vrs_capabilities.primitive_vrs_supported || vrs_capabilities.attachment_vrs_supported) {
+			print_verbose("- Vulkan Varying Shading Rates supported:");
+			if (vrs_capabilities.pipeline_vrs_supported) {
+				print_verbose("  Pipeline fragment shading rate");
+			}
+			if (vrs_capabilities.primitive_vrs_supported) {
+				print_verbose("  Primitive fragment shading rate");
+			}
+			if (vrs_capabilities.attachment_vrs_supported) {
+				// TODO expose these somehow to the end user
+				vrs_capabilities.min_texel_size.x = vrsProperties.minFragmentShadingRateAttachmentTexelSize.width;
+				vrs_capabilities.min_texel_size.y = vrsProperties.minFragmentShadingRateAttachmentTexelSize.height;
+				vrs_capabilities.max_texel_size.x = vrsProperties.maxFragmentShadingRateAttachmentTexelSize.width;
+				vrs_capabilities.max_texel_size.y = vrsProperties.maxFragmentShadingRateAttachmentTexelSize.height;
+
+				print_verbose(String("  Attachment fragment shading rate") + String(", min texel size: (") + itos(vrs_capabilities.min_texel_size.x) + String(", ") + itos(vrs_capabilities.min_texel_size.y) + String(")") + String(", max texel size: (") + itos(vrs_capabilities.max_texel_size.x) + String(", ") + itos(vrs_capabilities.max_texel_size.y) + String(")"));
+			}
+
+		} else {
+			print_verbose("- Vulkan Varying Shading Rates not supported");
+		}
+
 		if (multiview_capabilities.is_supported) {
 			multiview_capabilities.max_view_count = multiviewProperties.maxMultiviewViewCount;
 			multiview_capabilities.max_instance_count = multiviewProperties.maxMultiviewInstanceIndex;
@@ -999,6 +1057,13 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) {
 				// if multiview is supported, enable it
 				extension_names[enabled_extension_count++] = VK_KHR_MULTIVIEW_EXTENSION_NAME;
 			}
+			if (!strcmp(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, device_extensions[i].extensionName)) {
+				// if shading rate image is supported, enable it
+				extension_names[enabled_extension_count++] = VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME;
+			}
+			if (!strcmp(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, device_extensions[i].extensionName)) {
+				extension_names[enabled_extension_count++] = VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME;
+			}
 			if (enabled_extension_count >= MAX_EXTENSIONS) {
 				free(device_extensions);
 				ERR_FAIL_V_MSG(ERR_BUG, "Enabled extension count reaches MAX_EXTENSIONS, BUG");
@@ -1110,6 +1175,18 @@ Error VulkanContext::_create_device() {
 	};
 	nextptr = &shader_features;
 
+	VkPhysicalDeviceFragmentShadingRateFeaturesKHR vrs_features;
+	if (vrs_capabilities.pipeline_vrs_supported || vrs_capabilities.primitive_vrs_supported || vrs_capabilities.attachment_vrs_supported) {
+		// insert into our chain to enable these features if they are available
+		vrs_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR;
+		vrs_features.pNext = nextptr;
+		vrs_features.pipelineFragmentShadingRate = vrs_capabilities.pipeline_vrs_supported;
+		vrs_features.primitiveFragmentShadingRate = vrs_capabilities.primitive_vrs_supported;
+		vrs_features.attachmentFragmentShadingRate = vrs_capabilities.attachment_vrs_supported;
+
+		nextptr = &vrs_features;
+	}
+
 	VkPhysicalDeviceVulkan11Features vulkan11features;
 	VkPhysicalDevice16BitStorageFeaturesKHR storage_feature;
 	VkPhysicalDeviceMultiviewFeatures multiview_features;
@@ -1725,7 +1802,9 @@ Error VulkanContext::_update_swap_chain(Window *window) {
 	/******** FRAMEBUFFER ************/
 
 	{
-		const VkAttachmentDescription attachment = {
+		const VkAttachmentDescription2KHR attachment = {
+			/*sType*/ VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR,
+			/*pNext*/ nullptr,
 			/*flags*/ 0,
 			/*format*/ format,
 			/*samples*/ VK_SAMPLE_COUNT_1_BIT,
@@ -1737,14 +1816,20 @@ Error VulkanContext::_update_swap_chain(Window *window) {
 			/*finalLayout*/ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
 
 		};
-		const VkAttachmentReference color_reference = {
+		const VkAttachmentReference2KHR color_reference = {
+			/*sType*/ VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR,
+			/*pNext*/ nullptr,
 			/*attachment*/ 0,
 			/*layout*/ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+			/*aspectMask*/ 0,
 		};
 
-		const VkSubpassDescription subpass = {
+		const VkSubpassDescription2KHR subpass = {
+			/*sType*/ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR,
+			/*pNext*/ nullptr,
 			/*flags*/ 0,
 			/*pipelineBindPoint*/ VK_PIPELINE_BIND_POINT_GRAPHICS,
+			/*viewMask*/ 1,
 			/*inputAttachmentCount*/ 0,
 			/*pInputAttachments*/ nullptr,
 			/*colorAttachmentCount*/ 1,
@@ -1754,8 +1839,10 @@ Error VulkanContext::_update_swap_chain(Window *window) {
 			/*preserveAttachmentCount*/ 0,
 			/*pPreserveAttachments*/ nullptr,
 		};
-		const VkRenderPassCreateInfo rp_info = {
-			/*sTyp*/ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+
+		uint32_t view_masks = 1;
+		const VkRenderPassCreateInfo2KHR rp_info = {
+			/*sType*/ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR,
 			/*pNext*/ nullptr,
 			/*flags*/ 0,
 			/*attachmentCount*/ 1,
@@ -1764,9 +1851,11 @@ Error VulkanContext::_update_swap_chain(Window *window) {
 			/*pSubpasses*/ &subpass,
 			/*dependencyCount*/ 0,
 			/*pDependencies*/ nullptr,
+			/*correlatedViewMaskCount*/ 1,
+			/*pCorrelatedViewMasks*/ &view_masks,
 		};
 
-		err = vkCreateRenderPass(device, &rp_info, nullptr, &window->render_pass);
+		err = vkCreateRenderPass2KHR(device, &rp_info, nullptr, &window->render_pass);
 		ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
 
 		for (uint32_t i = 0; i < swapchainImageCount; i++) {

+ 15 - 0
drivers/vulkan/vulkan_context.h

@@ -69,6 +69,15 @@ public:
 		uint32_t max_instance_count;
 	};
 
+	struct VRSCapabilities {
+		bool pipeline_vrs_supported; // We can specify our fragment rate on a pipeline level
+		bool primitive_vrs_supported; // We can specify our fragment rate on each drawcall
+		bool attachment_vrs_supported; // We can provide a density map attachment on our framebuffer
+
+		Size2i min_texel_size;
+		Size2i max_texel_size;
+	};
+
 	struct ShaderCapabilities {
 		bool shader_float16_is_supported;
 		bool shader_int8_is_supported;
@@ -104,6 +113,7 @@ private:
 	uint32_t vulkan_patch = 0;
 	SubgroupCapabilities subgroup_capabilities;
 	MultiviewCapabilities multiview_capabilities;
+	VRSCapabilities vrs_capabilities;
 	ShaderCapabilities shader_capabilities;
 	StorageBufferCapabilities storage_buffer_capabilities;
 
@@ -206,6 +216,7 @@ private:
 	PFN_vkQueuePresentKHR fpQueuePresentKHR = nullptr;
 	PFN_vkGetRefreshCycleDurationGOOGLE fpGetRefreshCycleDurationGOOGLE = nullptr;
 	PFN_vkGetPastPresentationTimingGOOGLE fpGetPastPresentationTimingGOOGLE = nullptr;
+	PFN_vkCreateRenderPass2KHR fpCreateRenderPass2KHR = nullptr;
 
 	VkDebugUtilsMessengerEXT dbg_messenger = VK_NULL_HANDLE;
 	VkDebugReportCallbackEXT dbg_debug_report = VK_NULL_HANDLE;
@@ -256,10 +267,14 @@ protected:
 	Error _get_preferred_validation_layers(uint32_t *count, const char *const **names);
 
 public:
+	// Extension calls
+	VkResult vkCreateRenderPass2KHR(VkDevice device, const VkRenderPassCreateInfo2 *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkRenderPass *pRenderPass);
+
 	uint32_t get_vulkan_major() const { return vulkan_major; };
 	uint32_t get_vulkan_minor() const { return vulkan_minor; };
 	SubgroupCapabilities get_subgroup_capabilities() const { return subgroup_capabilities; };
 	MultiviewCapabilities get_multiview_capabilities() const { return multiview_capabilities; };
+	VRSCapabilities get_vrs_capabilities() const { return vrs_capabilities; };
 	ShaderCapabilities get_shader_capabilities() const { return shader_capabilities; };
 	StorageBufferCapabilities get_storage_buffer_capabilities() const { return storage_buffer_capabilities; };
 

+ 19 - 15
modules/glslang/register_types.cpp

@@ -38,7 +38,8 @@
 #include <glslang/Public/ShaderLang.h>
 #include <glslang/SPIRV/GlslangToSpv.h>
 
-static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage, const String &p_source_code, RenderingDevice::ShaderLanguage p_language, String *r_error, const RenderingDevice::Capabilities *p_capabilities) {
+static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage, const String &p_source_code, RenderingDevice::ShaderLanguage p_language, String *r_error, const RenderingDevice *p_render_device) {
+	const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
 	Vector<uint8_t> ret;
 
 	ERR_FAIL_COND_V(p_language == RenderingDevice::SHADER_LANGUAGE_HLSL, ret);
@@ -58,12 +59,12 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
 	glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5;
 	glslang::TShader::ForbidIncluder includer;
 
-	if (p_capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) {
-		if (p_capabilities->version_major == 1 && p_capabilities->version_minor == 0) {
+	if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) {
+		if (capabilities->version_major == 1 && capabilities->version_minor == 0) {
 			ClientVersion = glslang::EShTargetVulkan_1_0;
 			TargetVersion = glslang::EShTargetSpv_1_0;
 			check_subgroup_support = false; // subgroups are not supported in Vulkan 1.0
-		} else if (p_capabilities->version_major == 1 && p_capabilities->version_minor == 1) {
+		} else if (capabilities->version_major == 1 && capabilities->version_minor == 1) {
 			ClientVersion = glslang::EShTargetVulkan_1_1;
 			TargetVersion = glslang::EShTargetSpv_1_3;
 		} else {
@@ -90,34 +91,36 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
 	if (check_subgroup_support) {
 		uint32_t stage_bit = 1 << p_stage;
 
-		if ((p_capabilities->subgroup_in_shaders & stage_bit) == stage_bit) {
+		uint32_t subgroup_in_shaders = uint32_t(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS));
+		uint32_t subgroup_operations = uint32_t(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS));
+		if ((subgroup_in_shaders & stage_bit) == stage_bit) {
 			// stage supports subgroups
 			preamble += "#define has_GL_KHR_shader_subgroup_basic 1\n";
-			if (p_capabilities->subgroup_operations & RenderingDevice::SUBGROUP_VOTE_BIT) {
+			if (subgroup_operations & RenderingDevice::SUBGROUP_VOTE_BIT) {
 				preamble += "#define has_GL_KHR_shader_subgroup_vote 1\n";
 			}
-			if (p_capabilities->subgroup_operations & RenderingDevice::SUBGROUP_ARITHMETIC_BIT) {
+			if (subgroup_operations & RenderingDevice::SUBGROUP_ARITHMETIC_BIT) {
 				preamble += "#define has_GL_KHR_shader_subgroup_arithmetic 1\n";
 			}
-			if (p_capabilities->subgroup_operations & RenderingDevice::SUBGROUP_BALLOT_BIT) {
+			if (subgroup_operations & RenderingDevice::SUBGROUP_BALLOT_BIT) {
 				preamble += "#define has_GL_KHR_shader_subgroup_ballot 1\n";
 			}
-			if (p_capabilities->subgroup_operations & RenderingDevice::SUBGROUP_SHUFFLE_BIT) {
+			if (subgroup_operations & RenderingDevice::SUBGROUP_SHUFFLE_BIT) {
 				preamble += "#define has_GL_KHR_shader_subgroup_shuffle 1\n";
 			}
-			if (p_capabilities->subgroup_operations & RenderingDevice::SUBGROUP_SHUFFLE_RELATIVE_BIT) {
+			if (subgroup_operations & RenderingDevice::SUBGROUP_SHUFFLE_RELATIVE_BIT) {
 				preamble += "#define has_GL_KHR_shader_subgroup_shuffle_relative 1\n";
 			}
-			if (p_capabilities->subgroup_operations & RenderingDevice::SUBGROUP_CLUSTERED_BIT) {
+			if (subgroup_operations & RenderingDevice::SUBGROUP_CLUSTERED_BIT) {
 				preamble += "#define has_GL_KHR_shader_subgroup_clustered 1\n";
 			}
-			if (p_capabilities->subgroup_operations & RenderingDevice::SUBGROUP_QUAD_BIT) {
+			if (subgroup_operations & RenderingDevice::SUBGROUP_QUAD_BIT) {
 				preamble += "#define has_GL_KHR_shader_subgroup_quad 1\n";
 			}
 		}
 	}
 
-	if (p_capabilities->supports_multiview) {
+	if (p_render_device->has_feature(RD::SUPPORTS_MULTIVIEW)) {
 		preamble += "#define has_VK_KHR_multiview 1\n";
 	}
 
@@ -184,9 +187,10 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
 	return ret;
 }
 
-static String _get_cache_key_function_glsl(const RenderingDevice::Capabilities *p_capabilities) {
+static String _get_cache_key_function_glsl(const RenderingDevice *p_render_device) {
+	const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
 	String version;
-	version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(p_capabilities->version_major) + ", minor=" + itos(p_capabilities->version_minor) + " , subgroup_size=" + itos(p_capabilities->subgroup_operations) + " , subgroup_ops=" + itos(p_capabilities->subgroup_operations) + " , subgroup_in_shaders=" + itos(p_capabilities->subgroup_in_shaders);
+	version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS));
 	return version;
 }
 

+ 24 - 0
scene/main/scene_tree.cpp

@@ -34,6 +34,7 @@
 #include "core/debugger/engine_debugger.h"
 #include "core/input/input.h"
 #include "core/io/dir_access.h"
+#include "core/io/image_loader.h"
 #include "core/io/marshalls.h"
 #include "core/io/resource_loader.h"
 #include "core/multiplayer/multiplayer_api.h"
@@ -1446,6 +1447,29 @@ SceneTree::SceneTree() {
 	bool snap_2d_vertices = GLOBAL_DEF("rendering/2d/snap/snap_2d_vertices_to_pixel", false);
 	root->set_snap_2d_vertices_to_pixel(snap_2d_vertices);
 
+	// We setup VRS for the main viewport here, in the editor this will have little effect.
+	const int vrs_mode = GLOBAL_DEF("rendering/vrs/mode", 0);
+	ProjectSettings::get_singleton()->set_custom_property_info("rendering/vrs/mode", PropertyInfo(Variant::INT, "rendering/vrs/mode", PROPERTY_HINT_ENUM, String::utf8("Disabled,Texture,XR")));
+	root->set_vrs_mode(Viewport::VRSMode(vrs_mode));
+	const String vrs_texture_path = String(GLOBAL_DEF("rendering/vrs/texture", String())).strip_edges();
+	ProjectSettings::get_singleton()->set_custom_property_info("rendering/vrs/texture",
+			PropertyInfo(Variant::STRING,
+					"rendering/vrs/texture",
+					PROPERTY_HINT_FILE, "*.png"));
+	if (vrs_mode == 1 && !vrs_texture_path.is_empty()) {
+		Ref<Image> vrs_image;
+		vrs_image.instantiate();
+		Error load_err = ImageLoader::load_image(vrs_texture_path, vrs_image);
+		if (load_err) {
+			ERR_PRINT("Non-existing or invalid VRS texture at '" + vrs_texture_path + "'.");
+		} else {
+			Ref<ImageTexture> vrs_texture;
+			vrs_texture.instantiate();
+			vrs_texture->create_from_image(vrs_image);
+			root->set_vrs_texture(vrs_texture);
+		}
+	}
+
 	int shadowmap_size = GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_size", 4096);
 	ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/positional_shadow/atlas_size", PropertyInfo(Variant::INT, "rendering/shadows/positional_shadow/atlas_size", PROPERTY_HINT_RANGE, "256,16384"));
 	GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_size.mobile", 2048);

+ 55 - 0
scene/main/viewport.cpp

@@ -3080,6 +3080,41 @@ Viewport::DefaultCanvasItemTextureRepeat Viewport::get_default_canvas_item_textu
 	return default_canvas_item_texture_repeat;
 }
 
+void Viewport::set_vrs_mode(Viewport::VRSMode p_vrs_mode) {
+	// Note, set this even if not supported on this hardware, it will only be used if it is but we want to save the value as set by the user.
+	vrs_mode = p_vrs_mode;
+
+	switch (p_vrs_mode) {
+		case VRS_TEXTURE: {
+			RS::get_singleton()->viewport_set_vrs_mode(viewport, RS::VIEWPORT_VRS_TEXTURE);
+		} break;
+		case VRS_XR: {
+			RS::get_singleton()->viewport_set_vrs_mode(viewport, RS::VIEWPORT_VRS_XR);
+		} break;
+		default: {
+			RS::get_singleton()->viewport_set_vrs_mode(viewport, RS::VIEWPORT_VRS_DISABLED);
+		} break;
+	}
+
+	notify_property_list_changed();
+}
+
+Viewport::VRSMode Viewport::get_vrs_mode() const {
+	return vrs_mode;
+}
+
+void Viewport::set_vrs_texture(Ref<Texture2D> p_texture) {
+	vrs_texture = p_texture;
+
+	// TODO need to add something here in case the RID changes
+	RID tex = p_texture.is_valid() ? p_texture->get_rid() : RID();
+	RS::get_singleton()->viewport_set_vrs_texture(viewport, tex);
+}
+
+Ref<Texture2D> Viewport::get_vrs_texture() const {
+	return vrs_texture;
+}
+
 DisplayServer::WindowID Viewport::get_window_id() const {
 	return DisplayServer::MAIN_WINDOW_ID;
 }
@@ -3741,6 +3776,12 @@ void Viewport::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_fsr_mipmap_bias", "fsr_mipmap_bias"), &Viewport::set_fsr_mipmap_bias);
 	ClassDB::bind_method(D_METHOD("get_fsr_mipmap_bias"), &Viewport::get_fsr_mipmap_bias);
 
+	ClassDB::bind_method(D_METHOD("set_vrs_mode", "mode"), &Viewport::set_vrs_mode);
+	ClassDB::bind_method(D_METHOD("get_vrs_mode"), &Viewport::get_vrs_mode);
+
+	ClassDB::bind_method(D_METHOD("set_vrs_texture", "texture"), &Viewport::set_vrs_texture);
+	ClassDB::bind_method(D_METHOD("get_vrs_texture"), &Viewport::get_vrs_texture);
+
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_xr"), "set_use_xr", "is_using_xr");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world_3d"), "set_use_own_world_3d", "is_using_own_world_3d");
@@ -3766,6 +3807,9 @@ void Viewport::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fsr_mipmap_bias", PROPERTY_HINT_RANGE, "-2,2,0.1"), "set_fsr_mipmap_bias", "get_fsr_mipmap_bias");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fsr_sharpness", PROPERTY_HINT_RANGE, "0,2,0.1"), "set_fsr_sharpness", "get_fsr_sharpness");
 #endif
+	ADD_GROUP("Variable Rate Shading", "vrs_");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "vrs_mode", PROPERTY_HINT_ENUM, "Disabled,Texture,Depth buffer,XR"), "set_vrs_mode", "get_vrs_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "vrs_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_vrs_texture", "get_vrs_texture");
 	ADD_GROUP("Canvas Items", "canvas_item_");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_item_default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), "set_default_canvas_item_texture_filter", "get_default_canvas_item_texture_filter");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_item_default_texture_repeat", PROPERTY_HINT_ENUM, "Disabled,Enabled,Mirror"), "set_default_canvas_item_texture_repeat", "get_default_canvas_item_texture_repeat");
@@ -3876,6 +3920,17 @@ void Viewport::_bind_methods() {
 	BIND_ENUM_CONSTANT(SDF_SCALE_50_PERCENT);
 	BIND_ENUM_CONSTANT(SDF_SCALE_25_PERCENT);
 	BIND_ENUM_CONSTANT(SDF_SCALE_MAX);
+
+	BIND_ENUM_CONSTANT(VRS_DISABLED);
+	BIND_ENUM_CONSTANT(VRS_TEXTURE);
+	BIND_ENUM_CONSTANT(VRS_XR);
+	BIND_ENUM_CONSTANT(VRS_MAX);
+}
+
+void Viewport::_validate_property(PropertyInfo &property) const {
+	if (vrs_mode != VRS_TEXTURE && (property.name == "vrs_texture")) {
+		property.usage = PROPERTY_USAGE_NO_EDITOR;
+	}
 }
 
 Viewport::Viewport() {

+ 21 - 0
scene/main/viewport.h

@@ -197,6 +197,13 @@ public:
 		SUBWINDOW_CANVAS_LAYER = 1024
 	};
 
+	enum VRSMode {
+		VRS_DISABLED,
+		VRS_TEXTURE,
+		VRS_XR,
+		VRS_MAX
+	};
+
 private:
 	friend class ViewportTexture;
 
@@ -333,6 +340,10 @@ private:
 		RID canvas_item;
 	};
 
+	// VRS
+	VRSMode vrs_mode = VRS_DISABLED;
+	Ref<Texture2D> vrs_texture;
+
 	struct GUI {
 		// info used when this is a window
 
@@ -604,6 +615,14 @@ public:
 	void set_default_canvas_item_texture_repeat(DefaultCanvasItemTextureRepeat p_repeat);
 	DefaultCanvasItemTextureRepeat get_default_canvas_item_texture_repeat() const;
 
+	// VRS
+
+	void set_vrs_mode(VRSMode p_vrs_mode);
+	VRSMode get_vrs_mode() const;
+
+	void set_vrs_texture(Ref<Texture2D> p_texture);
+	Ref<Texture2D> get_vrs_texture() const;
+
 	virtual DisplayServer::WindowID get_window_id() const = 0;
 
 	void set_embedding_subwindows(bool p_embed);
@@ -690,6 +709,7 @@ public:
 	bool is_using_xr();
 #endif // _3D_DISABLED
 
+	virtual void _validate_property(PropertyInfo &property) const override;
 	Viewport();
 	~Viewport();
 };
@@ -752,6 +772,7 @@ VARIANT_ENUM_CAST(Viewport::ScreenSpaceAA);
 VARIANT_ENUM_CAST(Viewport::DebugDraw);
 VARIANT_ENUM_CAST(Viewport::SDFScale);
 VARIANT_ENUM_CAST(Viewport::SDFOversize);
+VARIANT_ENUM_CAST(Viewport::VRSMode);
 VARIANT_ENUM_CAST(SubViewport::ClearMode);
 VARIANT_ENUM_CAST(Viewport::RenderInfo);
 VARIANT_ENUM_CAST(Viewport::RenderInfoType);

+ 3 - 0
servers/rendering/dummy/storage/texture_storage.h

@@ -169,6 +169,9 @@ public:
 	virtual void render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) override {}
 	virtual Rect2i render_target_get_sdf_rect(RID p_render_target) const override { return Rect2i(); }
 	virtual void render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) override {}
+
+	virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override{};
+	virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override{};
 };
 
 } // namespace RendererDummy

+ 5 - 5
servers/rendering/renderer_rd/effects/copy_effects.cpp

@@ -100,11 +100,11 @@ CopyEffects::CopyEffects(bool p_prefer_raster_effects) {
 
 	{
 		Vector<String> copy_modes;
-		copy_modes.push_back("\n");
-		copy_modes.push_back("\n#define MODE_PANORAMA_TO_DP\n");
-		copy_modes.push_back("\n#define MODE_TWO_SOURCES\n");
-		copy_modes.push_back("\n#define MULTIVIEW\n");
-		copy_modes.push_back("\n#define MULTIVIEW\n#define MODE_TWO_SOURCES\n");
+		copy_modes.push_back("\n"); // COPY_TO_FB_COPY
+		copy_modes.push_back("\n#define MODE_PANORAMA_TO_DP\n"); // COPY_TO_FB_COPY_PANORAMA_TO_DP
+		copy_modes.push_back("\n#define MODE_TWO_SOURCES\n"); // COPY_TO_FB_COPY2
+		copy_modes.push_back("\n#define MULTIVIEW\n"); // COPY_TO_FB_MULTIVIEW
+		copy_modes.push_back("\n#define MULTIVIEW\n#define MODE_TWO_SOURCES\n"); // COPY_TO_FB_MULTIVIEW_WITH_DEPTH
 
 		copy_to_fb.shader.initialize(copy_modes);
 

+ 171 - 0
servers/rendering/renderer_rd/effects/vrs.cpp

@@ -0,0 +1,171 @@
+/*************************************************************************/
+/*  vrs.cpp                                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "vrs.h"
+#include "../renderer_compositor_rd.h"
+#include "../storage_rd/texture_storage.h"
+#include "../uniform_set_cache_rd.h"
+#include "servers/xr_server.h"
+
+using namespace RendererRD;
+
+VRS::VRS() {
+	{
+		Vector<String> vrs_modes;
+		vrs_modes.push_back("\n"); // VRS_DEFAULT
+		vrs_modes.push_back("\n#define MULTIVIEW\n"); // VRS_MULTIVIEW
+
+		vrs_shader.shader.initialize(vrs_modes);
+
+		if (!RendererCompositorRD::singleton->is_xr_enabled()) {
+			vrs_shader.shader.set_variant_enabled(VRS_MULTIVIEW, false);
+		}
+
+		vrs_shader.shader_version = vrs_shader.shader.version_create();
+
+		//use additive
+
+		for (int i = 0; i < VRS_MAX; i++) {
+			if (vrs_shader.shader.is_variant_enabled(i)) {
+				vrs_shader.pipelines[i].setup(vrs_shader.shader.version_get_shader(vrs_shader.shader_version, i), RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_disabled(), 0);
+			} else {
+				vrs_shader.pipelines[i].clear();
+			}
+		}
+	}
+}
+
+VRS::~VRS() {
+	vrs_shader.shader.version_free(vrs_shader.shader_version);
+}
+
+void VRS::copy_vrs(RID p_source_rd_texture, RID p_dest_framebuffer, bool p_multiview) {
+	UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
+	ERR_FAIL_NULL(uniform_set_cache);
+	MaterialStorage *material_storage = MaterialStorage::get_singleton();
+	ERR_FAIL_NULL(material_storage);
+
+	// setup our uniforms
+	RID default_sampler = material_storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+
+	RD::Uniform u_source_rd_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_sampler, p_source_rd_texture }));
+
+	VRSMode mode = p_multiview ? VRS_MULTIVIEW : VRS_DEFAULT;
+
+	RID shader = vrs_shader.shader.version_get_shader(vrs_shader.shader_version, mode);
+	ERR_FAIL_COND(shader.is_null());
+
+	RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dest_framebuffer, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD, Vector<Color>());
+	RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, vrs_shader.pipelines[mode].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer)));
+	RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
+	RD::get_singleton()->draw_list_bind_index_array(draw_list, material_storage->get_quad_index_array());
+	// RD::get_singleton()->draw_list_set_push_constant(draw_list, &vrs_shader.push_constant, sizeof(VRSPushConstant));
+	RD::get_singleton()->draw_list_draw(draw_list, true);
+	RD::get_singleton()->draw_list_end();
+}
+
+void VRS::create_vrs_texture(const int p_base_width, const int p_base_height, const uint32_t p_view_count, RID &p_vrs_texture, RID &p_vrs_fb) {
+	// TODO find a way to skip this if VRS is not supported, but we don't have access to VulkanContext here, even though we're in vulkan.. hmmm
+
+	// TODO we should find some way to store this properly, we're assuming 16x16 as this seems to be the standard but in our vrs_capacities we
+	// obtain a minimum and maximum size, and we should choose something within this range and then make sure that is consistantly set when creating
+	// our frame buffer. Also it is important that we make the resulting size we calculate down below available to the end user so they know the size
+	// of the VRS buffer to supply.
+	Size2i texel_size = Size2i(16, 16);
+
+	RD::TextureFormat tf;
+	if (p_view_count > 1) {
+		tf.texture_type = RD::TEXTURE_TYPE_2D_ARRAY;
+	} else {
+		tf.texture_type = RD::TEXTURE_TYPE_2D;
+	}
+	tf.format = RD::DATA_FORMAT_R8_UINT;
+	tf.width = p_base_width / texel_size.x;
+	if (p_base_width % texel_size.x != 0) {
+		tf.width++;
+	}
+	tf.height = p_base_height / texel_size.y;
+	if (p_base_height % texel_size.y != 0) {
+		tf.height++;
+	}
+	tf.array_layers = p_view_count; // create a layer for every view
+	tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_VRS_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
+	tf.samples = RD::TEXTURE_SAMPLES_1;
+
+	p_vrs_texture = RD::get_singleton()->texture_create(tf, RD::TextureView());
+
+	// by default VRS is assumed to be our VRS attachment, but if we need to write into it, we need a bit more control
+	Vector<RID> fb;
+	fb.push_back(p_vrs_texture);
+
+	RD::FramebufferPass pass;
+	pass.color_attachments.push_back(0);
+
+	Vector<RD::FramebufferPass> passes;
+	passes.push_back(pass);
+
+	p_vrs_fb = RD::get_singleton()->framebuffer_create_multipass(fb, passes, RenderingDevice::INVALID_ID, p_view_count);
+}
+
+void VRS::update_vrs_texture(RID p_vrs_fb, RID p_render_target) {
+	TextureStorage *texture_storage = TextureStorage::get_singleton();
+	RS::ViewportVRSMode vrs_mode = texture_storage->render_target_get_vrs_mode(p_render_target);
+
+	if (vrs_mode != RS::VIEWPORT_VRS_DISABLED) {
+		RD::get_singleton()->draw_command_begin_label("VRS Setup");
+
+		// TODO figure out if image has changed since it was last copied so we can save some resources..
+
+		if (vrs_mode == RS::VIEWPORT_VRS_TEXTURE) {
+			RID vrs_texture = texture_storage->render_target_get_vrs_texture(p_render_target);
+			if (vrs_texture.is_valid()) {
+				Texture *texture = texture_storage->get_texture(vrs_texture);
+				if (texture) {
+					// Copy into our density buffer
+					copy_vrs(texture->rd_texture, p_vrs_fb, texture->layers > 1);
+				}
+			}
+		} else if (vrs_mode == RS::VIEWPORT_VRS_XR) {
+			Ref<XRInterface> interface = XRServer::get_singleton()->get_primary_interface();
+			if (interface.is_valid()) {
+				RID vrs_texture = interface->get_vrs_texture();
+				if (vrs_texture.is_valid()) {
+					Texture *texture = texture_storage->get_texture(vrs_texture);
+					if (texture) {
+						// Copy into our density buffer
+						copy_vrs(texture->rd_texture, p_vrs_fb, texture->layers > 1);
+					}
+				}
+			}
+		}
+
+		RD::get_singleton()->draw_command_end_label();
+	}
+}

+ 75 - 0
servers/rendering/renderer_rd/effects/vrs.h

@@ -0,0 +1,75 @@
+/*************************************************************************/
+/*  vrs.h                                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef VRS_RD_H
+#define VRS_RD_H
+
+#include "servers/rendering/renderer_rd/pipeline_cache_rd.h"
+#include "servers/rendering/renderer_rd/shaders/effects/vrs.glsl.gen.h"
+#include "servers/rendering/renderer_scene_render.h"
+
+#include "servers/rendering_server.h"
+
+namespace RendererRD {
+
+class VRS {
+private:
+	enum VRSMode {
+		VRS_DEFAULT,
+		VRS_MULTIVIEW,
+		VRS_MAX,
+	};
+
+	/* we have no push constant here (yet)
+	struct VRSPushConstant {
+
+	};
+	*/
+
+	struct VRSShader {
+		// VRSPushConstant push_constant;
+		VrsShaderRD shader;
+		RID shader_version;
+		PipelineCacheRD pipelines[VRS_MAX];
+	} vrs_shader;
+
+public:
+	VRS();
+	~VRS();
+
+	void copy_vrs(RID p_source_rd_texture, RID p_dest_framebuffer, bool p_multiview = false);
+
+	void create_vrs_texture(const int p_base_width, const int p_base_height, const uint32_t p_view_count, RID &p_vrs_texture, RID &p_vrs_fb);
+	void update_vrs_texture(RID p_vrs_fb, RID p_render_target);
+};
+
+} // namespace RendererRD
+
+#endif // !VRS_RD_H

+ 1 - 1
servers/rendering/renderer_rd/effects_rd.cpp

@@ -1309,7 +1309,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) {
 		FSR_upscale_modes.push_back("\n#define MODE_FSR_UPSCALE_FALLBACK\n");
 #else
 		// Everyone else can use normal mode when available.
-		if (RD::get_singleton()->get_device_capabilities()->supports_fsr_half_float) {
+		if (RD::get_singleton()->has_feature(RD::SUPPORTS_FSR_HALF_FLOAT)) {
 			FSR_upscale_modes.push_back("\n#define MODE_FSR_UPSCALE_NORMAL\n");
 		} else {
 			FSR_upscale_modes.push_back("\n#define MODE_FSR_UPSCALE_FALLBACK\n");

+ 120 - 88
servers/rendering/renderer_rd/environment/gi.cpp

@@ -109,6 +109,7 @@ void GI::voxel_gi_allocate_data(RID p_voxel_gi, const Transform3D &p_to_cell_xfo
 			Vector<Vector<uint8_t>> s;
 			s.push_back(p_distance_field);
 			voxel_gi->sdf_texture = RD::get_singleton()->texture_create(tf, RD::TextureView(), s);
+			RD::get_singleton()->set_resource_name(voxel_gi->sdf_texture, "VoxelGI SDF Texture");
 		}
 #if 0
 			{
@@ -122,6 +123,7 @@ void GI::voxel_gi_allocate_data(RID p_voxel_gi, const Transform3D &p_to_cell_xfo
 				tf.shareable_formats.push_back(RD::DATA_FORMAT_R8_UNORM);
 				tf.shareable_formats.push_back(RD::DATA_FORMAT_R8_UINT);
 				voxel_gi->sdf_texture = RD::get_singleton()->texture_create(tf, RD::TextureView());
+				RD::get_singleton()->set_resource_name(voxel_gi->sdf_texture, "VoxelGI SDF Texture");
 			}
 			RID shared_tex;
 			{
@@ -402,29 +404,38 @@ void GI::SDFGI::create(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world
 		RD::TextureFormat tf_render = tf_sdf;
 		tf_render.format = RD::DATA_FORMAT_R16_UINT;
 		render_albedo = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_albedo, "VoxelGI Render Albedo");
 		tf_render.format = RD::DATA_FORMAT_R32_UINT;
 		render_emission = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_emission, "VoxelGI Render Emission");
 		render_emission_aniso = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_emission_aniso, "VoxelGI Render Emission Aniso");
 
 		tf_render.format = RD::DATA_FORMAT_R8_UNORM; //at least its easy to visualize
 
 		for (int i = 0; i < 8; i++) {
 			render_occlusion[i] = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+			RD::get_singleton()->set_resource_name(render_occlusion[i], String("VoxelGI Render Occlusion ") + itos(i));
 		}
 
 		tf_render.format = RD::DATA_FORMAT_R32_UINT;
 		render_geom_facing = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_geom_facing, "VoxelGI Render Geometry Facing");
 
 		tf_render.format = RD::DATA_FORMAT_R8G8B8A8_UINT;
 		render_sdf[0] = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_sdf[0], "VoxelGI Render SDF 0");
 		render_sdf[1] = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_sdf[1], "VoxelGI Render SDF 1");
 
 		tf_render.width /= 2;
 		tf_render.height /= 2;
 		tf_render.depth /= 2;
 
 		render_sdf_half[0] = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_sdf_half[0], "VoxelGI Render SDF Half 0");
 		render_sdf_half[1] = RD::get_singleton()->texture_create(tf_render, RD::TextureView());
+		RD::get_singleton()->set_resource_name(render_sdf_half[1], "VoxelGI Render SDF Half 1");
 	}
 
 	RD::TextureFormat tf_occlusion = tf_sdf;
@@ -465,7 +476,9 @@ void GI::SDFGI::create(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world
 	tf_probe_average.texture_type = RD::TEXTURE_TYPE_2D;
 
 	lightprobe_history_scroll = RD::get_singleton()->texture_create(tf_probe_history, RD::TextureView());
+	RD::get_singleton()->set_resource_name(lightprobe_history_scroll, "VoxelGI LightProbe History Scroll");
 	lightprobe_average_scroll = RD::get_singleton()->texture_create(tf_probe_average, RD::TextureView());
+	RD::get_singleton()->set_resource_name(lightprobe_average_scroll, "VoxelGI LightProbe Average Scroll");
 
 	{
 		//octahedral lightprobes
@@ -479,6 +492,7 @@ void GI::SDFGI::create(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world
 		//lightprobe texture is an octahedral texture
 
 		lightprobe_data = RD::get_singleton()->texture_create(tf_octprobes, RD::TextureView());
+		RD::get_singleton()->set_resource_name(lightprobe_data, "VoxelGI LightProbe Data");
 		RD::TextureView tv;
 		tv.format_override = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;
 		lightprobe_texture = RD::get_singleton()->texture_create_shared(tv, lightprobe_data);
@@ -492,11 +506,13 @@ void GI::SDFGI::create(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world
 		tf_ambient.texture_type = RD::TEXTURE_TYPE_2D_ARRAY;
 		//lightprobe texture is an octahedral texture
 		ambient_texture = RD::get_singleton()->texture_create(tf_ambient, RD::TextureView());
+		RD::get_singleton()->set_resource_name(ambient_texture, "VoxelGI Ambient Texture");
 	}
 
 	cascades_ubo = RD::get_singleton()->uniform_buffer_create(sizeof(SDFGI::Cascade::UBO) * SDFGI::MAX_CASCADES);
 
 	occlusion_data = RD::get_singleton()->texture_create(tf_occlusion, RD::TextureView());
+	RD::get_singleton()->set_resource_name(occlusion_data, "VoxelGI Occlusion Data");
 	{
 		RD::TextureView tv;
 		tv.format_override = RD::DATA_FORMAT_R4G4B4A4_UNORM_PACK16;
@@ -509,11 +525,15 @@ void GI::SDFGI::create(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world
 		/* 3D Textures */
 
 		cascade.sdf_tex = RD::get_singleton()->texture_create(tf_sdf, RD::TextureView());
+		RD::get_singleton()->set_resource_name(cascade.sdf_tex, "VoxelGI Cascade SDF Texture");
 
 		cascade.light_data = RD::get_singleton()->texture_create(tf_light, RD::TextureView());
+		RD::get_singleton()->set_resource_name(cascade.light_data, "VoxelGI Cascade Light Data");
 
 		cascade.light_aniso_0_tex = RD::get_singleton()->texture_create(tf_aniso0, RD::TextureView());
+		RD::get_singleton()->set_resource_name(cascade.light_aniso_0_tex, "VoxelGI Cascade Light Aniso 0 Texture");
 		cascade.light_aniso_1_tex = RD::get_singleton()->texture_create(tf_aniso1, RD::TextureView());
+		RD::get_singleton()->set_resource_name(cascade.light_aniso_1_tex, "VoxelGI Cascade Light Aniso 1 Texture");
 
 		{
 			RD::TextureView tv;
@@ -540,9 +560,11 @@ void GI::SDFGI::create(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world
 		/* Probe History */
 
 		cascade.lightprobe_history_tex = RD::get_singleton()->texture_create(tf_probe_history, RD::TextureView());
+		RD::get_singleton()->set_resource_name(cascade.lightprobe_history_tex, "VoxelGI Cascade LightProbe History Texture");
 		RD::get_singleton()->texture_clear(cascade.lightprobe_history_tex, Color(0, 0, 0, 0), 0, 1, 0, tf_probe_history.array_layers); //needs to be cleared for average to work
 
 		cascade.lightprobe_average_tex = RD::get_singleton()->texture_create(tf_probe_average, RD::TextureView());
+		RD::get_singleton()->set_resource_name(cascade.lightprobe_average_tex, "VoxelGI Cascade LightProbe Average Texture");
 		RD::get_singleton()->texture_clear(cascade.lightprobe_average_tex, Color(0, 0, 0, 0), 0, 1, 0, 1); //needs to be cleared for average to work
 
 		/* Buffers */
@@ -2444,6 +2466,7 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vector<RID
 			tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
 
 			texture = RD::get_singleton()->texture_create(tf, RD::TextureView());
+			RD::get_singleton()->set_resource_name(texture, "VoxelGI Instance Texture");
 
 			RD::get_singleton()->texture_clear(texture, Color(0, 0, 0, 0), 0, levels.size(), 0, 1);
 
@@ -2573,6 +2596,7 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vector<RID
 						dtf.usage_bits |= RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
 					}
 					dmap.texture = RD::get_singleton()->texture_create(dtf, RD::TextureView());
+					RD::get_singleton()->set_resource_name(dmap.texture, "VoxelGI Instance DMap Texture");
 
 					if (dynamic_maps.size() == 0) {
 						// Render depth for first one.
@@ -2580,6 +2604,7 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vector<RID
 						dtf.format = RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_D16_UNORM, RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ? RD::DATA_FORMAT_D16_UNORM : RD::DATA_FORMAT_X8_D24_UNORM_PACK32;
 						dtf.usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
 						dmap.fb_depth = RD::get_singleton()->texture_create(dtf, RD::TextureView());
+						RD::get_singleton()->set_resource_name(dmap.fb_depth, "VoxelGI Instance DMap FB Depth");
 					}
 
 					//just use depth as-is
@@ -2587,13 +2612,17 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vector<RID
 					dtf.usage_bits = RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
 
 					dmap.depth = RD::get_singleton()->texture_create(dtf, RD::TextureView());
+					RD::get_singleton()->set_resource_name(dmap.depth, "VoxelGI Instance DMap Depth");
 
 					if (dynamic_maps.size() == 0) {
 						dtf.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
 						dtf.usage_bits = RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
 						dmap.albedo = RD::get_singleton()->texture_create(dtf, RD::TextureView());
+						RD::get_singleton()->set_resource_name(dmap.albedo, "VoxelGI Instance DMap Albedo");
 						dmap.normal = RD::get_singleton()->texture_create(dtf, RD::TextureView());
+						RD::get_singleton()->set_resource_name(dmap.normal, "VoxelGI Instance DMap Normal");
 						dmap.orm = RD::get_singleton()->texture_create(dtf, RD::TextureView());
+						RD::get_singleton()->set_resource_name(dmap.orm, "VoxelGI Instance DMap ORM");
 
 						Vector<RID> fb;
 						fb.push_back(dmap.albedo);
@@ -3342,37 +3371,40 @@ void GI::init(RendererSceneSkyRD *p_sky) {
 		//calculate tables
 		String defines = "\n#define SDFGI_OCT_SIZE " + itos(SDFGI::LIGHTPROBE_OCT_SIZE) + "\n";
 		Vector<String> gi_modes;
+
 		gi_modes.push_back("\n#define USE_VOXEL_GI_INSTANCES\n"); // MODE_VOXEL_GI
 		gi_modes.push_back("\n#define USE_SDFGI\n"); // MODE_SDFGI
 		gi_modes.push_back("\n#define USE_SDFGI\n\n#define USE_VOXEL_GI_INSTANCES\n"); // MODE_COMBINED
-		gi_modes.push_back("\n#define MODE_HALF_RES\n#define USE_VOXEL_GI_INSTANCES\n"); // MODE_HALF_RES_VOXEL_GI
-		gi_modes.push_back("\n#define MODE_HALF_RES\n#define USE_SDFGI\n"); // MODE_HALF_RES_SDFGI
-		gi_modes.push_back("\n#define MODE_HALF_RES\n#define USE_SDFGI\n\n#define USE_VOXEL_GI_INSTANCES\n"); // MODE_HALF_RES_COMBINED
-
-		gi_modes.push_back("\n#define USE_VOXEL_GI_INSTANCES\n#define USE_MULTIVIEW\n"); // MODE_VOXEL_GI_MULTIVIEW
-		gi_modes.push_back("\n#define USE_SDFGI\n#define USE_MULTIVIEW\n"); // MODE_SDFGI_MULTIVIEW
-		gi_modes.push_back("\n#define USE_SDFGI\n\n#define USE_VOXEL_GI_INSTANCES\n#define USE_MULTIVIEW\n"); // MODE_COMBINED_MULTIVIEW
-		gi_modes.push_back("\n#define MODE_HALF_RES\n#define USE_VOXEL_GI_INSTANCES\n#define USE_MULTIVIEW\n"); // MODE_HALF_RES_VOXEL_GI_MULTIVIEW
-		gi_modes.push_back("\n#define MODE_HALF_RES\n#define USE_SDFGI\n#define USE_MULTIVIEW\n"); // MODE_HALF_RES_SDFGI_MULTIVIEW
-		gi_modes.push_back("\n#define MODE_HALF_RES\n#define USE_SDFGI\n\n#define USE_VOXEL_GI_INSTANCES\n#define USE_MULTIVIEW\n"); // MODE_HALF_RES_COMBINED_MULTIVIEW
 
 		shader.initialize(gi_modes, defines);
+		shader_version = shader.version_create();
+
+		Vector<RD::PipelineSpecializationConstant> specialization_constants;
+
+		{
+			RD::PipelineSpecializationConstant sc;
+			sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_BOOL;
+			sc.constant_id = 0; // SHADER_SPECIALIZATION_HALF_RES
+			sc.bool_value = false;
+			specialization_constants.push_back(sc);
 
-		if (!RendererCompositorRD::singleton->is_xr_enabled()) {
-			shader.set_variant_enabled(MODE_VOXEL_GI_MULTIVIEW, false);
-			shader.set_variant_enabled(MODE_SDFGI_MULTIVIEW, false);
-			shader.set_variant_enabled(MODE_COMBINED_MULTIVIEW, false);
-			shader.set_variant_enabled(MODE_HALF_RES_VOXEL_GI_MULTIVIEW, false);
-			shader.set_variant_enabled(MODE_HALF_RES_SDFGI_MULTIVIEW, false);
-			shader.set_variant_enabled(MODE_HALF_RES_COMBINED_MULTIVIEW, false);
+			sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_BOOL;
+			sc.constant_id = 1; // SHADER_SPECIALIZATION_USE_FULL_PROJECTION_MATRIX
+			sc.bool_value = false;
+			specialization_constants.push_back(sc);
+
+			sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_BOOL;
+			sc.constant_id = 2; // SHADER_SPECIALIZATION_USE_VRS
+			sc.bool_value = false;
+			specialization_constants.push_back(sc);
 		}
 
-		shader_version = shader.version_create();
-		for (int i = 0; i < MODE_MAX; i++) {
-			if (shader.is_variant_enabled(i)) {
-				pipelines[i] = RD::get_singleton()->compute_pipeline_create(shader.version_get_shader(shader_version, i));
-			} else {
-				pipelines[i] = RID();
+		for (int v = 0; v < SHADER_SPECIALIZATION_VARIATIONS; v++) {
+			specialization_constants.ptrw()[0].bool_value = (v & SHADER_SPECIALIZATION_HALF_RES) ? true : false;
+			specialization_constants.ptrw()[1].bool_value = (v & SHADER_SPECIALIZATION_USE_FULL_PROJECTION_MATRIX) ? true : false;
+			specialization_constants.ptrw()[2].bool_value = (v & SHADER_SPECIALIZATION_USE_VRS) ? true : false;
+			for (int i = 0; i < MODE_MAX; i++) {
+				pipelines[v][i] = RD::get_singleton()->compute_pipeline_create(shader.version_get_shader(shader_version, i), specialization_constants);
 			}
 		}
 
@@ -3564,25 +3596,17 @@ void GI::RenderBuffersGI::free() {
 	}
 
 	if (ambient_buffer.is_valid()) {
-		if (view_count == 1) {
-			// Only one view? then these are copies of our main buffers.
-			ambient_view[0] = RID();
-			reflection_view[0] = RID();
-		} else {
-			// Multiple views? free our slices.
-			for (uint32_t v = 0; v < view_count; v++) {
-				RD::get_singleton()->free(ambient_view[v]);
-				RD::get_singleton()->free(reflection_view[v]);
-				ambient_view[v] = RID();
-				reflection_view[v] = RID();
-			}
-		}
-
-		// Now we can free our buffers.
 		RD::get_singleton()->free(ambient_buffer);
 		RD::get_singleton()->free(reflection_buffer);
 		ambient_buffer = RID();
 		reflection_buffer = RID();
+
+		// these are automatically freed when we free the textures, so just reset..
+		for (uint32_t v = 0; v < RendererSceneRender::MAX_RENDER_VIEWS; v++) {
+			ambient_slice[v] = RID();
+			reflection_slice[v] = RID();
+		}
+
 		view_count = 0;
 	}
 
@@ -3592,7 +3616,7 @@ void GI::RenderBuffersGI::free() {
 	}
 }
 
-void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_voxel_gi_buffer, RID p_environment, uint32_t p_view_count, const CameraMatrix *p_projections, const Vector3 *p_eye_offsets, const Transform3D &p_cam_transform, const PagedArray<RID> &p_voxel_gi_instances, RendererSceneRenderRD *p_scene_render) {
+void GI::process_gi(RID p_render_buffers, const RID *p_normal_roughness_slices, RID p_voxel_gi_buffer, const RID *p_vrs_slices, RID p_environment, uint32_t p_view_count, const CameraMatrix *p_projections, const Vector3 *p_eye_offsets, const Transform3D &p_cam_transform, const PagedArray<RID> &p_voxel_gi_instances, RendererSceneRenderRD *p_scene_render) {
 	RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
 	RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
 
@@ -3606,14 +3630,13 @@ void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_v
 	if (rb->rbgi.ambient_buffer.is_null() || rb->rbgi.using_half_size_gi != half_resolution || rb->rbgi.view_count != p_view_count) {
 		// Free our old buffer if applicable
 		if (rb->rbgi.ambient_buffer.is_valid()) {
-			if (rb->rbgi.view_count > 1) {
-				for (uint32_t v = 0; v < rb->rbgi.view_count; v++) {
-					RD::get_singleton()->free(rb->rbgi.ambient_view[v]);
-					RD::get_singleton()->free(rb->rbgi.reflection_view[v]);
-				}
-			}
 			RD::get_singleton()->free(rb->rbgi.ambient_buffer);
 			RD::get_singleton()->free(rb->rbgi.reflection_buffer);
+
+			for (uint32_t v = 0; v < RendererSceneRender::MAX_RENDER_VIEWS; v++) {
+				rb->rbgi.ambient_slice[v] = RID();
+				rb->rbgi.reflection_slice[v] = RID();
+			}
 		}
 
 		// Remember the view count we're using
@@ -3637,18 +3660,19 @@ void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_v
 		}
 		tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
 		rb->rbgi.ambient_buffer = RD::get_singleton()->texture_create(tf, RD::TextureView());
+		RD::get_singleton()->set_resource_name(rb->rbgi.ambient_buffer, "GI Ambient Buffer");
 		rb->rbgi.reflection_buffer = RD::get_singleton()->texture_create(tf, RD::TextureView());
+		RD::get_singleton()->set_resource_name(rb->rbgi.reflection_buffer, "GI Reflection Buffer");
 		rb->rbgi.using_half_size_gi = half_resolution;
 
 		if (p_view_count == 1) {
-			// Just one view? Copy our buffers
-			rb->rbgi.ambient_view[0] = rb->rbgi.ambient_buffer;
-			rb->rbgi.reflection_view[0] = rb->rbgi.reflection_buffer;
+			// Just copy, we don't need to create slices
+			rb->rbgi.ambient_slice[0] = rb->rbgi.ambient_buffer;
+			rb->rbgi.reflection_slice[0] = rb->rbgi.reflection_buffer;
 		} else {
-			// More then one view? Create slices for each view
 			for (uint32_t v = 0; v < p_view_count; v++) {
-				rb->rbgi.ambient_view[v] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), rb->rbgi.ambient_buffer, v, 0);
-				rb->rbgi.reflection_view[v] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), rb->rbgi.reflection_buffer, v, 0);
+				rb->rbgi.ambient_slice[v] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), rb->rbgi.ambient_buffer, v, 0);
+				rb->rbgi.reflection_slice[v] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), rb->rbgi.reflection_buffer, v, 0);
 			}
 		}
 	}
@@ -3681,29 +3705,45 @@ void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_v
 	// Now compute the contents of our buffers.
 	RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(true);
 
-	for (uint32_t v = 0; v < p_view_count; v++) {
-		// Render each eye seperately.
-		// We need to look into whether we can make our compute shader use Multiview but not sure that works or makes a difference..
+	// Render each eye seperately.
+	// We need to look into whether we can make our compute shader use Multiview but not sure that works or makes a difference..
 
-		// setup our push constant
+	// setup our push constant
 
-		PushConstant push_constant;
+	PushConstant push_constant;
 
-		push_constant.view_index = v;
-		push_constant.orthogonal = p_projections[v].is_orthogonal();
-		push_constant.max_voxel_gi_instances = MIN((uint64_t)MAX_VOXEL_GI_INSTANCES, p_voxel_gi_instances.size());
-		push_constant.high_quality_vct = voxel_gi_quality == RS::VOXEL_GI_QUALITY_HIGH;
+	push_constant.max_voxel_gi_instances = MIN((uint64_t)MAX_VOXEL_GI_INSTANCES, p_voxel_gi_instances.size());
+	push_constant.high_quality_vct = voxel_gi_quality == RS::VOXEL_GI_QUALITY_HIGH;
 
-		push_constant.z_near = p_projections[v].get_z_near();
-		push_constant.z_far = p_projections[v].get_z_far();
+	// these should be the same for all views
+	push_constant.orthogonal = p_projections[0].is_orthogonal();
+	push_constant.z_near = p_projections[0].get_z_near();
+	push_constant.z_far = p_projections[0].get_z_far();
 
-		push_constant.proj_info[0] = -2.0f / (rb->internal_width * p_projections[v].matrix[0][0]);
-		push_constant.proj_info[1] = -2.0f / (rb->internal_height * p_projections[v].matrix[1][1]);
-		push_constant.proj_info[2] = (1.0f - p_projections[v].matrix[0][2]) / p_projections[v].matrix[0][0];
-		push_constant.proj_info[3] = (1.0f + p_projections[v].matrix[1][2]) / p_projections[v].matrix[1][1];
+	// these are only used if we have 1 view, else we use the projections in our scene data
+	push_constant.proj_info[0] = -2.0f / (rb->internal_width * p_projections[0].matrix[0][0]);
+	push_constant.proj_info[1] = -2.0f / (rb->internal_height * p_projections[0].matrix[1][1]);
+	push_constant.proj_info[2] = (1.0f - p_projections[0].matrix[0][2]) / p_projections[0].matrix[0][0];
+	push_constant.proj_info[3] = (1.0f + p_projections[0].matrix[1][2]) / p_projections[0].matrix[1][1];
 
-		bool use_sdfgi = rb->sdfgi != nullptr;
-		bool use_voxel_gi_instances = push_constant.max_voxel_gi_instances > 0;
+	bool use_sdfgi = rb->sdfgi != nullptr;
+	bool use_voxel_gi_instances = push_constant.max_voxel_gi_instances > 0;
+
+	uint32_t pipeline_specialization = 0;
+	if (rb->rbgi.using_half_size_gi) {
+		pipeline_specialization |= SHADER_SPECIALIZATION_HALF_RES;
+	}
+	if (p_view_count > 1) {
+		pipeline_specialization |= SHADER_SPECIALIZATION_USE_FULL_PROJECTION_MATRIX;
+	}
+	if (p_vrs_slices[0].is_valid()) {
+		pipeline_specialization |= SHADER_SPECIALIZATION_USE_VRS;
+	}
+
+	Mode mode = (use_sdfgi && use_voxel_gi_instances) ? MODE_COMBINED : (use_sdfgi ? MODE_SDFGI : MODE_VOXEL_GI);
+
+	for (uint32_t v = 0; v < p_view_count; v++) {
+		push_constant.view_index = v;
 
 		// setup our uniform set
 		if (rb->rbgi.uniform_set[v].is_null() || !RD::get_singleton()->uniform_set_is_valid(rb->rbgi.uniform_set[v])) {
@@ -3790,7 +3830,7 @@ void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_v
 				RD::Uniform u;
 				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
 				u.binding = 9;
-				u.append_id(rb->rbgi.ambient_view[v]);
+				u.append_id(rb->rbgi.ambient_slice[v]);
 				uniforms.push_back(u);
 			}
 
@@ -3798,7 +3838,7 @@ void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_v
 				RD::Uniform u;
 				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
 				u.binding = 10;
-				u.append_id(rb->rbgi.reflection_view[v]);
+				u.append_id(rb->rbgi.reflection_slice[v]);
 				uniforms.push_back(u);
 			}
 
@@ -3824,7 +3864,7 @@ void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_v
 				RD::Uniform u;
 				u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
 				u.binding = 13;
-				u.append_id(p_normal_roughness_views[v]);
+				u.append_id(p_normal_roughness_slices[v]);
 				uniforms.push_back(u);
 			}
 			{
@@ -3865,27 +3905,19 @@ void GI::process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_v
 				u.append_id(rb->rbgi.scene_data_ubo);
 				uniforms.push_back(u);
 			}
+			{
+				RD::Uniform u;
+				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
+				u.binding = 19;
+				RID buffer = p_vrs_slices[v].is_valid() ? p_vrs_slices[v] : texture_storage->texture_rd_get_default(RendererRD::DEFAULT_RD_TEXTURE_VRS);
+				u.append_id(buffer);
+				uniforms.push_back(u);
+			}
 
 			rb->rbgi.uniform_set[v] = RD::get_singleton()->uniform_set_create(uniforms, shader.version_get_shader(shader_version, 0), 0);
 		}
 
-		Mode mode;
-
-		if (p_view_count > 1) {
-			if (rb->rbgi.using_half_size_gi) {
-				mode = (use_sdfgi && use_voxel_gi_instances) ? MODE_HALF_RES_COMBINED_MULTIVIEW : (use_sdfgi ? MODE_HALF_RES_SDFGI_MULTIVIEW : MODE_HALF_RES_VOXEL_GI_MULTIVIEW);
-			} else {
-				mode = (use_sdfgi && use_voxel_gi_instances) ? MODE_COMBINED_MULTIVIEW : (use_sdfgi ? MODE_SDFGI_MULTIVIEW : MODE_VOXEL_GI_MULTIVIEW);
-			}
-		} else {
-			if (rb->rbgi.using_half_size_gi) {
-				mode = (use_sdfgi && use_voxel_gi_instances) ? MODE_HALF_RES_COMBINED : (use_sdfgi ? MODE_HALF_RES_SDFGI : MODE_HALF_RES_VOXEL_GI);
-			} else {
-				mode = (use_sdfgi && use_voxel_gi_instances) ? MODE_COMBINED : (use_sdfgi ? MODE_SDFGI : MODE_VOXEL_GI);
-			}
-		}
-
-		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, pipelines[mode]);
+		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, pipelines[pipeline_specialization][mode]);
 		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, rb->rbgi.uniform_set[v], 0);
 		RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
 

+ 15 - 18
servers/rendering/renderer_rd/environment/gi.h

@@ -660,13 +660,13 @@ public:
 
 		/* GI buffers */
 		RID ambient_buffer;
+		RID ambient_slice[RendererSceneRender::MAX_RENDER_VIEWS];
 		RID reflection_buffer;
-		RID ambient_view[RendererSceneRender::MAX_RENDER_VIEWS];
-		RID reflection_view[RendererSceneRender::MAX_RENDER_VIEWS];
-		RID uniform_set[RendererSceneRender::MAX_RENDER_VIEWS];
+		RID reflection_slice[RendererSceneRender::MAX_RENDER_VIEWS];
 		bool using_half_size_gi = false;
 		uint32_t view_count = 1;
 
+		RID uniform_set[RendererSceneRender::MAX_RENDER_VIEWS];
 		RID scene_data_ubo;
 
 		void free();
@@ -729,44 +729,41 @@ public:
 	};
 
 	struct PushConstant {
-		uint32_t view_index;
 		uint32_t max_voxel_gi_instances;
 		uint32_t high_quality_vct;
 		uint32_t orthogonal;
+		uint32_t view_index;
 
 		float proj_info[4];
 
 		float z_near;
 		float z_far;
-		float pad1;
 		float pad2;
+		float pad3;
 	};
 
 	RID sdfgi_ubo;
+
 	enum Mode {
 		MODE_VOXEL_GI,
 		MODE_SDFGI,
 		MODE_COMBINED,
-		MODE_HALF_RES_VOXEL_GI,
-		MODE_HALF_RES_SDFGI,
-		MODE_HALF_RES_COMBINED,
-
-		MODE_VOXEL_GI_MULTIVIEW,
-		MODE_SDFGI_MULTIVIEW,
-		MODE_COMBINED_MULTIVIEW,
-		MODE_HALF_RES_VOXEL_GI_MULTIVIEW,
-		MODE_HALF_RES_SDFGI_MULTIVIEW,
-		MODE_HALF_RES_COMBINED_MULTIVIEW,
-
 		MODE_MAX
 	};
 
+	enum ShaderSpecializations {
+		SHADER_SPECIALIZATION_HALF_RES = 1 << 0,
+		SHADER_SPECIALIZATION_USE_FULL_PROJECTION_MATRIX = 1 << 1,
+		SHADER_SPECIALIZATION_USE_VRS = 1 << 2,
+		SHADER_SPECIALIZATION_VARIATIONS = 0x07,
+	};
+
 	RID default_voxel_gi_buffer;
 
 	bool half_resolution = false;
 	GiShaderRD shader;
 	RID shader_version;
-	RID pipelines[MODE_MAX];
+	RID pipelines[SHADER_SPECIALIZATION_VARIATIONS][MODE_MAX];
 
 	GI();
 	~GI();
@@ -777,7 +774,7 @@ public:
 	SDFGI *create_sdfgi(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world_position, uint32_t p_requested_history_size);
 
 	void setup_voxel_gi_instances(RID p_render_buffers, const Transform3D &p_transform, const PagedArray<RID> &p_voxel_gi_instances, uint32_t &r_voxel_gi_instances_used, RendererSceneRenderRD *p_scene_render);
-	void process_gi(RID p_render_buffers, RID *p_normal_roughness_views, RID p_voxel_gi_buffer, RID p_environment, uint32_t p_view_count, const CameraMatrix *p_projections, const Vector3 *p_eye_offsets, const Transform3D &p_cam_transform, const PagedArray<RID> &p_voxel_gi_instances, RendererSceneRenderRD *p_scene_render);
+	void process_gi(RID p_render_buffers, const RID *p_normal_roughness_slices, RID p_voxel_gi_buffer, const RID *p_vrs_slices, RID p_environment, uint32_t p_view_count, const CameraMatrix *p_projections, const Vector3 *p_eye_offsets, const Transform3D &p_cam_transform, const PagedArray<RID> &p_voxel_gi_instances, RendererSceneRenderRD *p_scene_render);
 
 	RID voxel_gi_instance_create(RID p_base);
 	void voxel_gi_instance_set_transform_to_data(RID p_probe, const Transform3D &p_xform);

+ 40 - 62
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp

@@ -171,29 +171,24 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::ensure_voxelgi()
 }
 
 void RenderForwardClustered::RenderBufferDataForwardClustered::clear() {
+	// note, slices are freed automatically when the parent texture is freed so we just clear them.
+	for (uint32_t v = 0; v < RendererSceneRender::MAX_RENDER_VIEWS; v++) {
+		color_views[v] = RID();
+		depth_views[v] = RID();
+		color_msaa_views[v] = RID();
+		depth_msaa_views[v] = RID();
+		normal_roughness_views[v] = RID();
+		normal_roughness_msaa_views[v] = RID();
+		voxelgi_views[v] = RID();
+		voxelgi_msaa_views[v] = RID();
+		vrs_views[v] = RID();
+	}
+
 	if (voxelgi_buffer != RID()) {
 		RD::get_singleton()->free(voxelgi_buffer);
 		voxelgi_buffer = RID();
 
-		if (view_count == 1) {
-			voxelgi_views[0] = RID();
-		} else {
-			for (uint32_t v = 0; v < view_count; v++) {
-				RD::get_singleton()->free(voxelgi_views[v]);
-				voxelgi_views[v] = RID();
-			}
-		}
-
 		if (voxelgi_buffer_msaa.is_valid()) {
-			if (view_count == 1) {
-				voxelgi_msaa_views[0] = RID();
-			} else {
-				for (uint32_t v = 0; v < view_count; v++) {
-					RD::get_singleton()->free(voxelgi_msaa_views[v]);
-					voxelgi_msaa_views[v] = RID();
-				}
-			}
-
 			RD::get_singleton()->free(voxelgi_buffer_msaa);
 			voxelgi_buffer_msaa = RID();
 		}
@@ -202,35 +197,11 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::clear() {
 	}
 
 	if (color_msaa.is_valid()) {
-		if (view_count == 1) {
-			color_views[0] = RID();
-			color_msaa_views[0] = RID();
-		} else {
-			for (uint32_t v = 0; v < view_count; v++) {
-				RD::get_singleton()->free(color_views[v]);
-				RD::get_singleton()->free(color_msaa_views[v]);
-				color_views[v] = RID();
-				color_msaa_views[v] = RID();
-			}
-		}
-
 		RD::get_singleton()->free(color_msaa);
 		color_msaa = RID();
 	}
 
 	if (depth_msaa.is_valid()) {
-		if (view_count == 1) {
-			depth_views[0] = RID();
-			depth_msaa_views[0] = RID();
-		} else {
-			for (uint32_t v = 0; v < view_count; v++) {
-				RD::get_singleton()->free(depth_views[v]);
-				RD::get_singleton()->free(depth_msaa_views[v]);
-				depth_views[v] = RID();
-				depth_msaa_views[v] = RID();
-			}
-		}
-
 		RD::get_singleton()->free(depth_msaa);
 		depth_msaa = RID();
 	}
@@ -245,33 +216,17 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::clear() {
 	}
 
 	color = RID();
+	color_only_fb = RID();
 	depth = RID();
 	depth_fb = RID();
 
 	color_framebuffers.clear(); // Color pass framebuffers are freed automatically by their dependency relations
 
 	if (normal_roughness_buffer.is_valid()) {
-		if (view_count == 1) {
-			normal_roughness_views[0] = RID();
-		} else {
-			for (uint32_t v = 0; v < view_count; v++) {
-				RD::get_singleton()->free(normal_roughness_views[v]);
-				normal_roughness_views[v] = RID();
-			}
-		}
-
 		RD::get_singleton()->free(normal_roughness_buffer);
 		normal_roughness_buffer = RID();
 
 		if (normal_roughness_buffer_msaa.is_valid()) {
-			if (view_count == 1) {
-				normal_roughness_msaa_views[0] = RID();
-			} else {
-				for (uint32_t v = 0; v < view_count; v++) {
-					RD::get_singleton()->free(normal_roughness_msaa_views[v]);
-					normal_roughness_msaa_views[v] = RID();
-				}
-			}
 			RD::get_singleton()->free(normal_roughness_buffer_msaa);
 			normal_roughness_buffer_msaa = RID();
 		}
@@ -294,11 +249,12 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::clear() {
 	}
 }
 
-void RenderForwardClustered::RenderBufferDataForwardClustered::configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count) {
+void RenderForwardClustered::RenderBufferDataForwardClustered::configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count, RID p_vrs_texture) {
 	clear();
 
 	msaa = p_msaa;
 	use_taa = p_use_taa;
+	vrs = p_vrs_texture;
 
 	width = p_width;
 	height = p_height;
@@ -307,11 +263,26 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::configure(RID p_c
 	color = p_color_buffer;
 	depth = p_depth_buffer;
 
+	if (vrs.is_valid()) {
+		if (view_count == 1) {
+			// just reuse
+			vrs_views[0] = vrs;
+		} else {
+			// create slices
+			for (uint32_t v = 0; v < view_count; v++) {
+				vrs_views[v] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), vrs, v, 0);
+			}
+		}
+	}
+
 	if (p_msaa == RS::VIEWPORT_MSAA_DISABLED) {
 		{
 			Vector<RID> fb;
 			fb.push_back(p_color_buffer);
 			fb.push_back(depth);
+			if (vrs.is_valid()) {
+				fb.push_back(vrs);
+			}
 
 			color_only_fb = RD::get_singleton()->framebuffer_create(fb, RenderingDevice::INVALID_ID, view_count);
 		}
@@ -371,6 +342,9 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::configure(RID p_c
 			Vector<RID> fb;
 			fb.push_back(color_msaa);
 			fb.push_back(depth_msaa);
+			if (vrs.is_valid()) {
+				fb.push_back(vrs);
+			}
 
 			color_only_fb = RD::get_singleton()->framebuffer_create(fb, RenderingDevice::INVALID_ID, view_count);
 		}
@@ -409,6 +383,10 @@ RID RenderForwardClustered::RenderBufferDataForwardClustered::get_color_pass_fb(
 
 	fb.push_back(use_msaa ? depth_msaa : depth);
 
+	if (vrs.is_valid()) {
+		fb.push_back(vrs);
+	}
+
 	int v_count = (p_color_pass_flags & COLOR_PASS_FLAG_MULTIVIEW) ? view_count : 1;
 	RID framebuffer = RD::get_singleton()->framebuffer_create(fb, RD::INVALID_ID, v_count);
 	color_framebuffers[p_color_pass_flags] = framebuffer;
@@ -1673,8 +1651,8 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 		continue_depth = !finish_depth;
 	}
 
-	RID null_rids[2];
-	_pre_opaque_render(p_render_data, using_ssao, using_ssil, using_sdfgi || using_voxelgi, render_buffer ? render_buffer->normal_roughness_views : null_rids, render_buffer ? render_buffer->voxelgi_buffer : RID());
+	RID nullrids[RendererSceneRender::MAX_RENDER_VIEWS];
+	_pre_opaque_render(p_render_data, using_ssao, using_ssil, using_sdfgi || using_voxelgi, render_buffer ? render_buffer->normal_roughness_views : nullrids, render_buffer ? render_buffer->voxelgi_buffer : RID(), render_buffer ? render_buffer->vrs_views : nullrids);
 
 	RD::get_singleton()->draw_command_begin_label("Render Opaque Pass");
 

+ 6 - 2
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h

@@ -107,11 +107,14 @@ class RenderForwardClustered : public RendererSceneRenderRD {
 		RID depth_normal_roughness_voxelgi_fb;
 		RID color_only_fb;
 		RID specular_only_fb;
+
+		RID vrs;
+
 		int width, height;
 		HashMap<uint32_t, RID> color_framebuffers;
 
 		// for multiview
-		uint32_t view_count;
+		uint32_t view_count = 1;
 		RID color_views[RendererSceneRender::MAX_RENDER_VIEWS]; // we should rewrite this so we get access to the existing views in our renderer, something we can address when we reorg this
 		RID depth_views[RendererSceneRender::MAX_RENDER_VIEWS]; // we should rewrite this so we get access to the existing views in our renderer, something we can address when we reorg this
 		RID color_msaa_views[RendererSceneRender::MAX_RENDER_VIEWS];
@@ -120,13 +123,14 @@ class RenderForwardClustered : public RendererSceneRenderRD {
 		RID normal_roughness_msaa_views[RendererSceneRender::MAX_RENDER_VIEWS];
 		RID voxelgi_views[RendererSceneRender::MAX_RENDER_VIEWS];
 		RID voxelgi_msaa_views[RendererSceneRender::MAX_RENDER_VIEWS];
+		RID vrs_views[RendererSceneRender::MAX_RENDER_VIEWS];
 
 		RID render_sdfgi_uniform_set;
 		void ensure_specular();
 		void ensure_voxelgi();
 		void ensure_velocity();
 		void clear();
-		virtual void configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count);
+		virtual void configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count, RID p_vrs_texture);
 		RID get_color_pass_fb(uint32_t p_color_pass_flags);
 
 		~RenderBufferDataForwardClustered();

+ 27 - 11
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp

@@ -87,10 +87,11 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::clear() {
 	}
 }
 
-void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count) {
+void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count, RID p_vrs_texture) {
 	clear();
 
 	msaa = p_msaa;
+	vrs = p_vrs_texture;
 
 	Size2i target_size = RD::get_singleton()->texture_size(p_target_buffer);
 
@@ -108,6 +109,9 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b
 		Vector<RID> fb;
 		fb.push_back(p_color_buffer); // 0 - color buffer
 		fb.push_back(depth); // 1 - depth buffer
+		if (vrs.is_valid()) {
+			fb.push_back(vrs); // 2 - vrs texture
+		}
 
 		// Now define our subpasses
 		Vector<RD::FramebufferPass> passes;
@@ -116,6 +120,9 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b
 		// re-using the same attachments
 		pass.color_attachments.push_back(0);
 		pass.depth_attachment = 1;
+		if (vrs.is_valid()) {
+			pass.vrs_attachment = 2;
+		}
 
 		// - opaque pass
 		passes.push_back(pass);
@@ -131,12 +138,13 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b
 
 		if (!is_scaled) {
 			// - add blit to 2D pass
-			fb.push_back(p_target_buffer); // 2 - target buffer
+			int target_buffer_id = fb.size();
+			fb.push_back(p_target_buffer); // 2/3 - target buffer
 
 			RD::FramebufferPass blit_pass;
-			blit_pass.color_attachments.push_back(2);
+			blit_pass.color_attachments.push_back(target_buffer_id);
 			blit_pass.input_attachments.push_back(0);
-			passes.push_back(blit_pass);
+			passes.push_back(blit_pass); // this doesn't need VRS
 
 			color_fbs[FB_CONFIG_FOUR_SUBPASSES] = RD::get_singleton()->framebuffer_create_multipass(fb, passes, RenderingDevice::INVALID_ID, view_count);
 		} else {
@@ -179,6 +187,9 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b
 			Vector<RID> fb;
 			fb.push_back(color_msaa); // 0 - msaa color buffer
 			fb.push_back(depth_msaa); // 1 - msaa depth buffer
+			if (vrs.is_valid()) {
+				fb.push_back(vrs); // 2 - vrs texture
+			}
 
 			// Now define our subpasses
 			Vector<RD::FramebufferPass> passes;
@@ -187,18 +198,22 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b
 			// re-using the same attachments
 			pass.color_attachments.push_back(0);
 			pass.depth_attachment = 1;
+			if (vrs.is_valid()) {
+				pass.vrs_attachment = 2;
+			}
 
 			// - opaque pass
 			passes.push_back(pass);
 
 			// - add sky pass
-			fb.push_back(color); // 2 - color buffer
+			int color_buffer_id = fb.size();
+			fb.push_back(color); // color buffer
 			passes.push_back(pass); // without resolve for our 3 + 4 subpass config
 			{
 				// but with resolve for our 2 subpass config
 				Vector<RD::FramebufferPass> two_passes;
 				two_passes.push_back(pass); // opaque subpass without resolve
-				pass.resolve_attachments.push_back(2);
+				pass.resolve_attachments.push_back(color_buffer_id);
 				two_passes.push_back(pass); // sky subpass with resolve
 
 				color_fbs[FB_CONFIG_TWO_SUBPASSES] = RD::get_singleton()->framebuffer_create_multipass(fb, two_passes, RenderingDevice::INVALID_ID, view_count);
@@ -217,10 +232,11 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b
 
 			if (!is_scaled) {
 				// - add blit to 2D pass
-				fb.push_back(p_target_buffer); // 3 - target buffer
+				int target_buffer_id = fb.size();
+				fb.push_back(p_target_buffer); // target buffer
 				RD::FramebufferPass blit_pass;
-				blit_pass.color_attachments.push_back(3);
-				blit_pass.input_attachments.push_back(2);
+				blit_pass.color_attachments.push_back(target_buffer_id);
+				blit_pass.input_attachments.push_back(color_buffer_id);
 				passes.push_back(blit_pass);
 
 				color_fbs[FB_CONFIG_FOUR_SUBPASSES] = RD::get_singleton()->framebuffer_create_multipass(fb, passes, RenderingDevice::INVALID_ID, view_count);
@@ -675,8 +691,8 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
 		RD::get_singleton()->draw_command_end_label(); // Setup Sky resolution buffers
 	}
 
-	RID null_rids[2];
-	_pre_opaque_render(p_render_data, false, false, false, null_rids, RID());
+	RID nullrids[RendererSceneRender::MAX_RENDER_VIEWS];
+	_pre_opaque_render(p_render_data, false, false, false, nullrids, RID(), nullrids);
 
 	uint32_t spec_constant_base_flags = 0;
 

+ 3 - 1
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h

@@ -131,12 +131,14 @@ protected:
 		RID depth_msaa;
 		// RID normal_roughness_buffer_msaa;
 
+		RID vrs;
+
 		RID color_fbs[FB_CONFIG_MAX];
 		int width, height;
 		uint32_t view_count;
 
 		void clear();
-		virtual void configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count);
+		virtual void configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count, RID p_vrs_texture);
 
 		~RenderBufferDataForwardMobile();
 	};

+ 35 - 7
servers/rendering/renderer_rd/renderer_scene_render_rd.cpp

@@ -1827,6 +1827,16 @@ void RendererSceneRenderRD::_free_render_buffer_data(RenderBuffers *rb) {
 		rb->sss_texture = RID();
 	}
 
+	if (rb->vrs_fb.is_valid()) {
+		RD::get_singleton()->free(rb->vrs_fb);
+		rb->vrs_fb = RID();
+	}
+
+	if (rb->vrs_texture.is_valid()) {
+		RD::get_singleton()->free(rb->vrs_texture);
+		rb->vrs_texture = RID();
+	}
+
 	for (int i = 0; i < 2; i++) {
 		for (int l = 0; l < rb->blur[i].layers.size(); l++) {
 			for (int m = 0; m < rb->blur[i].layers[l].mipmaps.size(); m++) {
@@ -3151,8 +3161,13 @@ void RendererSceneRenderRD::render_buffers_configure(RID p_render_buffers, RID p
 		}
 	}
 
+	RS::ViewportVRSMode vrs_mode = texture_storage->render_target_get_vrs_mode(rb->render_target);
+	if (is_vrs_supported() && vrs_mode != RS::VIEWPORT_VRS_DISABLED) {
+		vrs->create_vrs_texture(p_internal_width, p_internal_height, p_view_count, rb->vrs_texture, rb->vrs_fb);
+	}
+
 	RID target_texture = texture_storage->render_target_get_rd_texture(rb->render_target);
-	rb->data->configure(rb->internal_texture, rb->depth_texture, target_texture, p_internal_width, p_internal_height, p_msaa, p_use_taa, p_view_count);
+	rb->data->configure(rb->internal_texture, rb->depth_texture, target_texture, p_internal_width, p_internal_height, p_msaa, p_use_taa, p_view_count, rb->vrs_texture);
 
 	if (is_clustered_enabled()) {
 		rb->cluster_builder->setup(Size2i(p_internal_width, p_internal_height), max_cluster_elements, rb->depth_texture, RendererRD::MaterialStorage::get_singleton()->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED), rb->internal_texture);
@@ -4929,7 +4944,7 @@ void RendererSceneRenderRD::_pre_resolve_render(RenderDataRD *p_render_data, boo
 	}
 }
 
-void RendererSceneRenderRD::_pre_opaque_render(RenderDataRD *p_render_data, bool p_use_ssao, bool p_use_ssil, bool p_use_gi, RID *p_normal_roughness_views, RID p_voxel_gi_buffer) {
+void RendererSceneRenderRD::_pre_opaque_render(RenderDataRD *p_render_data, bool p_use_ssao, bool p_use_ssil, bool p_use_gi, const RID *p_normal_roughness_slices, RID p_voxel_gi_buffer, const RID *p_vrs_slices) {
 	// Render shadows while GI is rendering, due to how barriers are handled, this should happen at the same time
 	RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
 
@@ -5004,7 +5019,7 @@ void RendererSceneRenderRD::_pre_opaque_render(RenderDataRD *p_render_data, bool
 
 	//start GI
 	if (render_gi) {
-		gi.process_gi(p_render_data->render_buffers, p_normal_roughness_views, p_voxel_gi_buffer, p_render_data->environment, p_render_data->view_count, p_render_data->view_projection, p_render_data->view_eye_offset, p_render_data->cam_transform, *p_render_data->voxel_gi_instances, this);
+		gi.process_gi(p_render_data->render_buffers, p_normal_roughness_slices, p_voxel_gi_buffer, p_vrs_slices, p_render_data->environment, p_render_data->view_count, p_render_data->view_projection, p_render_data->view_eye_offset, p_render_data->cam_transform, *p_render_data->voxel_gi_instances, this);
 	}
 
 	//Do shadow rendering (in parallel with GI)
@@ -5045,13 +5060,13 @@ void RendererSceneRenderRD::_pre_opaque_render(RenderDataRD *p_render_data, bool
 		}
 
 		if (p_use_ssao) {
-			// TODO make these proper stereo and thus use p_normal_roughness_views correctly
-			_process_ssao(p_render_data->render_buffers, p_render_data->environment, p_normal_roughness_views[0], p_render_data->cam_projection);
+			// TODO make these proper stereo
+			_process_ssao(p_render_data->render_buffers, p_render_data->environment, p_normal_roughness_slices[0], p_render_data->cam_projection);
 		}
 
 		if (p_use_ssil) {
-			// TODO make these proper stereo and thus use p_normal_roughness_views correctly
-			_process_ssil(p_render_data->render_buffers, p_render_data->environment, p_normal_roughness_views[0], p_render_data->cam_projection, p_render_data->cam_transform);
+			// TODO make these proper stereo
+			_process_ssil(p_render_data->render_buffers, p_render_data->environment, p_normal_roughness_slices[0], p_render_data->cam_projection, p_render_data->cam_transform);
 		}
 	}
 
@@ -5240,6 +5255,11 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const CameraData
 		render_data.cluster_max_elements = current_cluster_builder->get_max_cluster_elements();
 	}
 
+	if (rb != nullptr && rb->vrs_fb.is_valid()) {
+		// vrs_fb will only be valid if vrs is enabled
+		vrs->update_vrs_texture(rb->vrs_fb, rb->render_target);
+	}
+
 	_render_scene(&render_data, clear_color);
 
 	if (p_render_buffers.is_valid()) {
@@ -5736,6 +5756,10 @@ int RendererSceneRenderRD::get_max_directional_lights() const {
 	return cluster.max_directional_lights;
 }
 
+bool RendererSceneRenderRD::is_vrs_supported() const {
+	return RD::get_singleton()->has_feature(RD::SUPPORTS_ATTACHMENT_VRS);
+}
+
 bool RendererSceneRenderRD::is_dynamic_gi_supported() const {
 	// usable by default (unless low end = true)
 	return true;
@@ -5975,6 +5999,7 @@ void fog() {
 	bokeh_dof = memnew(RendererRD::BokehDOF(!can_use_storage));
 	copy_effects = memnew(RendererRD::CopyEffects(!can_use_storage));
 	tone_mapper = memnew(RendererRD::ToneMapper);
+	vrs = memnew(RendererRD::VRS);
 }
 
 RendererSceneRenderRD::~RendererSceneRenderRD() {
@@ -5989,6 +6014,9 @@ RendererSceneRenderRD::~RendererSceneRenderRD() {
 	if (tone_mapper) {
 		memdelete(tone_mapper);
 	}
+	if (vrs) {
+		memdelete(vrs);
+	}
 
 	for (const KeyValue<int, ShadowCubemap> &E : shadow_cubemaps) {
 		RD::get_singleton()->free(E.value.cubemap);

+ 7 - 2
servers/rendering/renderer_rd/renderer_scene_render_rd.h

@@ -38,6 +38,7 @@
 #include "servers/rendering/renderer_rd/effects/bokeh_dof.h"
 #include "servers/rendering/renderer_rd/effects/copy_effects.h"
 #include "servers/rendering/renderer_rd/effects/tone_mapper.h"
+#include "servers/rendering/renderer_rd/effects/vrs.h"
 #include "servers/rendering/renderer_rd/environment/gi.h"
 #include "servers/rendering/renderer_rd/renderer_scene_environment_rd.h"
 #include "servers/rendering/renderer_rd/renderer_scene_sky_rd.h"
@@ -104,11 +105,12 @@ protected:
 	RendererRD::BokehDOF *bokeh_dof = nullptr;
 	RendererRD::CopyEffects *copy_effects = nullptr;
 	RendererRD::ToneMapper *tone_mapper = nullptr;
+	RendererRD::VRS *vrs = nullptr;
 	double time = 0.0;
 	double time_step = 0.0;
 
 	struct RenderBufferData {
-		virtual void configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count) = 0;
+		virtual void configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, bool p_use_taa, uint32_t p_view_count, RID p_vrs_texture) = 0;
 		virtual ~RenderBufferData() {}
 	};
 	virtual RenderBufferData *_create_render_buffer_data() = 0;
@@ -149,7 +151,7 @@ protected:
 	void _post_prepass_render(RenderDataRD *p_render_data, bool p_use_gi);
 	void _pre_resolve_render(RenderDataRD *p_render_data, bool p_use_gi);
 
-	void _pre_opaque_render(RenderDataRD *p_render_data, bool p_use_ssao, bool p_use_ssil, bool p_use_gi, RID *p_normal_roughness_views, RID p_voxel_gi_buffer);
+	void _pre_opaque_render(RenderDataRD *p_render_data, bool p_use_ssao, bool p_use_ssil, bool p_use_gi, const RID *p_normal_roughness_slices, RID p_voxel_gi_buffer, const RID *p_vrs_slices);
 
 	void _render_buffers_copy_screen_texture(const RenderDataRD *p_render_data);
 	void _render_buffers_copy_depth_texture(const RenderDataRD *p_render_data);
@@ -492,6 +494,8 @@ private:
 		RID depth_texture; //main depth texture
 		RID texture_fb; // framebuffer for the main texture, ONLY USED FOR MOBILE RENDERER POST EFFECTS, DO NOT USE FOR RENDERING 3D!!!
 		RID upscale_texture; //used when upscaling internal_texture (This uses the same resource as internal_texture if there is no upscaling)
+		RID vrs_texture; // texture for vrs.
+		RID vrs_fb; // framebuffer to write to our vrs texture
 
 		// Access to the layers for each of our views (specifically needed for applying post effects on stereoscopic images)
 		struct View {
@@ -1503,6 +1507,7 @@ public:
 
 	virtual void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) override;
 
+	virtual bool is_vrs_supported() const;
 	virtual bool is_dynamic_gi_supported() const;
 	virtual bool is_clustered_enabled() const;
 	virtual bool is_volumetric_supported() const;

+ 5 - 3
servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl

@@ -88,7 +88,7 @@ layout(set = 0, binding = 0) uniform sampler2DArray source_color;
 layout(set = 1, binding = 0) uniform sampler2DArray source_depth;
 layout(location = 1) out float depth;
 #endif /* MODE_TWO_SOURCES */
-#else
+#else /* MULTIVIEW */
 layout(set = 0, binding = 0) uniform sampler2D source_color;
 #ifdef MODE_TWO_SOURCES
 layout(set = 1, binding = 0) uniform sampler2D source_color2;
@@ -139,7 +139,7 @@ void main() {
 		//uv.y = 1.0 - uv.y;
 		uv = 1.0 - uv;
 	}
-#endif
+#endif /* MODE_PANORAMA_TO_DP */
 
 #ifdef MULTIVIEW
 	vec4 color = textureLod(source_color, uv, 0.0);
@@ -148,12 +148,13 @@ void main() {
 	depth = textureLod(source_depth, uv, 0.0).r;
 #endif /* MODE_TWO_SOURCES */
 
-#else
+#else /* MULTIVIEW */
 	vec4 color = textureLod(source_color, uv, 0.0);
 #ifdef MODE_TWO_SOURCES
 	color += textureLod(source_color2, uv, 0.0);
 #endif /* MODE_TWO_SOURCES */
 #endif /* MULTIVIEW */
+
 	if (params.force_luminance) {
 		color.rgb = vec3(max(max(color.r, color.g), color.b));
 	}
@@ -163,5 +164,6 @@ void main() {
 	if (params.srgb) {
 		color.rgb = linear_to_srgb(color.rgb);
 	}
+
 	frag_color = color;
 }

+ 72 - 0
servers/rendering/renderer_rd/shaders/effects/vrs.glsl

@@ -0,0 +1,72 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef MULTIVIEW
+#ifdef has_VK_KHR_multiview
+#extension GL_EXT_multiview : enable
+#define ViewIndex gl_ViewIndex
+#else // has_VK_KHR_multiview
+#define ViewIndex 0
+#endif // has_VK_KHR_multiview
+#endif //MULTIVIEW
+
+#ifdef MULTIVIEW
+layout(location = 0) out vec3 uv_interp;
+#else
+layout(location = 0) out vec2 uv_interp;
+#endif
+
+void main() {
+	vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0));
+	uv_interp.xy = base_arr[gl_VertexIndex];
+#ifdef MULTIVIEW
+	uv_interp.z = ViewIndex;
+#endif
+
+	gl_Position = vec4(uv_interp.xy * 2.0 - 1.0, 0.0, 1.0);
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef MULTIVIEW
+#ifdef has_VK_KHR_multiview
+#extension GL_EXT_multiview : enable
+#define ViewIndex gl_ViewIndex
+#else // has_VK_KHR_multiview
+#define ViewIndex 0
+#endif // has_VK_KHR_multiview
+#endif //MULTIVIEW
+
+#ifdef MULTIVIEW
+layout(location = 0) in vec3 uv_interp;
+layout(set = 0, binding = 0) uniform sampler2DArray source_color;
+#else /* MULTIVIEW */
+layout(location = 0) in vec2 uv_interp;
+layout(set = 0, binding = 0) uniform sampler2D source_color;
+#endif /* MULTIVIEW */
+
+layout(location = 0) out uint frag_color;
+
+void main() {
+#ifdef MULTIVIEW
+	vec3 uv = uv_interp;
+#else
+	vec2 uv = uv_interp;
+#endif
+
+#ifdef MULTIVIEW
+	vec4 color = textureLod(source_color, uv, 0.0);
+#else /* MULTIVIEW */
+	vec4 color = textureLod(source_color, uv, 0.0);
+#endif /* MULTIVIEW */
+
+	// See if we can change the sampler to one that returns int...
+	frag_color = uint(color.r * 256.0);
+}

+ 125 - 33
servers/rendering/renderer_rd/shaders/environment/gi.glsl

@@ -8,6 +8,12 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
 
 #define M_PI 3.141592
 
+/* Specialization Constants (Toggles) */
+
+layout(constant_id = 0) const bool sc_half_res = false;
+layout(constant_id = 1) const bool sc_use_full_projection_matrix = false;
+layout(constant_id = 2) const bool sc_use_vrs = false;
+
 #define SDFGI_MAX_CASCADES 8
 
 //set 0 for SDFGI and render buffers
@@ -97,18 +103,20 @@ layout(set = 0, binding = 18, std140) uniform SceneData {
 }
 scene_data;
 
+layout(r8ui, set = 0, binding = 19) uniform restrict readonly uimage2D vrs_buffer;
+
 layout(push_constant, std430) uniform Params {
-	uint view_index;
 	uint max_voxel_gi_instances;
 	bool high_quality_vct;
 	bool orthogonal;
+	uint view_index;
 
 	vec4 proj_info;
 
 	float z_near;
 	float z_far;
-	float pad1;
 	float pad2;
+	float pad3;
 }
 params;
 
@@ -140,34 +148,34 @@ vec4 blend_color(vec4 src, vec4 dst) {
 }
 
 vec3 reconstruct_position(ivec2 screen_pos) {
-#ifdef USE_MULTIVIEW
-	vec4 pos;
-	pos.xy = (2.0 * vec2(screen_pos) / vec2(scene_data.screen_size)) - 1.0;
-	pos.z = texelFetch(sampler2D(depth_buffer, linear_sampler), screen_pos, 0).r * 2.0 - 1.0;
-	pos.w = 1.0;
+	if (sc_use_full_projection_matrix) {
+		vec4 pos;
+		pos.xy = (2.0 * vec2(screen_pos) / vec2(scene_data.screen_size)) - 1.0;
+		pos.z = texelFetch(sampler2D(depth_buffer, linear_sampler), screen_pos, 0).r * 2.0 - 1.0;
+		pos.w = 1.0;
 
-	pos = scene_data.inv_projection[params.view_index] * pos;
+		pos = scene_data.inv_projection[params.view_index] * pos;
 
-	return pos.xyz / pos.w;
-#else
-	vec3 pos;
-	pos.z = texelFetch(sampler2D(depth_buffer, linear_sampler), screen_pos, 0).r;
-
-	pos.z = pos.z * 2.0 - 1.0;
-	if (params.orthogonal) {
-		pos.z = ((pos.z + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
+		return pos.xyz / pos.w;
 	} else {
-		pos.z = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - pos.z * (params.z_far - params.z_near));
-	}
-	pos.z = -pos.z;
+		vec3 pos;
+		pos.z = texelFetch(sampler2D(depth_buffer, linear_sampler), screen_pos, 0).r;
+
+		pos.z = pos.z * 2.0 - 1.0;
+		if (params.orthogonal) {
+			pos.z = ((pos.z + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
+		} else {
+			pos.z = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - pos.z * (params.z_far - params.z_near));
+		}
+		pos.z = -pos.z;
 
-	pos.xy = vec2(screen_pos) * params.proj_info.xy + params.proj_info.zw;
-	if (!params.orthogonal) {
-		pos.xy *= pos.z;
-	}
+		pos.xy = vec2(screen_pos) * params.proj_info.xy + params.proj_info.zw;
+		if (!params.orthogonal) {
+			pos.xy *= pos.z;
+		}
 
-	return pos;
-#endif
+		return pos;
+	}
 }
 
 void sdfvoxel_gi_process(uint cascade, vec3 cascade_pos, vec3 cam_pos, vec3 cam_normal, vec3 cam_specular_normal, float roughness, out vec3 diffuse_light, out vec3 specular_light) {
@@ -587,7 +595,6 @@ void voxel_gi_compute(uint index, vec3 position, vec3 normal, vec3 ref_vec, mat3
 
 vec4 fetch_normal_and_roughness(ivec2 pos) {
 	vec4 normal_roughness = texelFetch(sampler2D(normal_roughness_buffer, linear_sampler), pos, 0);
-
 	normal_roughness.xyz = normalize(normal_roughness.xyz * 2.0 - 1.0);
 	return normal_roughness;
 }
@@ -600,7 +607,7 @@ void process_gi(ivec2 pos, vec3 vertex, inout vec4 ambient_light, inout vec4 ref
 	if (normal.length() > 0.5) {
 		//valid normal, can do GI
 		float roughness = normal_roughness.w;
-		vec3 view = -normalize(mat3(scene_data.cam_transform) * (vertex - scene_data.eye_offset[params.view_index].xyz));
+		vec3 view = -normalize(mat3(scene_data.cam_transform) * (vertex - scene_data.eye_offset[gl_GlobalInvocationID.z].xyz));
 		vertex = mat3(scene_data.cam_transform) * vertex;
 		normal = normalize(mat3(scene_data.cam_transform) * normal);
 		vec3 reflection = normalize(reflect(-view, normal));
@@ -648,9 +655,35 @@ void process_gi(ivec2 pos, vec3 vertex, inout vec4 ambient_light, inout vec4 ref
 void main() {
 	ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
 
-#ifdef MODE_HALF_RES
-	pos <<= 1;
-#endif
+	uint vrs_x, vrs_y;
+	if (sc_use_vrs) {
+		ivec2 vrs_pos;
+
+		// Currenty we use a 16x16 texel, possibly some day make this configurable.
+		if (sc_half_res) {
+			vrs_pos = pos >> 3;
+		} else {
+			vrs_pos = pos >> 4;
+		}
+
+		uint vrs_texel = imageLoad(vrs_buffer, vrs_pos).r;
+		// note, valid values for vrs_x and vrs_y are 1, 2 and 4.
+		vrs_x = 1 << ((vrs_texel >> 2) & 3);
+		vrs_y = 1 << (vrs_texel & 3);
+
+		if (mod(pos.x, vrs_x) != 0) {
+			return;
+		}
+
+		if (mod(pos.y, vrs_y) != 0) {
+			return;
+		}
+	}
+
+	if (sc_half_res) {
+		pos <<= 1;
+	}
+
 	if (any(greaterThanEqual(pos, scene_data.screen_size))) { //too large, do nothing
 		return;
 	}
@@ -663,10 +696,69 @@ void main() {
 
 	process_gi(pos, vertex, ambient_light, reflection_light);
 
-#ifdef MODE_HALF_RES
-	pos >>= 1;
-#endif
+	if (sc_half_res) {
+		pos >>= 1;
+	}
 
 	imageStore(ambient_buffer, pos, ambient_light);
 	imageStore(reflection_buffer, pos, reflection_light);
+
+	if (sc_use_vrs) {
+		if (vrs_x > 1) {
+			imageStore(ambient_buffer, pos + ivec2(1, 0), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(1, 0), reflection_light);
+		}
+
+		if (vrs_x > 2) {
+			imageStore(ambient_buffer, pos + ivec2(2, 0), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(2, 0), reflection_light);
+
+			imageStore(ambient_buffer, pos + ivec2(3, 0), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(3, 0), reflection_light);
+		}
+
+		if (vrs_y > 1) {
+			imageStore(ambient_buffer, pos + ivec2(0, 1), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(0, 1), reflection_light);
+		}
+
+		if (vrs_y > 1 && vrs_x > 1) {
+			imageStore(ambient_buffer, pos + ivec2(1, 1), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(1, 1), reflection_light);
+		}
+
+		if (vrs_y > 1 && vrs_x > 2) {
+			imageStore(ambient_buffer, pos + ivec2(2, 1), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(2, 1), reflection_light);
+
+			imageStore(ambient_buffer, pos + ivec2(3, 1), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(3, 1), reflection_light);
+		}
+
+		if (vrs_y > 2) {
+			imageStore(ambient_buffer, pos + ivec2(0, 2), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(0, 2), reflection_light);
+			imageStore(ambient_buffer, pos + ivec2(0, 3), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(0, 3), reflection_light);
+		}
+
+		if (vrs_y > 2 && vrs_x > 1) {
+			imageStore(ambient_buffer, pos + ivec2(1, 2), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(1, 2), reflection_light);
+			imageStore(ambient_buffer, pos + ivec2(1, 3), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(1, 3), reflection_light);
+		}
+
+		if (vrs_y > 2 && vrs_x > 2) {
+			imageStore(ambient_buffer, pos + ivec2(2, 2), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(2, 2), reflection_light);
+			imageStore(ambient_buffer, pos + ivec2(2, 3), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(2, 3), reflection_light);
+
+			imageStore(ambient_buffer, pos + ivec2(3, 2), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(3, 2), reflection_light);
+			imageStore(ambient_buffer, pos + ivec2(3, 3), ambient_light);
+			imageStore(reflection_buffer, pos + ivec2(3, 3), reflection_light);
+		}
+	}
 }

+ 51 - 2
servers/rendering/renderer_rd/storage_rd/texture_storage.cpp

@@ -349,7 +349,6 @@ TextureStorage::TextureStorage() {
 
 		Vector<uint8_t> pv;
 		pv.resize(16 * 4);
-
 		for (int i = 0; i < 16; i++) {
 			pv.set(i * 4 + 0, 0);
 			pv.set(i * 4 + 1, 0);
@@ -358,7 +357,6 @@ TextureStorage::TextureStorage() {
 		}
 
 		{
-			//take the chance and initialize decal atlas to something
 			Vector<Vector<uint8_t>> vpv;
 			vpv.push_back(pv);
 			decal_atlas.texture = RD::get_singleton()->texture_create(tformat, RD::TextureView(), vpv);
@@ -366,6 +364,29 @@ TextureStorage::TextureStorage() {
 		}
 	}
 
+	{ //create default VRS
+
+		RD::TextureFormat tformat;
+		tformat.format = RD::DATA_FORMAT_R8_UINT;
+		tformat.width = 4;
+		tformat.height = 4;
+		tformat.array_layers = 1;
+		tformat.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_VRS_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
+		tformat.texture_type = RD::TEXTURE_TYPE_2D_ARRAY;
+
+		Vector<uint8_t> pv;
+		pv.resize(4 * 4);
+		for (int i = 0; i < 4 * 4; i++) {
+			pv.set(i, 0);
+		}
+
+		{
+			Vector<Vector<uint8_t>> vpv;
+			vpv.push_back(pv);
+			default_rd_textures[DEFAULT_RD_TEXTURE_VRS] = RD::get_singleton()->texture_create(tformat, RD::TextureView(), vpv);
+		}
+	}
+
 	{
 		Vector<String> sdf_modes;
 		sdf_modes.push_back("\n#define MODE_LOAD\n");
@@ -2751,3 +2772,31 @@ void TextureStorage::render_target_set_backbuffer_uniform_set(RID p_render_targe
 	ERR_FAIL_COND(!rt);
 	rt->backbuffer_uniform_set = p_uniform_set;
 }
+
+void TextureStorage::render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_COND(!rt);
+
+	rt->vrs_mode = p_mode;
+}
+
+void TextureStorage::render_target_set_vrs_texture(RID p_render_target, RID p_texture) {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_COND(!rt);
+
+	rt->vrs_texture = p_texture;
+}
+
+RS::ViewportVRSMode TextureStorage::render_target_get_vrs_mode(RID p_render_target) const {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_COND_V(!rt, RS::VIEWPORT_VRS_DISABLED);
+
+	return rt->vrs_mode;
+}
+
+RID TextureStorage::render_target_get_vrs_texture(RID p_render_target) const {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_COND_V(!rt, RID());
+
+	return rt->vrs_texture;
+}

+ 11 - 0
servers/rendering/renderer_rd/storage_rd/texture_storage.h

@@ -52,6 +52,7 @@ enum DefaultRDTexture {
 	DEFAULT_RD_TEXTURE_3D_BLACK,
 	DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE,
 	DEFAULT_RD_TEXTURE_2D_UINT,
+	DEFAULT_RD_TEXTURE_VRS,
 	DEFAULT_RD_TEXTURE_MAX
 };
 
@@ -229,6 +230,10 @@ struct RenderTarget {
 	RS::ViewportSDFScale sdf_scale = RS::VIEWPORT_SDF_SCALE_50_PERCENT;
 	Size2i process_size;
 
+	// VRS
+	RS::ViewportVRSMode vrs_mode = RS::VIEWPORT_VRS_DISABLED;
+	RID vrs_texture;
+
 	//texture generated for this owner (nor RD).
 	RID texture;
 	bool was_used;
@@ -549,6 +554,12 @@ public:
 	virtual void render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) override;
 	bool render_target_is_sdf_enabled(RID p_render_target) const;
 
+	virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override;
+	virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override;
+
+	RS::ViewportVRSMode render_target_get_vrs_mode(RID p_render_target) const;
+	RID render_target_get_vrs_texture(RID p_render_target) const;
+
 	Size2 render_target_get_size(RID p_render_target);
 	RID render_target_get_rd_framebuffer(RID p_render_target);
 	RID render_target_get_rd_texture(RID p_render_target);

+ 16 - 0
servers/rendering/renderer_viewport.cpp

@@ -1207,6 +1207,22 @@ RID RendererViewport::viewport_find_from_screen_attachment(DisplayServer::Window
 	return RID();
 }
 
+void RendererViewport::viewport_set_vrs_mode(RID p_viewport, RS::ViewportVRSMode p_mode) {
+	Viewport *viewport = viewport_owner.get_or_null(p_viewport);
+	ERR_FAIL_COND(!viewport);
+
+	RSG::texture_storage->render_target_set_vrs_mode(viewport->render_target, p_mode);
+	_configure_3d_render_buffers(viewport);
+}
+
+void RendererViewport::viewport_set_vrs_texture(RID p_viewport, RID p_texture) {
+	Viewport *viewport = viewport_owner.get_or_null(p_viewport);
+	ERR_FAIL_COND(!viewport);
+
+	RSG::texture_storage->render_target_set_vrs_texture(viewport->render_target, p_texture);
+	_configure_3d_render_buffers(viewport);
+}
+
 bool RendererViewport::free(RID p_rid) {
 	if (viewport_owner.owns(p_rid)) {
 		Viewport *viewport = viewport_owner.get_or_null(p_rid);

+ 3 - 0
servers/rendering/renderer_viewport.h

@@ -284,6 +284,9 @@ public:
 
 	virtual RID viewport_find_from_screen_attachment(DisplayServer::WindowID p_id = DisplayServer::MAIN_WINDOW_ID) const;
 
+	void viewport_set_vrs_mode(RID p_viewport, RS::ViewportVRSMode p_mode);
+	void viewport_set_vrs_texture(RID p_viewport, RID p_texture);
+
 	void handle_timestamp(String p_timestamp, uint64_t p_cpu_time, uint64_t p_gpu_time);
 
 	void set_default_clear_color(const Color &p_color);

+ 3 - 2
servers/rendering/rendering_device.cpp

@@ -64,12 +64,12 @@ Vector<uint8_t> RenderingDevice::shader_compile_spirv_from_source(ShaderStage p_
 
 	ERR_FAIL_COND_V(!compile_to_spirv_function, Vector<uint8_t>());
 
-	return compile_to_spirv_function(p_stage, p_source_code, p_language, r_error, &device_capabilities);
+	return compile_to_spirv_function(p_stage, p_source_code, p_language, r_error, this);
 }
 
 String RenderingDevice::shader_get_spirv_cache_key() const {
 	if (get_spirv_cache_key_function) {
-		return get_spirv_cache_key_function(&device_capabilities);
+		return get_spirv_cache_key_function(this);
 	}
 	return String();
 }
@@ -279,6 +279,7 @@ static Vector<RenderingDevice::PipelineSpecializationConstant> _get_spec_constan
 	}
 	return ret;
 }
+
 RID RenderingDevice::_render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const Ref<RDPipelineRasterizationState> &p_rasterization_state, const Ref<RDPipelineMultisampleState> &p_multisample_state, const Ref<RDPipelineDepthStencilState> &p_depth_stencil_state, const Ref<RDPipelineColorBlendState> &p_blend_state, int p_dynamic_state_flags, uint32_t p_for_render_pass, const TypedArray<RDPipelineSpecializationConstant> &p_specialization_constants) {
 	PipelineRasterizationState rasterization_state;
 	if (p_rasterization_state.is_valid()) {

+ 16 - 12
servers/rendering/rendering_device.h

@@ -123,19 +123,10 @@ public:
 		DeviceFamily device_family = DEVICE_UNKNOWN;
 		uint32_t version_major = 1.0;
 		uint32_t version_minor = 0.0;
-
-		// subgroup capabilities
-		uint32_t subgroup_size = 0;
-		uint32_t subgroup_in_shaders = 0; // Set flags using SHADER_STAGE_VERTEX_BIT, SHADER_STAGE_FRAGMENT_BIT, etc.
-		uint32_t subgroup_operations = 0; // Set flags, using SubgroupOperations
-
-		// features
-		bool supports_multiview = false; // If true this device supports multiview options
-		bool supports_fsr_half_float = false; // If true this device supports FSR scaling 3D in half float mode, otherwise use the fallback mode
 	};
 
-	typedef String (*ShaderSPIRVGetCacheKeyFunction)(const Capabilities *p_capabilities);
-	typedef Vector<uint8_t> (*ShaderCompileToSPIRVFunction)(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language, String *r_error, const Capabilities *p_capabilities);
+	typedef String (*ShaderSPIRVGetCacheKeyFunction)(const RenderingDevice *p_render_device);
+	typedef Vector<uint8_t> (*ShaderCompileToSPIRVFunction)(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language, String *r_error, const RenderingDevice *p_render_device);
 	typedef Vector<uint8_t> (*ShaderCacheFunction)(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language);
 
 private:
@@ -444,6 +435,7 @@ public:
 		TEXTURE_USAGE_CAN_COPY_FROM_BIT = (1 << 7),
 		TEXTURE_USAGE_CAN_COPY_TO_BIT = (1 << 8),
 		TEXTURE_USAGE_INPUT_ATTACHMENT_BIT = (1 << 9),
+		TEXTURE_USAGE_VRS_ATTACHMENT_BIT = (1 << 10),
 	};
 
 	enum TextureSwizzle {
@@ -552,6 +544,7 @@ public:
 		Vector<int32_t> resolve_attachments;
 		Vector<int32_t> preserve_attachments;
 		int32_t depth_attachment = ATTACHMENT_UNUSED;
+		int32_t vrs_attachment = ATTACHMENT_UNUSED; // density map for VRS, only used if supported
 	};
 
 	virtual FramebufferFormatID framebuffer_format_create_multipass(const Vector<AttachmentFormat> &p_attachments, Vector<FramebufferPass> &p_passes, uint32_t p_view_count = 1) = 0;
@@ -675,6 +668,13 @@ public:
 
 	const Capabilities *get_device_capabilities() const { return &device_capabilities; };
 
+	enum Features {
+		SUPPORTS_MULTIVIEW,
+		SUPPORTS_FSR_HALF_FLOAT,
+		SUPPORTS_ATTACHMENT_VRS,
+	};
+	virtual bool has_feature(const Features p_feature) const = 0;
+
 	virtual Vector<uint8_t> shader_compile_spirv_from_source(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language = SHADER_LANGUAGE_GLSL, String *r_error = nullptr, bool p_allow_cache = true);
 	virtual String shader_get_spirv_cache_key() const;
 
@@ -1221,9 +1221,12 @@ public:
 		LIMIT_MAX_COMPUTE_WORKGROUP_SIZE_X,
 		LIMIT_MAX_COMPUTE_WORKGROUP_SIZE_Y,
 		LIMIT_MAX_COMPUTE_WORKGROUP_SIZE_Z,
+		LIMIT_SUBGROUP_SIZE,
+		LIMIT_SUBGROUP_IN_SHADERS, // Set flags using SHADER_STAGE_VERTEX_BIT, SHADER_STAGE_FRAGMENT_BIT, etc.
+		LIMIT_SUBGROUP_OPERATIONS,
 	};
 
-	virtual uint64_t limit_get(Limit p_limit) = 0;
+	virtual uint64_t limit_get(Limit p_limit) const = 0;
 
 	//methods below not exposed, used by RenderingDeviceRD
 	virtual void prepare_screen_for_drawing() = 0;
@@ -1324,6 +1327,7 @@ VARIANT_ENUM_CAST(RenderingDevice::InitialAction)
 VARIANT_ENUM_CAST(RenderingDevice::FinalAction)
 VARIANT_ENUM_CAST(RenderingDevice::Limit)
 VARIANT_ENUM_CAST(RenderingDevice::MemoryType)
+VARIANT_ENUM_CAST(RenderingDevice::Features)
 
 typedef RenderingDevice RD;
 

+ 3 - 0
servers/rendering/rendering_server_default.h

@@ -637,6 +637,9 @@ public:
 
 	FUNC2(call_set_vsync_mode, DisplayServer::VSyncMode, DisplayServer::WindowID)
 
+	FUNC2(viewport_set_vrs_mode, RID, ViewportVRSMode)
+	FUNC2(viewport_set_vrs_texture, RID, RID)
+
 	/* ENVIRONMENT API */
 
 #undef server_name

+ 3 - 0
servers/rendering/storage/texture_storage.h

@@ -143,6 +143,9 @@ public:
 	virtual void render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) = 0;
 	virtual Rect2i render_target_get_sdf_rect(RID p_render_target) const = 0;
 	virtual void render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) = 0;
+
+	virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) = 0;
+	virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) = 0;
 };
 
 #endif // !TEXTURE_STORAGE_H

+ 8 - 0
servers/rendering_server.cpp

@@ -2225,6 +2225,9 @@ void RenderingServer::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("viewport_get_measured_render_time_gpu", "viewport"), &RenderingServer::viewport_get_measured_render_time_gpu);
 
+	ClassDB::bind_method(D_METHOD("viewport_set_vrs_mode", "viewport", "mode"), &RenderingServer::viewport_set_vrs_mode);
+	ClassDB::bind_method(D_METHOD("viewport_set_vrs_texture", "viewport", "texture"), &RenderingServer::viewport_set_vrs_texture);
+
 	BIND_ENUM_CONSTANT(VIEWPORT_SCALING_3D_MODE_BILINEAR);
 	BIND_ENUM_CONSTANT(VIEWPORT_SCALING_3D_MODE_FSR);
 	BIND_ENUM_CONSTANT(VIEWPORT_SCALING_3D_MODE_MAX);
@@ -2300,6 +2303,11 @@ void RenderingServer::_bind_methods() {
 	BIND_ENUM_CONSTANT(VIEWPORT_DEBUG_DRAW_OCCLUDERS);
 	BIND_ENUM_CONSTANT(VIEWPORT_DEBUG_DRAW_MOTION_VECTORS);
 
+	BIND_ENUM_CONSTANT(VIEWPORT_VRS_DISABLED);
+	BIND_ENUM_CONSTANT(VIEWPORT_VRS_TEXTURE);
+	BIND_ENUM_CONSTANT(VIEWPORT_VRS_XR);
+	BIND_ENUM_CONSTANT(VIEWPORT_VRS_MAX);
+
 	/* SKY API */
 
 	ClassDB::bind_method(D_METHOD("sky_create"), &RenderingServer::sky_create);

+ 11 - 0
servers/rendering_server.h

@@ -946,6 +946,16 @@ public:
 
 	virtual RID viewport_find_from_screen_attachment(DisplayServer::WindowID p_id = DisplayServer::MAIN_WINDOW_ID) const = 0;
 
+	enum ViewportVRSMode {
+		VIEWPORT_VRS_DISABLED,
+		VIEWPORT_VRS_TEXTURE,
+		VIEWPORT_VRS_XR,
+		VIEWPORT_VRS_MAX,
+	};
+
+	virtual void viewport_set_vrs_mode(RID p_viewport, ViewportVRSMode p_mode) = 0;
+	virtual void viewport_set_vrs_texture(RID p_viewport, RID p_texture) = 0;
+
 	/* SKY API */
 
 	enum SkyMode {
@@ -1609,6 +1619,7 @@ VARIANT_ENUM_CAST(RenderingServer::ViewportDebugDraw);
 VARIANT_ENUM_CAST(RenderingServer::ViewportOcclusionCullingBuildQuality);
 VARIANT_ENUM_CAST(RenderingServer::ViewportSDFOversize);
 VARIANT_ENUM_CAST(RenderingServer::ViewportSDFScale);
+VARIANT_ENUM_CAST(RenderingServer::ViewportVRSMode);
 VARIANT_ENUM_CAST(RenderingServer::SkyMode);
 VARIANT_ENUM_CAST(RenderingServer::EnvironmentBG);
 VARIANT_ENUM_CAST(RenderingServer::EnvironmentAmbientSource);

+ 86 - 2
servers/xr/xr_interface.cpp

@@ -29,7 +29,7 @@
 /*************************************************************************/
 
 #include "xr_interface.h"
-// #include "servers/rendering/renderer_compositor.h"
+#include "servers/rendering/renderer_compositor.h"
 
 void XRInterface::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("play_area_changed", PropertyInfo(Variant::INT, "mode")));
@@ -114,7 +114,12 @@ void XRInterface::set_primary(bool p_primary) {
 
 XRInterface::XRInterface() {}
 
-XRInterface::~XRInterface() {}
+XRInterface::~XRInterface() {
+	if (vrs.vrs_texture.is_valid()) {
+		RS::get_singleton()->free(vrs.vrs_texture);
+		vrs.vrs_texture = RID();
+	}
+}
 
 // query if this interface supports this play area mode
 bool XRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) {
@@ -151,6 +156,85 @@ int XRInterface::get_camera_feed_id() {
 	return 0;
 }
 
+RID XRInterface::get_vrs_texture() {
+	// Default logic will return a standard VRS image based on our target size and default projections.
+	// Note that this only gets called if VRS is supported on the hardware.
+
+	Size2 texel_size = Size2(16.0, 16.0); // For now we assume we always use 16x16 texels, seems to be the standard.
+	int view_count = get_view_count();
+	Size2 target_size = get_render_target_size();
+	real_t aspect = target_size.x / target_size.y; // is this y/x ?
+	Size2 vrs_size = Size2(round(0.5 + target_size.x / texel_size.x), round(0.5 + target_size.y / texel_size.y));
+	real_t radius = vrs_size.length() * 0.5;
+	Size2 vrs_sizei = vrs_size;
+
+	if (vrs.size != vrs_sizei) {
+		const uint8_t densities[] = {
+			0, // 1x1
+			1, // 1x2
+			// 4, // 2x1
+			5, // 2x2
+			6, // 2x4
+			// 9, // 4x2
+			10, // 4x4
+		};
+
+		// out with the old
+		if (vrs.vrs_texture.is_valid()) {
+			RS::get_singleton()->free(vrs.vrs_texture);
+			vrs.vrs_texture = RID();
+		}
+
+		// in with the new
+		Vector<Ref<Image>> images;
+		vrs.size = vrs_sizei;
+
+		for (int i = 0; i < view_count && i < 2; i++) {
+			PackedByteArray data;
+			data.resize(vrs_sizei.x * vrs_sizei.y);
+			uint8_t *data_ptr = data.ptrw();
+
+			// Our near and far don't matter much for what we're doing here, but there are some interfaces that will remember this as the near and far and may fail as a result...
+			CameraMatrix cm = get_projection_for_view(i, aspect, 0.1, 1000.0);
+			Vector3 center = cm.xform(Vector3(0.0, 0.0, 999.0));
+
+			Vector2i view_center;
+			view_center.x = int(vrs_size.x * (center.x + 1.0) * 0.5);
+			view_center.y = int(vrs_size.y * (center.y + 1.0) * 0.5);
+
+			int d = 0;
+			for (int y = 0; y < vrs_sizei.y; y++) {
+				for (int x = 0; x < vrs_sizei.x; x++) {
+					Vector2 offset = Vector2(x - view_center.x, y - view_center.y);
+					offset.y *= aspect;
+					real_t distance = offset.length();
+					int idx = round(5.0 * distance / radius);
+					if (idx > 4) {
+						idx = 4;
+					}
+					uint8_t density = densities[idx];
+
+					data_ptr[d++] = density;
+				}
+			}
+
+			Ref<Image> image;
+			image.instantiate();
+			image->create_from_data(vrs_sizei.x, vrs_sizei.y, false, Image::FORMAT_R8, data);
+
+			images.push_back(image);
+		}
+
+		if (images.size() == 1) {
+			vrs.vrs_texture = RS::get_singleton()->texture_2d_create(images[0]);
+		} else {
+			vrs.vrs_texture = RS::get_singleton()->texture_2d_layered_create(images, RS::TEXTURE_LAYERED_2D_ARRAY);
+		}
+	}
+
+	return vrs.vrs_texture;
+}
+
 /** these are optional, so we want dummies **/
 PackedStringArray XRInterface::get_suggested_tracker_names() const {
 	PackedStringArray arr;

+ 7 - 0
servers/xr/xr_interface.h

@@ -120,6 +120,7 @@ public:
 	virtual Transform3D get_camera_transform() = 0; /* returns the position of our camera for updating our camera node. For monoscopic this is equal to the views transform, for stereoscopic this should be an average */
 	virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) = 0; /* get each views transform */
 	virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) = 0; /* get each view projection matrix */
+	virtual RID get_vrs_texture(); /* obtain VRS texture */
 
 	// note, external color/depth/vrs texture support will be added here soon.
 
@@ -133,6 +134,12 @@ public:
 
 	XRInterface();
 	~XRInterface();
+
+private:
+	struct VRSData {
+		RID vrs_texture;
+		Size2i size;
+	} vrs;
 };
 
 VARIANT_ENUM_CAST(XRInterface::Capabilities);

+ 10 - 0
servers/xr/xr_interface_extension.cpp

@@ -50,6 +50,7 @@ void XRInterfaceExtension::_bind_methods() {
 	GDVIRTUAL_BIND(_get_camera_transform);
 	GDVIRTUAL_BIND(_get_transform_for_view, "view", "cam_transform");
 	GDVIRTUAL_BIND(_get_projection_for_view, "view", "aspect", "z_near", "z_far");
+	GDVIRTUAL_BIND(_get_vrs_texture);
 
 	GDVIRTUAL_BIND(_process);
 	GDVIRTUAL_BIND(_pre_render);
@@ -273,6 +274,15 @@ CameraMatrix XRInterfaceExtension::get_projection_for_view(uint32_t p_view, doub
 	return CameraMatrix();
 }
 
+RID XRInterfaceExtension::get_vrs_texture() {
+	RID vrs_texture;
+	if (GDVIRTUAL_CALL(_get_vrs_texture, vrs_texture)) {
+		return vrs_texture;
+	} else {
+		return XRInterface::get_vrs_texture();
+	}
+}
+
 void XRInterfaceExtension::add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer, uint32_t p_layer, bool p_apply_lens_distortion, Vector2 p_eye_center, double p_k1, double p_k2, double p_upscale, double p_aspect_ratio) {
 	BlitToScreen blit;
 

+ 2 - 0
servers/xr/xr_interface_extension.h

@@ -101,12 +101,14 @@ public:
 	virtual Transform3D get_camera_transform() override;
 	virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override;
 	virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override;
+	virtual RID get_vrs_texture() override;
 
 	GDVIRTUAL0R(Size2, _get_render_target_size);
 	GDVIRTUAL0R(uint32_t, _get_view_count);
 	GDVIRTUAL0R(Transform3D, _get_camera_transform);
 	GDVIRTUAL2R(Transform3D, _get_transform_for_view, uint32_t, const Transform3D &);
 	GDVIRTUAL4R(PackedFloat64Array, _get_projection_for_view, uint32_t, double, double, double);
+	GDVIRTUAL0R(RID, _get_vrs_texture);
 
 	void add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer = false, uint32_t p_layer = 0, bool p_apply_lens_distortion = false, Vector2 p_eye_center = Vector2(), double p_k1 = 0.0, double p_k2 = 0.0, double p_upscale = 1.0, double p_aspect_ratio = 1.0);