Browse Source

OpenXR: Use the `XR_FB_foveation_vulkan` extension to get the density map for VRS

David Snopek 9 months ago
parent
commit
79f5a4d9fe

+ 1 - 2
doc/classes/ProjectSettings.xml

@@ -3257,11 +3257,10 @@
 		</member>
 		<member name="xr/openxr/foveation_dynamic" type="bool" setter="" getter="" default="false">
 			If [code]true[/code] and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [member xr/openxr/foveation_level].
-			[b]Note:[/b] Only works on the Compatibility rendering method.
 		</member>
 		<member name="xr/openxr/foveation_level" type="int" setter="" getter="" default="&quot;0&quot;">
 			Applied foveation level if supported: 0 = off, 1 = low, 2 = medium, 3 = high.
-			[b]Note:[/b] Only works on the Compatibility rendering method. On platforms other than Android, if [member rendering/anti_aliasing/quality/msaa_3d] is enabled, this feature will be disabled.
+			[b]Note:[/b] On platforms other than Android, if [member rendering/anti_aliasing/quality/msaa_3d] is enabled, this feature will be disabled.
 		</member>
 		<member name="xr/openxr/reference_space" type="int" setter="" getter="" default="&quot;1&quot;">
 			Specify the default reference space.

+ 9 - 0
doc/classes/XRInterface.xml

@@ -275,5 +275,14 @@
 		<constant name="XR_ENV_BLEND_MODE_ALPHA_BLEND" value="2" enum="EnvironmentBlendMode">
 			Alpha blend mode. This is typically used for AR or VR devices with passthrough capabilities. The alpha channel controls how much of the passthrough is visible. Alpha of 0.0 means the passthrough is visible and this pixel works in ADDITIVE mode. Alpha of 1.0 means that the passthrough is not visible and this pixel works in OPAQUE mode.
 		</constant>
+		<constant name="XR_VRS_TEXTURE_FORMAT_UNIFIED" value="0" enum="VRSTextureFormat">
+			The texture format is the same as returned by [method XRVRS.make_vrs_texture].
+		</constant>
+		<constant name="XR_VRS_TEXTURE_FORMAT_FRAGMENT_SHADING_RATE" value="1" enum="VRSTextureFormat">
+			The texture format is the same as expected by the Vulkan [code]VK_KHR_fragment_shading_rate[/code] extension.
+		</constant>
+		<constant name="XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP" value="2" enum="VRSTextureFormat">
+			The texture format is the same as expected by the Vulkan [code]VK_EXT_fragment_density_map[/code] extension.
+		</constant>
 	</constants>
 </class>

+ 6 - 0
doc/classes/XRInterfaceExtension.xml

@@ -136,6 +136,12 @@
 			<description>
 			</description>
 		</method>
+		<method name="_get_vrs_texture_format" qualifiers="virtual">
+			<return type="int" enum="XRInterface.VRSTextureFormat" />
+			<description>
+				Returns the format of the texture returned by [method _get_vrs_texture].
+			</description>
+		</method>
 		<method name="_initialize" qualifiers="virtual">
 			<return type="bool" />
 			<description>

+ 1 - 0
modules/openxr/extensions/openxr_extension_wrapper.h

@@ -192,4 +192,5 @@ public:
 	virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) = 0; // `cleanup_swapchain_graphics_data` cleans up the data held in our implementation dependent data structure and should free up its memory.
 	virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) = 0; // `create_projection_fov` creates a proper projection matrix based on asymmetric FOV data provided by OpenXR.
 	virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) = 0; // `get_texture` returns a Godot texture RID for the current active texture in our swapchain.
+	virtual RID get_density_map(void *p_swapchain_graphics_data, int p_image_index) = 0; // `get_density_map` returns a Godot texture RID for the current active density map in our swapchain (if any).
 };

+ 17 - 6
modules/openxr/extensions/openxr_fb_foveation_extension.cpp

@@ -31,6 +31,8 @@
 #include "openxr_fb_foveation_extension.h"
 #include "core/config/project_settings.h"
 
