Pārlūkot izejas kodu

Merge pull request #99407 from devloglogan/rec-resolution

Allow using custom `Rect2i` for rendering with OpenXR
Rémi Verschelde 6 mēneši atpakaļ
vecāks
revīzija
3014eec40d

+ 3 - 0
doc/classes/XRVRS.xml

@@ -23,6 +23,9 @@
 		<member name="vrs_min_radius" type="float" setter="set_vrs_min_radius" getter="get_vrs_min_radius" default="20.0">
 			The minimum radius around the focal point where full quality is guaranteed if VRS is used as a percentage of screen size.
 		</member>
+		<member name="vrs_render_region" type="Rect2i" setter="set_vrs_render_region" getter="get_vrs_render_region" default="Rect2i(0, 0, 0, 0)">
+			The render region that the VRS texture will be scaled to when generated.
+		</member>
 		<member name="vrs_strength" type="float" setter="set_vrs_strength" getter="get_vrs_strength" default="1.0">
 			The strength used to calculate the VRS density map. The greater this value, the more noticeable VRS is.
 		</member>

+ 14 - 1
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -2254,7 +2254,12 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 	RenderDataGLES3 render_data;
 	{
 		render_data.render_buffers = rb;
-		render_data.transparent_bg = rt ? rt->is_transparent : false;
+
+		if (rt) {
+			render_data.transparent_bg = rt->is_transparent;
+			render_data.render_region = rt->render_region;
+		}
+
 		// Our first camera is used by default
 		render_data.cam_transform = p_camera_data->main_transform;
 		render_data.inv_cam_transform = render_data.cam_transform.affine_inverse();
@@ -2493,6 +2498,10 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 		RENDER_TIMESTAMP("Depth Prepass");
 		//pre z pass
 
+		if (render_data.render_region != Rect2i()) {
+			glViewport(render_data.render_region.position.x, render_data.render_region.position.y, render_data.render_region.size.width, render_data.render_region.size.height);
+		}
+
 		scene_state.enable_gl_depth_test(true);
 		scene_state.enable_gl_depth_draw(true);
 		scene_state.enable_gl_blend(false);
@@ -2572,6 +2581,10 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 	RENDER_TIMESTAMP("Render Opaque Pass");
 	uint64_t spec_constant_base_flags = 0;
 
+	if (render_data.render_region != Rect2i()) {
+		glViewport(render_data.render_region.position.x, render_data.render_region.position.y, render_data.render_region.size.width, render_data.render_region.size.height);
+	}
+
 	{
 		// Specialization Constants that apply for entire rendering pass.
 		if (render_data.directional_light_count == 0) {

+ 1 - 0
drivers/gles3/rasterizer_scene_gles3.h

@@ -98,6 +98,7 @@ enum SkyUniformLocation {
 struct RenderDataGLES3 {
 	Ref<RenderSceneBuffersGLES3> render_buffers;
 	bool transparent_bg = false;
+	Rect2i render_region;
 
 	Transform3D cam_transform;
 	Transform3D inv_cam_transform;

+ 14 - 0
drivers/gles3/storage/texture_storage.cpp

@@ -2568,6 +2568,20 @@ RID TextureStorage::render_target_get_override_velocity(RID p_render_target) con
 	return rt->overridden.velocity;
 }
 
+void TextureStorage::render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_NULL(rt);
+
+	rt->render_region = p_render_region;
+}
+
+Rect2i TextureStorage::render_target_get_render_region(RID p_render_target) const {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_NULL_V(rt, Rect2i());
+
+	return rt->render_region;
+}
+
 RID TextureStorage::render_target_get_texture(RID p_render_target) {
 	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
 	ERR_FAIL_NULL_V(rt, RID());

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

@@ -372,6 +372,8 @@ struct RenderTarget {
 	RS::ViewportMSAA msaa = RS::VIEWPORT_MSAA_DISABLED;
 	bool reattach_textures = false;
 
+	Rect2i render_region;
+
 	struct RTOverridden {
 		bool is_overridden = false;
 		RID color;
@@ -691,6 +693,9 @@ public:
 	virtual RID render_target_get_override_velocity(RID p_render_target) const override;
 	virtual RID render_target_get_override_velocity_depth(RID p_render_target) const override { return RID(); }
 
+	virtual void render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) override;
+	virtual Rect2i render_target_get_render_region(RID p_render_target) const override;
+
 	virtual RID render_target_get_texture(RID p_render_target) override;
 
 	virtual void render_target_set_velocity_target_size(RID p_render_target, const Size2i &p_target_size) override {}

+ 14 - 0
modules/openxr/doc_classes/OpenXRAPIExtension.xml

@@ -82,6 +82,13 @@
 				Returns the predicted display timing for the current frame.
 			</description>
 		</method>
+		<method name="get_projection_layer">
+			<return type="int" />
+			<description>
+				Returns a pointer to the render state's [code]XrCompositionLayerProjection[/code] struct.
+				[b]Note:[/b] This method should only be called from the rendering thread.
+			</description>
+		</method>
 		<method name="get_render_state_z_far">
 			<return type="float" />
 			<description>
@@ -231,6 +238,13 @@
 				Set the object name of an OpenXR object, used for debug output. [param object_type] must be a valid OpenXR [code]XrObjectType[/code] enum and [param object_handle] must be a valid OpenXR object handle.
 			</description>
 		</method>
+		<method name="set_render_region">
+			<return type="void" />
+			<param index="0" name="render_region" type="Rect2i" />
+			<description>
+				Sets the render region to [param render_region], overriding the normal render target's rect.
+			</description>
+		</method>
 		<method name="set_velocity_depth_texture">
 			<return type="void" />
 			<param index="0" name="render_target" type="RID" />

+ 45 - 9
modules/openxr/openxr_api.cpp

@@ -2167,6 +2167,14 @@ void OpenXRAPI::_set_render_state_multiplier(double p_render_target_size_multipl
 	openxr_api->render_state.render_target_size_multiplier = p_render_target_size_multiplier;
 }
 
+void OpenXRAPI::_set_render_state_render_region(const Rect2i &p_render_region) {
+	ERR_NOT_ON_RENDER_THREAD;
+
+	OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+	ERR_FAIL_NULL(openxr_api);
+	openxr_api->render_state.render_region = p_render_region;
+}
+
 bool OpenXRAPI::process() {
 	ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
 
@@ -2399,6 +2407,12 @@ Size2i OpenXRAPI::get_velocity_target_size() {
 	return velocity_target_size;
 }
 
+const XrCompositionLayerProjection *OpenXRAPI::get_projection_layer() const {
+	ERR_NOT_ON_RENDER_THREAD_V(nullptr);
+
+	return &render_state.projection_layer;
+}
+
 void OpenXRAPI::post_draw_viewport(RID p_render_target) {
 	// Must be called from rendering thread!
 	ERR_NOT_ON_RENDER_THREAD;
@@ -2432,6 +2446,23 @@ void OpenXRAPI::end_frame() {
 		}
 	}
 
+	Rect2i new_render_region = (render_state.render_region != Rect2i()) ? render_state.render_region : Rect2i(Point2i(0, 0), render_state.main_swapchain_size);
+
+	for (uint32_t i = 0; i < render_state.view_count; i++) {
+		render_state.projection_views[i].subImage.imageRect.offset.x = new_render_region.position.x;
+		render_state.projection_views[i].subImage.imageRect.offset.y = new_render_region.position.y;
+		render_state.projection_views[i].subImage.imageRect.extent.width = new_render_region.size.width;
+		render_state.projection_views[i].subImage.imageRect.extent.height = new_render_region.size.height;
+	}
+	if (render_state.submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && render_state.depth_views) {
+		for (uint32_t i = 0; i < render_state.view_count; i++) {
+			render_state.depth_views[i].subImage.imageRect.offset.x = new_render_region.position.x;
+			render_state.depth_views[i].subImage.imageRect.offset.y = new_render_region.position.y;
+			render_state.depth_views[i].subImage.imageRect.extent.width = new_render_region.size.width;
+			render_state.depth_views[i].subImage.imageRect.extent.height = new_render_region.size.height;
+		}
+	}
+
 	// must have:
 	// - should_render set to true
 	// - a valid view pose for projection_views[eye].pose to submit layer
@@ -2496,14 +2527,10 @@ void OpenXRAPI::end_frame() {
 		layer_flags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
 	}
 
-	XrCompositionLayerProjection projection_layer = {
-		XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type
-		nullptr, // next
-		layer_flags, // layerFlags
-		render_state.play_space, // space
-		render_state.view_count, // viewCount
-		render_state.projection_views, // views
-	};
+	render_state.projection_layer.layerFlags = layer_flags;
+	render_state.projection_layer.space = render_state.play_space;
+	render_state.projection_layer.viewCount = render_state.view_count;
+	render_state.projection_layer.views = render_state.projection_views;
 
 	if (projection_views_extensions.size() > 0) {
 		for (uint32_t v = 0; v < render_state.view_count; v++) {
@@ -2518,7 +2545,7 @@ void OpenXRAPI::end_frame() {
 		}
 	}
 
-	ordered_layers_list.push_back({ (const XrCompositionLayerBaseHeader *)&projection_layer, 0 });
+	ordered_layers_list.push_back({ (const XrCompositionLayerBaseHeader *)&render_state.projection_layer, 0 });
 
 	// Sort our layers.
 	ordered_layers_list.sort_custom<OrderedCompositionLayer>();
@@ -2580,6 +2607,15 @@ void OpenXRAPI::set_render_target_size_multiplier(double multiplier) {
 	set_render_state_multiplier(multiplier);
 }
 
+Rect2i OpenXRAPI::get_render_region() const {
+	return render_region;
+}
+
+void OpenXRAPI::set_render_region(const Rect2i &p_render_region) {
+	render_region = p_render_region;
+	set_render_state_render_region(p_render_region);
+}
+
 bool OpenXRAPI::is_foveation_supported() const {
 	OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton();
 	return fov_ext != nullptr && fov_ext->is_enabled();

+ 23 - 0
modules/openxr/openxr_api.h

@@ -143,6 +143,7 @@ private:
 	bool running = false;
 	XrFrameState frame_state = { XR_TYPE_FRAME_STATE, nullptr, 0, 0, false };
 	double render_target_size_multiplier = 1.0;
+	Rect2i render_region;
 
 	OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr;
 	XrSystemGraphicsProperties graphics_properties;
@@ -341,6 +342,7 @@ private:
 		XrSpace play_space = XR_NULL_HANDLE;
 		double render_target_size_multiplier = 1.0;
 		uint64_t frame = 0;
+		Rect2i render_region;
 
 		uint32_t view_count = 0;
 		XrView *views = nullptr;
@@ -352,6 +354,15 @@ private:
 		double z_near = 0.0;
 		double z_far = 0.0;
 
+		XrCompositionLayerProjection projection_layer = {
+			XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type
+			nullptr, // next
+			0, // layerFlags
+			XR_NULL_HANDLE, // space
+			0, // viewCount
+			nullptr // views
+		};
+
 		Size2i main_swapchain_size;
 		OpenXRSwapChainInfo main_swapchains[OPENXR_SWAPCHAIN_MAX];
 	} render_state;
@@ -361,6 +372,7 @@ private:
 	static void _set_render_display_info(XrTime p_predicted_display_time, bool p_should_render);
 	static void _set_render_play_space(uint64_t p_play_space);
 	static void _set_render_state_multiplier(double p_render_target_size_multiplier);
+	static void _set_render_state_render_region(const Rect2i &p_render_region);
 
 	_FORCE_INLINE_ void allocate_view_buffers(uint32_t p_view_count, bool p_submit_depth_buffer) {
 		// If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready...
@@ -402,6 +414,13 @@ private:
 		rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_state_multiplier).bind(p_render_target_size_multiplier));
 	}
 
+	_FORCE_INLINE_ void set_render_state_render_region(const Rect2i &p_render_region) {
+		RenderingServer *rendering_server = RenderingServer::get_singleton();
+		ERR_FAIL_NULL(rendering_server);
+
+		rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_state_render_region).bind(p_render_region));
+	}
+
 public:
 	XrInstance get_instance() const { return instance; }
 	XrSystemId get_system_id() const { return system_id; }
@@ -491,6 +510,7 @@ public:
 	RID get_velocity_depth_texture();
 	void set_velocity_target_size(const Size2i &p_target_size);
 	Size2i get_velocity_target_size();
+	const XrCompositionLayerProjection *get_projection_layer() const;
 	void post_draw_viewport(RID p_render_target);
 	void end_frame();
 
@@ -503,6 +523,9 @@ public:
 	double get_render_target_size_multiplier() const;
 	void set_render_target_size_multiplier(double multiplier);
 
+	Rect2i get_render_region() const;
+	void set_render_region(const Rect2i &p_render_region);
+
 	// Foveation settings
 	bool is_foveation_supported() const;
 

+ 14 - 0
modules/openxr/openxr_api_extension.cpp

@@ -80,6 +80,10 @@ void OpenXRAPIExtension::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("openxr_swapchain_get_image", "swapchain"), &OpenXRAPIExtension::openxr_swapchain_get_image);
 	ClassDB::bind_method(D_METHOD("openxr_swapchain_release", "swapchain"), &OpenXRAPIExtension::openxr_swapchain_release);
 
+	ClassDB::bind_method(D_METHOD("get_projection_layer"), &OpenXRAPIExtension::get_projection_layer);
+
+	ClassDB::bind_method(D_METHOD("set_render_region", "render_region"), &OpenXRAPIExtension::set_render_region);
+
 	ClassDB::bind_method(D_METHOD("set_emulate_environment_blend_mode_alpha_blend", "enabled"), &OpenXRAPIExtension::set_emulate_environment_blend_mode_alpha_blend);
 	ClassDB::bind_method(D_METHOD("is_environment_blend_mode_alpha_supported"), &OpenXRAPIExtension::is_environment_blend_mode_alpha_blend_supported);
 
@@ -300,6 +304,16 @@ void OpenXRAPIExtension::openxr_swapchain_release(uint64_t p_swapchain_info) {
 	swapchain_info->release();
 }
 
+uint64_t OpenXRAPIExtension::get_projection_layer() {
+	ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+	return (uint64_t)OpenXRAPI::get_singleton()->get_projection_layer();
+}
+
+void OpenXRAPIExtension::set_render_region(const Rect2i &p_render_region) {
+	ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+	OpenXRAPI::get_singleton()->set_render_region(p_render_region);
+}
+
 void OpenXRAPIExtension::set_emulate_environment_blend_mode_alpha_blend(bool p_enabled) {
 	ERR_FAIL_NULL(OpenXRAPI::get_singleton());
 	OpenXRAPI::get_singleton()->set_emulate_environment_blend_mode_alpha_blend(p_enabled);

+ 4 - 0
modules/openxr/openxr_api_extension.h

@@ -100,6 +100,10 @@ public:
 	RID openxr_swapchain_get_image(uint64_t p_swapchain_info);
 	void openxr_swapchain_release(uint64_t p_swapchain_info);
 
+	uint64_t get_projection_layer();
+
+	void set_render_region(const Rect2i &p_render_region);
+
 	enum OpenXRAlphaBlendModeSupport {
 		OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE = 0,
 		OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL = 1,

+ 10 - 0
modules/openxr/openxr_interface.cpp

@@ -1055,6 +1055,14 @@ Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_as
 	return cm;
 }
 
+Rect2i OpenXRInterface::get_render_region() {
+	if (openxr_api) {
+		return openxr_api->get_render_region();
+	} else {
+		return Rect2i();
+	}
+}
+
 RID OpenXRInterface::get_color_texture() {
 	if (openxr_api) {
 		return openxr_api->get_color_texture();
@@ -1528,6 +1536,8 @@ RID OpenXRInterface::get_vrs_texture() {
 		eye_foci.push_back(openxr_api->get_eye_focus(v, aspect_ratio));
 	}
 
+	xr_vrs.set_vrs_render_region(get_render_region());
+
 	return xr_vrs.make_vrs_texture(target_size, eye_foci);
 }
 

+ 2 - 0
modules/openxr/openxr_interface.h

@@ -183,6 +183,8 @@ 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 Rect2i get_render_region() override;
+
 	virtual RID get_color_texture() override;
 	virtual RID get_depth_texture() override;
 	virtual RID get_velocity_texture() override;

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

@@ -205,6 +205,9 @@ public:
 	virtual RID render_target_get_override_velocity(RID p_render_target) const override { return RID(); }
 	virtual RID render_target_get_override_velocity_depth(RID p_render_target) const override { return RID(); }
 
+	virtual void render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) override {}
+	virtual Rect2i render_target_get_render_region(RID p_render_target) const override { return Rect2i(); }
+
 	virtual RID render_target_get_texture(RID p_render_target) override { return RID(); }
 
 	virtual void render_target_set_velocity_target_size(RID p_render_target, const Size2i &p_target_size) override {}

+ 4 - 4
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp

@@ -2074,7 +2074,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 
 		bool finish_depth = using_ssao || using_ssil || using_sdfgi || using_voxelgi || ce_pre_opaque_resolved_depth || ce_post_opaque_resolved_depth;
 		RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, depth_pass_mode, 0, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, base_specialization);
-		_render_list_with_draw_list(&render_list_params, depth_framebuffer, RD::DrawFlags(needs_pre_resolve ? RD::DRAW_DEFAULT_ALL : RD::DRAW_CLEAR_ALL), depth_pass_clear, 0.0f);
+		_render_list_with_draw_list(&render_list_params, depth_framebuffer, RD::DrawFlags(needs_pre_resolve ? RD::DRAW_DEFAULT_ALL : RD::DRAW_CLEAR_ALL), depth_pass_clear, 0.0f, 0u, p_render_data->render_region);
 
 		RD::get_singleton()->draw_command_end_label();
 
@@ -2153,7 +2153,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 			uint32_t opaque_color_pass_flags = using_motion_pass ? (color_pass_flags & ~uint32_t(COLOR_PASS_FLAG_MOTION_VECTORS)) : color_pass_flags;
 			RID opaque_framebuffer = using_motion_pass ? rb_data->get_color_pass_fb(opaque_color_pass_flags) : color_framebuffer;
 			RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, base_specialization);
-			_render_list_with_draw_list(&render_list_params, opaque_framebuffer, RD::DrawFlags(load_color ? RD::DRAW_DEFAULT_ALL : RD::DRAW_CLEAR_COLOR_ALL) | (depth_pre_pass ? RD::DRAW_DEFAULT_ALL : RD::DRAW_CLEAR_DEPTH), c, 0.0f);
+			_render_list_with_draw_list(&render_list_params, opaque_framebuffer, RD::DrawFlags(load_color ? RD::DRAW_DEFAULT_ALL : RD::DRAW_CLEAR_COLOR_ALL) | (depth_pre_pass ? RD::DRAW_DEFAULT_ALL : RD::DRAW_CLEAR_DEPTH), c, 0.0f, 0u, p_render_data->render_region);
 		}
 
 		RD::get_singleton()->draw_command_end_label();
@@ -2229,7 +2229,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 		RENDER_TIMESTAMP("Render Sky");
 
 		RD::get_singleton()->draw_command_begin_label("Draw Sky");
-		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(color_only_framebuffer);
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(color_only_framebuffer, RD::DRAW_DEFAULT_ALL, Vector<Color>(), 1.0f, 0u, p_render_data->render_region);
 
 		sky.draw_sky(draw_list, rb, p_render_data->environment, color_only_framebuffer, time, sky_luminance_multiplier, sky_brightness_multiplier);
 
@@ -2358,7 +2358,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 
 		RID alpha_framebuffer = rb_data.is_valid() ? rb_data->get_color_pass_fb(transparent_color_pass_flags) : color_only_framebuffer;
 		RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, base_specialization);
-		_render_list_with_draw_list(&render_list_params, alpha_framebuffer);
+		_render_list_with_draw_list(&render_list_params, alpha_framebuffer, RD::DRAW_DEFAULT_ALL, Vector<Color>(), 0.0f, 0u, p_render_data->render_region);
 	}
 
 	RD::get_singleton()->draw_command_end_label();

+ 2 - 2
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp

@@ -1099,7 +1099,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
 			}
 		}
 
-		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, load_color ? RD::DRAW_CLEAR_DEPTH : (RD::DRAW_CLEAR_COLOR_0 | RD::DRAW_CLEAR_DEPTH), c, 0.0f, 0, Rect2(), breadcrumb);
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, load_color ? RD::DRAW_CLEAR_DEPTH : (RD::DRAW_CLEAR_COLOR_0 | RD::DRAW_CLEAR_DEPTH), c, 0.0f, 0, p_render_data->render_region, breadcrumb);
 		RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
 
 		if (copy_canvas) {
@@ -1196,7 +1196,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
 				render_list_params.framebuffer_format = fb_format;
 				render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass(); // Should now always be 0.
 
-				draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::DRAW_DEFAULT_ALL, Vector<Color>(), 1.0f, 0, Rect2(), breadcrumb);
+				draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::DRAW_DEFAULT_ALL, Vector<Color>(), 1.0f, 0, p_render_data->render_region, breadcrumb);
 				_render_list(draw_list, fb_format, &render_list_params, 0, render_list_params.element_count);
 				RD::get_singleton()->draw_list_end();
 