+#include "../openxr_platform_inc.h"
+
 OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::singleton = nullptr;
 
 OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::get_singleton() {
@@ -51,6 +53,12 @@ OpenXRFBFoveationExtension::OpenXRFBFoveationExtension(const String &p_rendering
 	swapchain_create_info_foveation_fb.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB;
 	swapchain_create_info_foveation_fb.next = nullptr;
 	swapchain_create_info_foveation_fb.flags = 0;
+
+	if (rendering_driver == "opengl3") {
+		swapchain_create_info_foveation_fb.flags = XR_SWAPCHAIN_CREATE_FOVEATION_SCALED_BIN_BIT_FB;
+	} else if (rendering_driver == "vulkan") {
+		swapchain_create_info_foveation_fb.flags = XR_SWAPCHAIN_CREATE_FOVEATION_FRAGMENT_DENSITY_MAP_BIT_FB;
+	}
 }
 
 OpenXRFBFoveationExtension::~OpenXRFBFoveationExtension() {
@@ -61,12 +69,11 @@ OpenXRFBFoveationExtension::~OpenXRFBFoveationExtension() {
 HashMap<String, bool *> OpenXRFBFoveationExtension::get_requested_extensions() {
 	HashMap<String, bool *> request_extensions;
 
-	if (rendering_driver == "vulkan") {
-		// This is currently only supported on OpenGL, but we may add Vulkan support in the future...
+	request_extensions[XR_FB_FOVEATION_EXTENSION_NAME] = &fb_foveation_ext;
+	request_extensions[XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME] = &fb_foveation_configuration_ext;
 
-	} else if (rendering_driver == "opengl3") {
-		request_extensions[XR_FB_FOVEATION_EXTENSION_NAME] = &fb_foveation_ext;
-		request_extensions[XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME] = &fb_foveation_configuration_ext;
+	if (rendering_driver == "vulkan") {
+		request_extensions[XR_FB_FOVEATION_VULKAN_EXTENSION_NAME] = &fb_foveation_vulkan_ext;
 	}
 
 	return request_extensions;
@@ -89,7 +96,11 @@ void OpenXRFBFoveationExtension::on_instance_destroyed() {
 }
 
 bool OpenXRFBFoveationExtension::is_enabled() const {
-	return swapchain_update_state_ext != nullptr && swapchain_update_state_ext->is_enabled() && fb_foveation_ext && fb_foveation_configuration_ext;
+	bool enabled = swapchain_update_state_ext != nullptr && swapchain_update_state_ext->is_enabled() && fb_foveation_ext && fb_foveation_configuration_ext;
+	if (rendering_driver == "vulkan") {
+		enabled = enabled && fb_foveation_vulkan_ext;
+	}
+	return enabled;
 }
 
 void *OpenXRFBFoveationExtension::set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) {

+ 1 - 5
modules/openxr/extensions/openxr_fb_foveation_extension.h

@@ -35,11 +35,6 @@
 // Other Android based devices are implementing this as well, see:
 // https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_FB_foveation
 
-// Note: Currently we only support this for OpenGL.
-// This extension works on enabling foveated rendering on the swapchain.
-// Vulkan does not render 3D content directly to the swapchain image
-// hence this extension can't be used.
-
 #include "../openxr_api.h"
 #include "../util.h"
 #include "openxr_extension_wrapper.h"
@@ -81,6 +76,7 @@ private:
 	String rendering_driver;
 	bool fb_foveation_ext = false;
 	bool fb_foveation_configuration_ext = false;
+	bool fb_foveation_vulkan_ext = false;
 
 	// Configuration
 	XrFoveationLevelFB foveation_level = XR_FOVEATION_LEVEL_NONE_FB;

+ 1 - 0
modules/openxr/extensions/platform/openxr_metal_extension.h

@@ -53,6 +53,7 @@ public:
 	virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override;
 	virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override;
 	virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) override;
+	virtual RID get_density_map(void *p_swapchain_graphics_data, int p_image_index) override { return RID(); }
 
 private:
 	static XrGraphicsBindingMetalKHR graphics_binding_metal;

+ 1 - 0
modules/openxr/extensions/platform/openxr_opengl_extension.h

@@ -55,6 +55,7 @@ public:
 	virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override;
 	virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override;
 	virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) override;
+	virtual RID get_density_map(void *p_swapchain_graphics_data, int p_image_index) override { return RID(); }
 
 private:
 	static OpenXROpenGLExtension *singleton;

+ 53 - 1
modules/openxr/extensions/platform/openxr_vulkan_extension.cpp

@@ -31,6 +31,7 @@
 #include "openxr_vulkan_extension.h"
 
 #include "../../openxr_util.h"
+#include "../openxr_fb_foveation_extension.h"
 
 #include "core/string/print_string.h"
 #include "servers/rendering/renderer_rd/effects/copy_effects.h"
@@ -240,6 +241,7 @@ void OpenXRVulkanExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_s
 
 bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) {
 	LocalVector<XrSwapchainImageVulkanKHR> images;
+	LocalVector<XrSwapchainImageFoveationVulkanFB> density_images;
 
 	RenderingServer *rendering_server = RenderingServer::get_singleton();
 	ERR_FAIL_NULL_V(rendering_server, false);
@@ -261,6 +263,20 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in
 		image.image = VK_NULL_HANDLE;
 	}
 
+	if (OpenXRFBFoveationExtension::get_singleton()->is_enabled()) {
+		density_images.resize(swapchain_length);
+
+		for (uint64_t i = 0; i < swapchain_length; i++) {
+			density_images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_FOVEATION_VULKAN_FB;
+			density_images[i].next = nullptr;
+			density_images[i].image = VK_NULL_HANDLE;
+			density_images[i].width = 0;
+			density_images[i].height = 0;
+
+			images[i].next = &density_images[i];
+		}
+	}
+
 	result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images.ptr());
 	if (XR_FAILED(result)) {
 		print_line("OpenXR: Failed to get swapchaim images [", OpenXRAPI::get_singleton()->get_error_string(result), "]");
@@ -351,9 +367,12 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in
 	}
 
 	Vector<RID> texture_rids;
+	Vector<RID> density_map_rids;
 
 	// create Godot texture objects for each entry in our swapchain
-	for (const XrSwapchainImageVulkanKHR &swapchain_image : images) {
+	for (uint32_t i = 0; i < swapchain_length; i++) {
+		const XrSwapchainImageVulkanKHR &swapchain_image = images[i];
+
 		RID image_rid = rendering_device->texture_create_from_extension(
 				p_array_size == 1 ? RenderingDevice::TEXTURE_TYPE_2D : RenderingDevice::TEXTURE_TYPE_2D_ARRAY,
 				format,
@@ -366,9 +385,27 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in
 				p_array_size);
 
 		texture_rids.push_back(image_rid);
+
+		if (OpenXRFBFoveationExtension::get_singleton()->is_enabled() && density_images[i].image != VK_NULL_HANDLE) {
+			RID density_map_rid = rendering_device->texture_create_from_extension(
+					p_array_size == 1 ? RenderingDevice::TEXTURE_TYPE_2D : RenderingDevice::TEXTURE_TYPE_2D_ARRAY,
+					RD::DATA_FORMAT_R8G8_UNORM,
+					RenderingDevice::TEXTURE_SAMPLES_1,
+					RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_VRS_ATTACHMENT_BIT,
+					(uint64_t)density_images[i].image,
+					density_images[i].width,
+					density_images[i].height,
+					1,
+					p_array_size);
+
+			density_map_rids.push_back(density_map_rid);
+		} else {
+			density_map_rids.push_back(RID());
+		}
 	}
 
 	data->texture_rids = texture_rids;
+	data->density_map_rids = density_map_rids;
 
 	return true;
 }
@@ -395,6 +432,14 @@ RID OpenXRVulkanExtension::get_texture(void *p_swapchain_graphics_data, int p_im
 	return data->texture_rids[p_image_index];
 }
 
+RID OpenXRVulkanExtension::get_density_map(void *p_swapchain_graphics_data, int p_image_index) {
+	SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data;
+	ERR_FAIL_NULL_V(data, RID());
+
+	ERR_FAIL_INDEX_V(p_image_index, data->density_map_rids.size(), RID());
+	return data->density_map_rids[p_image_index];
+}
+
 void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) {
 	if (*p_swapchain_graphics_data == nullptr) {
 		return;
@@ -413,6 +458,13 @@ void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_g
 	}
 	data->texture_rids.clear();
 
+	for (int i = 0; i < data->density_map_rids.size(); i++) {
+		if (data->density_map_rids[i].is_valid()) {
+			rendering_device->free(data->density_map_rids[i]);
+		}
+	}
+	data->density_map_rids.clear();
+
 	memdelete(data);
 	*p_swapchain_graphics_data = nullptr;
 }