+ 1 - 0
servers/rendering/renderer_rd/renderer_scene_render_rd.cpp

@@ -1228,6 +1228,7 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render
 
 		if (p_render_buffers.is_valid() && p_reflection_probe.is_null()) {
 			render_data.transparent_bg = texture_storage->render_target_get_transparent(rb->get_render_target());
+			render_data.render_region = texture_storage->render_target_get_render_region(rb->get_render_target());
 		}
 	}
 

+ 1 - 0
servers/rendering/renderer_rd/storage_rd/render_data_rd.h

@@ -79,6 +79,7 @@ public:
 
 	/* Viewport data */
 	bool transparent_bg = false;
+	Rect2i render_region;
 
 	/* Shadow data */
 	const RendererSceneRender::RenderShadowData *render_shadows = nullptr;

+ 14 - 0
servers/rendering/renderer_rd/storage_rd/texture_storage.cpp

@@ -3525,6 +3525,20 @@ RID TextureStorage::render_target_get_override_velocity_slice(RID p_render_targe
 	}
 }
 
+void RendererRD::TextureStorage::render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_NULL(rt);
+
+	rt->render_region = p_render_region;
+}
+
+Rect2i RendererRD::TextureStorage::render_target_get_render_region(RID p_render_target) const {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_NULL_V(rt, Rect2i());
+
+	return rt->render_region;
+}
+
 void TextureStorage::render_target_set_transparent(RID p_render_target, bool p_is_transparent) {
 	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
 	ERR_FAIL_NULL(rt);

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

@@ -388,6 +388,8 @@ private:
 		RS::ViewportVRSUpdateMode vrs_update_mode = RS::VIEWPORT_VRS_UPDATE_ONCE;
 		RID vrs_texture;
 
+		Rect2i render_region;
+
 		// overridden textures
 		struct RTOverridden {
 			RID color;
@@ -786,6 +788,9 @@ public:
 	RID render_target_get_override_velocity_slice(RID p_render_target, const uint32_t p_layer) const;
 	virtual RID render_target_get_override_velocity_depth(RID p_render_target) const override { return RID(); }
 
+	virtual void render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) override;
+	virtual Rect2i render_target_get_render_region(RID p_render_target) const override;
+
 	virtual RID render_target_get_texture(RID p_render_target) override;
 
 	virtual void render_target_set_velocity_target_size(RID p_render_target, const Size2i &p_target_size) override {}

+ 2 - 0
servers/rendering/renderer_viewport.cpp

@@ -832,6 +832,8 @@ void RendererViewport::draw_viewports(bool p_swap_buffers) {
 					viewport_set_force_motion_vectors(vp->self, false);
 				}
 
+				RSG::texture_storage->render_target_set_render_region(vp->render_target, xr_interface->get_render_region());
+
 				// render...
 				RSG::scene->set_debug_draw_mode(vp->debug_draw);
 

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

@@ -184,6 +184,9 @@ public:
 	virtual RID render_target_get_override_velocity(RID p_render_target) const = 0;
 	virtual RID render_target_get_override_velocity_depth(RID p_render_target) const = 0;
 
+	virtual void render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) = 0;
+	virtual Rect2i render_target_get_render_region(RID p_render_target) const = 0;
+
 	// get textures
 	virtual RID render_target_get_texture(RID p_render_target) = 0;
 