+ 2 - 0
modules/openxr/extensions/platform/openxr_vulkan_extension.h

@@ -62,6 +62,7 @@ public:
 	virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override;
 	virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override;
 	virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) override;
+	virtual RID get_density_map(void *p_swapchain_graphics_data, int p_image_index) override;
 
 private:
 	static OpenXRVulkanExtension *singleton;
@@ -70,6 +71,7 @@ private:
 	struct SwapchainGraphicsData {
 		bool is_multiview;
 		Vector<RID> texture_rids;
+		Vector<RID> density_map_rids;
 	};
 
 	bool check_graphics_api_support(XrVersion p_desired_version);

+ 21 - 0
modules/openxr/openxr_api.cpp

@@ -267,6 +267,16 @@ RID OpenXRAPI::OpenXRSwapChainInfo::get_image() {
 	}
 }
 
+RID OpenXRAPI::OpenXRSwapChainInfo::get_density_map() {
+	OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+
+	if (image_acquired && openxr_api && openxr_api->get_graphics_extension()) {
+		return openxr_api->get_graphics_extension()->get_density_map(swapchain_graphics_data, image_index);
+	} else {
+		return RID();
+	}
+}
+
 ////////////////////////////////////
 // OpenXRAPI
 
@@ -2370,6 +2380,17 @@ RID OpenXRAPI::get_depth_texture() {
 	}
 }
 
+RID OpenXRAPI::get_density_map_texture() {
+	ERR_NOT_ON_RENDER_THREAD_V(RID());
+
+	OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton();
+	if (fov_ext && fov_ext->is_enabled()) {
+		return render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_density_map();
+	}
+
+	return RID();
+}
+
 void OpenXRAPI::set_velocity_texture(RID p_render_target) {
 	velocity_texture = p_render_target;
 }

+ 2 - 0
modules/openxr/openxr_api.h

@@ -73,6 +73,7 @@ public:
 		bool acquire(bool &p_should_render);
 		bool release();
 		RID get_image();
+		RID get_density_map();
 	};
 
 private:
@@ -501,6 +502,7 @@ public:
 	XrSwapchain get_color_swapchain();
 	RID get_color_texture();
 	RID get_depth_texture();
+	RID get_density_map_texture();
 	void set_velocity_texture(RID p_render_target);
 	RID get_velocity_texture();
 	void set_velocity_depth_texture(RID p_render_target);

+ 18 - 0
modules/openxr/openxr_interface.cpp

@@ -1546,6 +1546,11 @@ RID OpenXRInterface::get_vrs_texture() {
 		return RID();
 	}
 
+	RID density_map = openxr_api->get_density_map_texture();
+	if (density_map.is_valid()) {
+		return density_map;
+	}
+
 	PackedVector2Array eye_foci;
 
 	Size2 target_size = get_render_target_size();
@@ -1561,6 +1566,19 @@ RID OpenXRInterface::get_vrs_texture() {
 	return xr_vrs.make_vrs_texture(target_size, eye_foci);
 }
 