+ 4 - 0
servers/xr/xr_interface.cpp

@@ -194,6 +194,10 @@ Size2i XRInterface::get_velocity_target_size() {
 	return Size2i();
 }
 
+Rect2i XRInterface::get_render_region() {
+	return Rect2i();
+}
+
 PackedStringArray XRInterface::get_suggested_tracker_names() const {
 	PackedStringArray arr;
 

+ 1 - 0
servers/xr/xr_interface.h

@@ -139,6 +139,7 @@ public:
 	virtual RID get_velocity_texture(); /* obtain velocity output texture (if applicable, used for spacewarp) */
 	virtual RID get_velocity_depth_texture();
 	virtual Size2i get_velocity_target_size();
+	virtual Rect2i get_render_region();
 	virtual void pre_render() {}
 	virtual bool pre_draw_viewport(RID p_render_target) { return true; } /* inform XR interface we are about to start our viewport draw process */
 	virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) = 0; /* inform XR interface we finished our viewport draw process */

+ 23 - 3
servers/xr/xr_vrs.cpp

@@ -40,8 +40,12 @@ void XRVRS::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_vrs_strength"), &XRVRS::get_vrs_strength);
 	ClassDB::bind_method(D_METHOD("set_vrs_strength", "strength"), &XRVRS::set_vrs_strength);
 
+	ClassDB::bind_method(D_METHOD("get_vrs_render_region"), &XRVRS::get_vrs_render_region);
+	ClassDB::bind_method(D_METHOD("set_vrs_render_region", "render_region"), &XRVRS::set_vrs_render_region);
+
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vrs_min_radius", PROPERTY_HINT_RANGE, "1.0,100.0,1.0"), "set_vrs_min_radius", "get_vrs_min_radius");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vrs_strength", PROPERTY_HINT_RANGE, "0.1,10.0,0.1"), "set_vrs_strength", "get_vrs_strength");
+	ADD_PROPERTY(PropertyInfo(Variant::RECT2I, "vrs_render_region"), "set_vrs_render_region", "get_vrs_render_region");
 
 	ClassDB::bind_method(D_METHOD("make_vrs_texture", "target_size", "eye_foci"), &XRVRS::make_vrs_texture);
 }
@@ -88,6 +92,15 @@ void XRVRS::set_vrs_strength(float p_vrs_strength) {
 	}
 }
 
+Rect2i XRVRS::get_vrs_render_region() const {
+	return vrs_render_region;
+}
+
+void XRVRS::set_vrs_render_region(const Rect2i &p_vrs_render_region) {
+	vrs_render_region = p_vrs_render_region;
+	vrs_dirty = true;
+}
+
 RID XRVRS::make_vrs_texture(const Size2 &p_target_size, const PackedVector2Array &p_eye_foci) {
 	ERR_FAIL_COND_V(p_eye_foci.is_empty(), RID());
 
@@ -123,19 +136,26 @@ RID XRVRS::make_vrs_texture(const Size2 &p_target_size, const PackedVector2Array
 		target_size = vrs_sizei;
 		eye_foci = p_eye_foci;
 
+		Size2 region_ratio = Size2(1.0, 1.0);
+		Point2i region_offset;
+		if (vrs_render_region != Rect2i()) {
+			region_ratio = (Size2)vrs_render_region.size / p_target_size;
+			region_offset = (Point2)vrs_render_region.position / p_target_size * vrs_sizei;
+		}
+
 		for (int i = 0; i < eye_foci.size() && i < RendererSceneRender::MAX_RENDER_VIEWS; i++) {
 			PackedByteArray data;
 			data.resize(vrs_sizei.x * vrs_sizei.y * 2);
 			uint8_t *data_ptr = data.ptrw();
 
 			Vector2i view_center;
-			view_center.x = int(vrs_size.x * (eye_foci[i].x + 1.0) * 0.5);
-			view_center.y = int(vrs_size.y * (eye_foci[i].y + 1.0) * 0.5);
+			view_center.x = int(vrs_size.x * (eye_foci[i].x + 1.0) * region_ratio.x * 0.5) + region_offset.x;
+			view_center.y = int(vrs_size.y * (eye_foci[i].y + 1.0) * region_ratio.y * 0.5) + region_offset.y;
 
 			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);
+					Vector2 offset = Vector2(x - view_center.x, y - view_center.y) / region_ratio;
 					real_t density = 255.0 * MAX(0.0, (Math::abs(offset.x) - min_radius) / outer_radius);
 					data_ptr[d++] = MIN(255, density);
 					density = 255.0 * MAX(0.0, (Math::abs(offset.y) - min_radius) / outer_radius);

+ 3 - 0
servers/xr/xr_vrs.h

@@ -44,6 +44,7 @@ class XRVRS : public Object {
 private:
 	float vrs_min_radius = 20.0;
 	float vrs_strength = 1.0;
+	Rect2i vrs_render_region;
 	bool vrs_dirty = true;
 
 	RID vrs_texture;
@@ -60,6 +61,8 @@ public:
 	void set_vrs_min_radius(float p_vrs_min_radius);
 	float get_vrs_strength() const;
 	void set_vrs_strength(float p_vrs_strength);
+	Rect2i get_vrs_render_region() const;
+	void set_vrs_render_region(const Rect2i &p_vrs_render_region);
 
 	RID make_vrs_texture(const Size2 &p_target_size, const PackedVector2Array &p_eye_foci);
 };