+XRInterface::VRSTextureFormat OpenXRInterface::get_vrs_texture_format() {
+	if (!openxr_api) {
+		return XR_VRS_TEXTURE_FORMAT_UNIFIED;
+	}
+
+	RID density_map = openxr_api->get_density_map_texture();
+	if (density_map.is_valid()) {
+		return XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP;
+	}
+
+	return XR_VRS_TEXTURE_FORMAT_UNIFIED;
+}
+
 void OpenXRInterface::set_cpu_level(PerfSettingsLevel p_level) {
 	OpenXRPerformanceSettingsExtension *performance_settings_ext = OpenXRPerformanceSettingsExtension::get_singleton();
 	if (performance_settings_ext && performance_settings_ext->is_available()) {

+ 1 - 0
modules/openxr/openxr_interface.h

@@ -290,6 +290,7 @@ public:
 	Vector3 get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const;
 
 	virtual RID get_vrs_texture() override;
+	virtual VRSTextureFormat get_vrs_texture_format() override;
 
 	// Performance settings.
 	enum PerfSettingsLevel {

+ 1 - 1
servers/rendering/renderer_rd/effects/vrs.cpp

@@ -132,7 +132,7 @@ void VRS::update_vrs_texture(RID p_vrs_fb, RID p_render_target) {
 #ifndef XR_DISABLED
 		} else if (vrs_mode == RS::VIEWPORT_VRS_XR) {
 			Ref<XRInterface> interface = XRServer::get_singleton()->get_primary_interface();
-			if (interface.is_valid()) {
+			if (interface.is_valid() && interface->get_vrs_texture_format() == XRInterface::XR_VRS_TEXTURE_FORMAT_UNIFIED) {
 				RID vrs_texture = interface->get_vrs_texture();
 				if (vrs_texture.is_valid()) {
 					RID rd_texture = texture_storage->texture_get_rd_texture(vrs_texture);

+ 9 - 1
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp

@@ -183,7 +183,15 @@ RID RenderForwardMobile::RenderBufferDataForwardMobile::get_color_fbs(Framebuffe
 	uint32_t view_count = render_buffers->get_view_count();
 
 	RID vrs_texture;
-	if (render_buffers->has_texture(RB_SCOPE_VRS, RB_TEXTURE)) {
+#ifndef XR_DISABLED
+	if (render_buffers->get_vrs_mode() == RS::VIEWPORT_VRS_XR) {
+		Ref<XRInterface> interface = XRServer::get_singleton()->get_primary_interface();
+		if (interface.is_valid() && RD::get_singleton()->vrs_get_method() == RD::VRS_METHOD_FRAGMENT_DENSITY_MAP && interface->get_vrs_texture_format() == XRInterface::XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP) {
+			vrs_texture = interface->get_vrs_texture();
+		}
+	}
+#endif // XR_DISABLED
+	if (vrs_texture.is_null() && render_buffers->has_texture(RB_SCOPE_VRS, RB_TEXTURE)) {
 		vrs_texture = render_buffers->get_texture(RB_SCOPE_VRS, RB_TEXTURE);
 	}
 

+ 2 - 1
servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp

@@ -168,6 +168,8 @@ void RenderSceneBuffersRD::configure(const RenderSceneBuffersConfiguration *p_co
 
 	ERR_FAIL_COND_MSG(view_count == 0, "Must have at least 1 view");
 
+	vrs_mode = texture_storage->render_target_get_vrs_mode(render_target);
+
 	update_samplers();
 
 	// cleanout any old buffers we had.
@@ -194,7 +196,6 @@ void RenderSceneBuffersRD::configure(const RenderSceneBuffersConfiguration *p_co
 
 	// VRS (note, our vrs object will only be set if VRS is supported)
 	RID vrs_texture;
-	RS::ViewportVRSMode vrs_mode = texture_storage->render_target_get_vrs_mode(render_target);
 	if (vrs && vrs_mode != RS::VIEWPORT_VRS_DISABLED) {
 		vrs_texture = create_texture(RB_SCOPE_VRS, RB_TEXTURE, get_vrs_format(), get_vrs_usage_bits(), RD::TEXTURE_SAMPLES_1, vrs->get_vrs_texture_size(internal_size));
 	}

+ 2 - 0
servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h

@@ -69,6 +69,7 @@ private:
 	RD::DataFormat base_data_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
 	RendererRD::VRS *vrs = nullptr;
 	uint64_t auto_exposure_version = 1;
+	RS::ViewportVRSMode vrs_mode = RS::VIEWPORT_VRS_DISABLED;
 
 	// Our render target represents our final destination that we display on screen.
 	RID render_target;
@@ -188,6 +189,7 @@ public:
 	void set_base_data_format(const RD::DataFormat p_base_data_format) { base_data_format = p_base_data_format; }
 	RD::DataFormat get_base_data_format() const { return base_data_format; }
 	void set_vrs(RendererRD::VRS *p_vrs) { vrs = p_vrs; }
+	RS::ViewportVRSMode get_vrs_mode() { return vrs_mode; }
 
 	void cleanup();
 	virtual void configure(const RenderSceneBuffersConfiguration *p_config) override;

+ 4 - 0
servers/rendering/rendering_device.cpp

@@ -2761,6 +2761,10 @@ void RenderingDevice::_vrs_detect_method() {
 	}
 }
 
+RD::VRSMethod RenderingDevice::vrs_get_method() const {
+	return vrs_method;
+}
+
 RD::DataFormat RenderingDevice::vrs_get_format() const {
 	return vrs_format;
 }

+ 3 - 1
servers/rendering/rendering_device.h

@@ -429,7 +429,7 @@ public:
 	void texture_set_discardable(RID p_texture, bool p_discardable);
 	bool texture_is_discardable(RID p_texture);
 
-private:
+public:
 	/*************/
 	/**** VRS ****/
 	/*************/
@@ -440,6 +440,7 @@ private:
 		VRS_METHOD_FRAGMENT_DENSITY_MAP,
 	};
 
+private:
 	VRSMethod vrs_method = VRS_METHOD_NONE;
 	DataFormat vrs_format = DATA_FORMAT_MAX;
 	Size2i vrs_texel_size;
@@ -450,6 +451,7 @@ private:
 	void _vrs_detect_method();
 
 public:
+	VRSMethod vrs_get_method() const;
 	DataFormat vrs_get_format() const;
 	Size2i vrs_get_texel_size() const;
 

+ 4 - 0
servers/xr/xr_interface.cpp

@@ -108,6 +108,10 @@ void XRInterface::_bind_methods() {
 	BIND_ENUM_CONSTANT(XR_ENV_BLEND_MODE_OPAQUE);
 	BIND_ENUM_CONSTANT(XR_ENV_BLEND_MODE_ADDITIVE);
 	BIND_ENUM_CONSTANT(XR_ENV_BLEND_MODE_ALPHA_BLEND);
+
+	BIND_ENUM_CONSTANT(XR_VRS_TEXTURE_FORMAT_UNIFIED);
+	BIND_ENUM_CONSTANT(XR_VRS_TEXTURE_FORMAT_FRAGMENT_SHADING_RATE);
+	BIND_ENUM_CONSTANT(XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP);
 }
 
 bool XRInterface::is_primary() {

+ 8 - 0
servers/xr/xr_interface.h

@@ -85,6 +85,12 @@ public:
 		XR_ENV_BLEND_MODE_ALPHA_BLEND, /* Real world is passed through where alpha channel is 0.0 and gradually blends to opaque for value 1.0. */
 	};
 
+	enum VRSTextureFormat {
+		XR_VRS_TEXTURE_FORMAT_UNIFIED,
+		XR_VRS_TEXTURE_FORMAT_FRAGMENT_SHADING_RATE,
+		XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP,
+	};
+
 protected:
 	_THREAD_SAFE_CLASS_
 
@@ -159,6 +165,7 @@ public:
 
 	/** VRS **/
 	virtual RID get_vrs_texture(); /* obtain VRS texture */
+	virtual VRSTextureFormat get_vrs_texture_format() { return XR_VRS_TEXTURE_FORMAT_UNIFIED; }
 
 	XRInterface();
 	~XRInterface();
@@ -168,3 +175,4 @@ VARIANT_ENUM_CAST(XRInterface::Capabilities);
 VARIANT_ENUM_CAST(XRInterface::TrackingStatus);
 VARIANT_ENUM_CAST(XRInterface::PlayAreaMode);
 VARIANT_ENUM_CAST(XRInterface::EnvironmentBlendMode);
+VARIANT_ENUM_CAST(XRInterface::VRSTextureFormat);

+ 9 - 0
servers/xr/xr_interface_extension.cpp

@@ -51,6 +51,7 @@ void XRInterfaceExtension::_bind_methods() {
 	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(_get_vrs_texture_format);
 
 	GDVIRTUAL_BIND(_process);
 	GDVIRTUAL_BIND(_pre_render);
@@ -242,6 +243,14 @@ RID XRInterfaceExtension::get_vrs_texture() {
 	}
 }
 
+XRInterface::VRSTextureFormat XRInterfaceExtension::get_vrs_texture_format() {
+	VRSTextureFormat vrs_texture_format = XR_VRS_TEXTURE_FORMAT_UNIFIED;
+	if (GDVIRTUAL_CALL(_get_vrs_texture_format, vrs_texture_format)) {
+		return vrs_texture_format;
+	}
+	return vrs_texture_format;
+}
+
 RID XRInterfaceExtension::get_color_texture() {
 	RID texture;
 	GDVIRTUAL_CALL(_get_color_texture, texture);

+ 2 - 0
servers/xr/xr_interface_extension.h

@@ -103,6 +103,7 @@ public:
 	virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override;
 	virtual Projection 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;
+	virtual VRSTextureFormat get_vrs_texture_format() override;
 	virtual RID get_color_texture() override;
 	virtual RID get_depth_texture() override;
 	virtual RID get_velocity_texture() override;
@@ -113,6 +114,7 @@ public:
 	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);
+	GDVIRTUAL0R(VRSTextureFormat, _get_vrs_texture_format);
 	GDVIRTUAL0R(RID, _get_color_texture);
 	GDVIRTUAL0R(RID, _get_depth_texture);
 	GDVIRTUAL0R(RID, _get_velocity_texture);