Просмотр исходного кода

Merge pull request #58141 from lawnjelly/occluder_shared_resources

Rémi Verschelde 3 лет назад
Родитель
Сommit
2dd545b512

+ 1 - 1
doc/classes/OccluderShapeSphere.xml

@@ -28,7 +28,7 @@
 		</method>
 	</methods>
 	<members>
-		<member name="spheres" type="Array" setter="set_spheres" getter="get_spheres" default="[  ]">
+		<member name="spheres" type="Array" setter="set_spheres" getter="get_spheres" default="[ Plane( 0, 0, 0, 1 ) ]">
 			The sphere data can be accessed as an array of [Plane]s. The position of each sphere is stored in the [code]normal[/code], and the radius is stored in the [code]d[/code] value of the plane.
 		</member>
 	</members>

+ 22 - 25
scene/3d/occluder.cpp

@@ -35,10 +35,6 @@
 
 void Occluder::resource_changed(RES res) {
 	update_gizmo();
-
-	if (_shape.is_valid()) {
-		_shape->update_shape_to_visual_server();
-	}
 }
 
 void Occluder::set_shape(const Ref<OccluderShape> &p_shape) {
@@ -52,16 +48,9 @@ void Occluder::set_shape(const Ref<OccluderShape> &p_shape) {
 	if (_shape.is_valid()) {
 		_shape->register_owner(this);
 
-		// Especially in the editor, the shape can be added AFTER the occluder
-		// is added to the world. This means the shape scenario will not be set.
-		// To remedy this we make sure the scenario etc is refreshed as soon as
-		// a shape is set on the occluder.
 		if (is_inside_world() && get_world().is_valid()) {
-			_shape->notification_enter_world(get_world()->get_scenario());
-			_shape->update_shape_to_visual_server();
-			if (is_inside_tree()) {
-				_shape->update_active_to_visual_server(is_visible_in_tree());
-				_shape->update_transform_to_visual_server(get_global_transform());
+			if (_occluder_instance.is_valid()) {
+				VisualServer::get_singleton()->occluder_instance_link_resource(_occluder_instance, p_shape->get_rid());
 			}
 		}
 	}
@@ -115,12 +104,16 @@ void Occluder::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_WORLD: {
 			ERR_FAIL_COND(get_world().is_null());
-			if (_shape.is_valid()) {
-				_shape->notification_enter_world(get_world()->get_scenario());
-				_shape->update_active_to_visual_server(is_visible_in_tree());
-				_shape->update_shape_to_visual_server();
-				_shape->update_transform_to_visual_server(get_global_transform());
+
+			if (_occluder_instance.is_valid()) {
+				VisualServer::get_singleton()->occluder_instance_set_scenario(_occluder_instance, get_world()->get_scenario());
+				if (get_shape().is_valid()) {
+					VisualServer::get_singleton()->occluder_instance_link_resource(_occluder_instance, get_shape()->get_rid());
+				}
+				VisualServer::get_singleton()->occluder_instance_set_active(_occluder_instance, is_visible_in_tree());
+				VisualServer::get_singleton()->occluder_instance_set_transform(_occluder_instance, get_global_transform());
 			}
+
 #ifdef TOOLS_ENABLED
 			if (Engine::get_singleton()->is_editor_hint()) {
 				set_process_internal(true);
@@ -128,8 +121,8 @@ void Occluder::_notification(int p_what) {
 #endif
 		} break;
 		case NOTIFICATION_EXIT_WORLD: {
-			if (_shape.is_valid()) {
-				_shape->notification_exit_world();
+			if (_occluder_instance.is_valid()) {
+				VisualServer::get_singleton()->occluder_instance_set_scenario(_occluder_instance, RID());
 			}
 #ifdef TOOLS_ENABLED
 			if (Engine::get_singleton()->is_editor_hint()) {
@@ -138,14 +131,13 @@ void Occluder::_notification(int p_what) {
 #endif
 		} break;
 		case NOTIFICATION_VISIBILITY_CHANGED: {
-			if (_shape.is_valid() && is_inside_tree()) {
-				_shape->update_active_to_visual_server(is_visible_in_tree());
+			if (_occluder_instance.is_valid() && is_inside_tree()) {
+				VisualServer::get_singleton()->occluder_instance_set_active(_occluder_instance, is_visible_in_tree());
 			}
 		} break;
 		case NOTIFICATION_TRANSFORM_CHANGED: {
-			if (_shape.is_valid()) {
-				_shape->update_transform_to_visual_server(get_global_transform());
-
+			if (_occluder_instance.is_valid()) {
+				VisualServer::get_singleton()->occluder_instance_set_transform(_occluder_instance, get_global_transform());
 #ifdef TOOLS_ENABLED
 				if (Engine::get_singleton()->is_editor_hint()) {
 					update_configuration_warning();
@@ -171,10 +163,15 @@ void Occluder::_bind_methods() {
 }
 
 Occluder::Occluder() {
+	_occluder_instance = RID_PRIME(VisualServer::get_singleton()->occluder_instance_create());
 	set_notify_transform(true);
 }
 
 Occluder::~Occluder() {
+	if (_occluder_instance != RID()) {
+		VisualServer::get_singleton()->free(_occluder_instance);
+	}
+
 	if (!_shape.is_null()) {
 		_shape->unregister_owner(this);
 	}

+ 1 - 0
scene/3d/occluder.h

@@ -40,6 +40,7 @@ class Occluder : public Spatial {
 	friend class OccluderSpatialGizmo;
 	friend class OccluderEditorPlugin;
 
+	RID _occluder_instance;
 	Ref<OccluderShape> _shape;
 
 	void resource_changed(RES res);

+ 16 - 22
scene/resources/occluder_shape.cpp

@@ -41,28 +41,16 @@
 void OccluderShape::_bind_methods() {
 }
 
-OccluderShape::OccluderShape(RID p_shape) {
-	_shape = p_shape;
+OccluderShape::OccluderShape() {
+	_shape = RID_PRIME(VisualServer::get_singleton()->occluder_resource_create());
 }
 
 OccluderShape::~OccluderShape() {
-	if (_shape != RID()) {
+	if (_shape.is_valid()) {
 		VisualServer::get_singleton()->free(_shape);
 	}
 }
 
-void OccluderShape::update_transform_to_visual_server(const Transform &p_global_xform) {
-	VisualServer::get_singleton()->occluder_set_transform(get_shape(), p_global_xform);
-}
-
-void OccluderShape::update_active_to_visual_server(bool p_active) {
-	VisualServer::get_singleton()->occluder_set_active(get_shape(), p_active);
-}
-
-void OccluderShape::notification_exit_world() {
-	VisualServer::get_singleton()->occluder_set_scenario(_shape, RID(), VisualServer::OCCLUDER_TYPE_UNDEFINED);
-}
-
 #ifdef TOOLS_ENABLED
 AABB OccluderShape::get_fallback_gizmo_aabb() const {
 	return AABB(Vector3(-0.5, -0.5, -0.5), Vector3(1, 1, 1));
@@ -105,7 +93,7 @@ AABB OccluderShapeSphere::get_fallback_gizmo_aabb() const {
 #endif
 
 void OccluderShapeSphere::update_shape_to_visual_server() {
-	VisualServer::get_singleton()->occluder_spheres_update(get_shape(), _spheres);
+	VisualServer::get_singleton()->occluder_resource_spheres_update(get_shape(), _spheres);
 }
 
 Transform OccluderShapeSphere::center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap) {
@@ -189,10 +177,6 @@ Transform OccluderShapeSphere::center_node(const Transform &p_global_xform, cons
 	return new_local_xform;
 }
 
-void OccluderShapeSphere::notification_enter_world(RID p_scenario) {
-	VisualServer::get_singleton()->occluder_set_scenario(get_shape(), p_scenario, VisualServer::OCCLUDER_TYPE_SPHERE);
-}
-
 void OccluderShapeSphere::set_spheres(const Vector<Plane> &p_spheres) {
 #ifdef TOOLS_ENABLED
 	// try and detect special circumstance of adding a new sphere in the editor
@@ -221,6 +205,7 @@ void OccluderShapeSphere::set_spheres(const Vector<Plane> &p_spheres) {
 	_update_aabb();
 #endif
 
+	update_shape_to_visual_server();
 	notify_change_to_owners();
 }
 
@@ -232,6 +217,7 @@ void OccluderShapeSphere::set_sphere_position(int p_idx, const Vector3 &p_positi
 #ifdef TOOLS_ENABLED
 		_update_aabb();
 #endif
+		update_shape_to_visual_server();
 		notify_change_to_owners();
 	}
 }
@@ -244,10 +230,18 @@ void OccluderShapeSphere::set_sphere_radius(int p_idx, real_t p_radius) {
 #ifdef TOOLS_ENABLED
 		_update_aabb();
 #endif
+		update_shape_to_visual_server();
 		notify_change_to_owners();
 	}
 }
 
-OccluderShapeSphere::OccluderShapeSphere() :
-		OccluderShape(RID_PRIME(VisualServer::get_singleton()->occluder_create())) {
+OccluderShapeSphere::OccluderShapeSphere() {
+	if (get_shape().is_valid()) {
+		VisualServer::get_singleton()->occluder_resource_prepare(get_shape(), VisualServer::OCCLUDER_TYPE_SPHERE);
+	}
+
+	// Create a default sphere
+	Vector<Plane> planes;
+	planes.push_back(Plane(Vector3(0, 0, 0), 1));
+	set_spheres(planes);
 }

+ 2 - 8
scene/resources/occluder_shape.h

@@ -45,17 +45,12 @@ protected:
 	static void _bind_methods();
 
 	RID get_shape() const { return _shape; }
-	OccluderShape(RID p_shape);
+	OccluderShape();
 
 public:
 	virtual RID get_rid() const { return _shape; }
 	~OccluderShape();
 
-	virtual void notification_enter_world(RID p_scenario) = 0;
-	virtual void update_shape_to_visual_server() = 0;
-	void update_transform_to_visual_server(const Transform &p_global_xform);
-	void update_active_to_visual_server(bool p_active);
-	void notification_exit_world();
 	virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap) = 0;
 
 #ifdef TOOLS_ENABLED
@@ -87,8 +82,7 @@ public:
 	void set_sphere_position(int p_idx, const Vector3 &p_position);
 	void set_sphere_radius(int p_idx, real_t p_radius);
 
-	virtual void notification_enter_world(RID p_scenario);
-	virtual void update_shape_to_visual_server();
+	void update_shape_to_visual_server();
 	virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap);
 
 #ifdef TOOLS_ENABLED

+ 11 - 7
scene/resources/occluder_shape_polygon.cpp

@@ -108,6 +108,7 @@ void OccluderShapePolygon::set_polygon_point(int p_idx, const Vector2 &p_point)
 
 	_poly_pts_local_raw.set(p_idx, p_point);
 	_sanitize_points();
+	update_shape_to_visual_server();
 	notify_change_to_owners();
 }
 
@@ -118,18 +119,21 @@ void OccluderShapePolygon::set_hole_point(int p_idx, const Vector2 &p_point) {
 
 	_hole_pts_local_raw.set(p_idx, p_point);
 	_sanitize_points();
+	update_shape_to_visual_server();
 	notify_change_to_owners();
 }
 
 void OccluderShapePolygon::set_polygon_points(const PoolVector<Vector2> &p_points) {
 	_poly_pts_local_raw = p_points;
 	_sanitize_points();
+	update_shape_to_visual_server();
 	notify_change_to_owners();
 }
 
 void OccluderShapePolygon::set_hole_points(const PoolVector<Vector2> &p_points) {
 	_hole_pts_local_raw = p_points;
 	_sanitize_points();
+	update_shape_to_visual_server();
 	notify_change_to_owners();
 }
 
@@ -141,10 +145,6 @@ PoolVector<Vector2> OccluderShapePolygon::get_hole_points() const {
 	return _hole_pts_local_raw;
 }
 
-void OccluderShapePolygon::notification_enter_world(RID p_scenario) {
-	VisualServer::get_singleton()->occluder_set_scenario(get_shape(), p_scenario, VisualServer::OCCLUDER_TYPE_MESH);
-}
-
 void OccluderShapePolygon::update_shape_to_visual_server() {
 	if (_poly_pts_local.size() < 3)
 		return;
@@ -179,11 +179,12 @@ void OccluderShapePolygon::update_shape_to_visual_server() {
 
 	face.plane = Plane(Vector3(0, 0, 0), Vector3(0, 0, -1));
 
-	VisualServer::get_singleton()->occluder_mesh_update(get_shape(), md);
+	VisualServer::get_singleton()->occluder_resource_mesh_update(get_shape(), md);
 }
 
 void OccluderShapePolygon::set_two_way(bool p_two_way) {
 	_settings_two_way = p_two_way;
+	update_shape_to_visual_server();
 	notify_change_to_owners();
 }
 
@@ -221,8 +222,11 @@ void OccluderShapePolygon::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "hole_points"), "set_hole_points", "get_hole_points");
 }
 
-OccluderShapePolygon::OccluderShapePolygon() :
-		OccluderShape(RID_PRIME(VisualServer::get_singleton()->occluder_create())) {
+OccluderShapePolygon::OccluderShapePolygon() {
+	if (get_shape().is_valid()) {
+		VisualServer::get_singleton()->occluder_resource_prepare(get_shape(), VisualServer::OCCLUDER_TYPE_MESH);
+	}
+
 	clear();
 
 	PoolVector<Vector2> points;

+ 1 - 2
scene/resources/occluder_shape_polygon.h

@@ -81,8 +81,7 @@ public:
 
 	void clear();
 
-	virtual void notification_enter_world(RID p_scenario);
-	virtual void update_shape_to_visual_server();
+	void update_shape_to_visual_server();
 	virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap);
 
 #ifdef TOOLS_ENABLED

+ 20 - 16
servers/visual/portals/portal_occlusion_culler.cpp

@@ -34,6 +34,8 @@
 #include "core/math/aabb.h"
 #include "core/project_settings.h"
 #include "portal_renderer.h"
+#include "servers/visual/visual_server_globals.h"
+#include "servers/visual/visual_server_scene.h"
 
 #define _log(a, b) ;
 //#define _log_prepare(a) log(a, 0)
@@ -275,10 +277,12 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
 	uint32_t polycount = 0;
 #endif
 
+	const PortalResources &resources = VSG::scene->get_portal_resources();
+
 	// find occluders
 	for (unsigned int o = 0; o < p_occluder_pool_ids.size(); o++) {
 		int id = p_occluder_pool_ids[o];
-		VSOccluder &occ = p_portal_renderer.get_pool_occluder(id);
+		VSOccluder_Instance &occ = p_portal_renderer.get_pool_occluder_instance(id);
 
 		// is it active?
 		// in the case of rooms, they will always be active, as inactive
@@ -290,9 +294,9 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
 		// TODO : occlusion cull spheres AGAINST themselves.
 		// i.e. a sphere that is occluded by another occluder is no
 		// use as an occluder...
-		if (occ.type == VSOccluder::OT_SPHERE) {
+		if (occ.type == VSOccluder_Instance::OT_SPHERE) {
 			// make sure world space spheres are up to date
-			p_portal_renderer.occluder_ensure_up_to_date_sphere(occ);
+			p_portal_renderer.occluder_ensure_up_to_date_sphere(resources, occ);
 
 			// cull entire AABB
 			if (is_aabb_culled(occ.aabb, p_planes)) {
@@ -301,7 +305,7 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
 
 			// multiple spheres
 			for (int n = 0; n < occ.list_ids.size(); n++) {
-				const Occlusion::Sphere &occluder_sphere = p_portal_renderer.get_pool_occluder_sphere(occ.list_ids[n]).world;
+				const Occlusion::Sphere &occluder_sphere = p_portal_renderer.get_pool_occluder_world_sphere(occ.list_ids[n]);
 
 				// is the occluder sphere culled?
 				if (is_sphere_culled(occluder_sphere.pos, occluder_sphere.radius, p_planes)) {
@@ -358,14 +362,14 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
 			}
 		} // sphere
 
-		if (occ.type == VSOccluder::OT_MESH) {
+		if (occ.type == VSOccluder_Instance::OT_MESH) {
 			// make sure world space spheres are up to date
-			p_portal_renderer.occluder_ensure_up_to_date_polys(occ);
+			p_portal_renderer.occluder_ensure_up_to_date_polys(resources, occ);
 
 			// multiple polys
 			for (int n = 0; n < occ.list_ids.size(); n++) {
-				const VSOccluder_Mesh &opoly = p_portal_renderer.get_pool_occluder_mesh(occ.list_ids[n]);
-				const Occlusion::PolyPlane &poly = opoly.poly_world;
+				const VSOccluder_Poly &opoly = p_portal_renderer.get_pool_occluder_world_poly(occ.list_ids[n]);
+				const Occlusion::PolyPlane &poly = opoly.poly;
 
 				// backface cull
 				bool faces_camera = poly.plane.is_point_over(pt_camera);
@@ -507,21 +511,21 @@ void PortalOcclusionCuller::precalc_poly_edge_planes(const Vector3 &p_pt_camera)
 		// holes
 		if (sortpoly.flags & SortPoly::SPF_HAS_HOLES) {
 			// get the mesh poly and the holes
-			const VSOccluder_Mesh &mesh = _portal_renderer->get_pool_occluder_mesh(sortpoly.mesh_source_id);
+			const VSOccluder_Poly &mesh = _portal_renderer->get_pool_occluder_world_poly(sortpoly.mesh_source_id);
 
 			dpoly.num_holes = mesh.num_holes;
 
 			for (int h = 0; h < mesh.num_holes; h++) {
 				uint32_t hid = mesh.hole_pool_ids[h];
-				const VSOccluder_Hole &hole = _portal_renderer->get_pool_occluder_hole(hid);
+				const VSOccluder_Hole &hole = _portal_renderer->get_pool_occluder_world_hole(hid);
 
 				// copy the verts to the precalced poly,
 				// we will need these later for whittling polys.
 				// We could alternatively link back to the original verts, but that gets messy.
-				dpoly.hole_polys[h] = hole.poly_world;
+				dpoly.hole_polys[h] = hole;
 
-				int hole_num_verts = hole.poly_world.num_verts;
-				const Vector3 *hverts = hole.poly_world.verts;
+				int hole_num_verts = hole.num_verts;
+				const Vector3 *hverts = hole.verts;
 
 				// number of planes equals number of verts forming edges
 				dpoly.hole_edge_planes[h].num_planes = hole_num_verts;
@@ -671,7 +675,7 @@ void PortalOcclusionCuller::whittle_polys() {
 	// order polys by distance to camera / area? NYI
 }
 
-bool PortalOcclusionCuller::calculate_poly_goodness_of_fit(const VSOccluder_Mesh &p_opoly, real_t &r_fit) {
+bool PortalOcclusionCuller::calculate_poly_goodness_of_fit(const VSOccluder_Poly &p_opoly, real_t &r_fit) {
 	// transform each of the poly points, find the area in screen space
 
 	// The points must be homogeneous coordinates, i.e. BEFORE
@@ -679,11 +683,11 @@ bool PortalOcclusionCuller::calculate_poly_goodness_of_fit(const VSOccluder_Mesh
 	// divide applied after clipping, to calculate the area.
 	// We therefore store them as planes to store the w coordinate as d.
 	Plane xpoints[Occlusion::PolyPlane::MAX_POLY_VERTS];
-	int num_verts = p_opoly.poly_world.num_verts;
+	int num_verts = p_opoly.poly.num_verts;
 
 	for (int n = 0; n < num_verts; n++) {
 		// source and dest in homogeneous coords
-		Plane source(p_opoly.poly_world.verts[n], 1.0f);
+		Plane source(p_opoly.poly.verts[n], 1.0f);
 		Plane &dest = xpoints[n];
 
 		dest = _matrix_camera.xform4(source);

+ 1 - 5
servers/visual/portals/portal_occlusion_culler.h

@@ -161,14 +161,10 @@ private:
 		return false;
 	}
 
-	bool calculate_poly_goodness_of_fit(const VSOccluder_Mesh &p_opoly, real_t &r_fit);
+	bool calculate_poly_goodness_of_fit(const VSOccluder_Poly &p_opoly, real_t &r_fit);
 	void whittle_polys();
 	void precalc_poly_edge_planes(const Vector3 &p_pt_camera);
 
-	bool is_vso_poly_culled(const VSOccluder_Mesh &p_opoly, const LocalVector<Plane> &p_planes) const {
-		return is_poly_culled(p_opoly.poly_world, p_planes);
-	}
-
 	// If all the points of the poly are beyond one of the planes (e.g. frustum), it is completely culled.
 	bool is_poly_culled(const Occlusion::PolyPlane &p_opoly, const LocalVector<Plane> &p_planes) const {
 		for (unsigned int p = 0; p < p_planes.size(); p++) {

+ 71 - 156
servers/visual/portals/portal_renderer.cpp

@@ -117,10 +117,13 @@ void PortalRenderer::_rghost_remove_from_rooms(uint32_t p_pool_id) {
 }
 
 void PortalRenderer::_occluder_remove_from_rooms(uint32_t p_pool_id) {
-	VSOccluder &occ = _occluder_pool[p_pool_id];
+	VSOccluder_Instance &occ = _occluder_instance_pool[p_pool_id];
 	if (_loaded && (occ.room_id != -1)) {
 		VSRoom &room = get_room(occ.room_id);
-		room.remove_occluder(p_pool_id);
+		bool res = room.remove_occluder(p_pool_id);
+		if (!res) {
+			WARN_PRINT_ONCE("OccluderInstance was not present in Room");
+		}
 	}
 }
 
@@ -467,22 +470,38 @@ void PortalRenderer::rghost_destroy(RGhostHandle p_handle) {
 	_rghost_pool.free(p_handle);
 }
 
-OccluderHandle PortalRenderer::occluder_create(VSOccluder::Type p_type) {
+OccluderInstanceHandle PortalRenderer::occluder_instance_create() {
 	uint32_t pool_id = 0;
-	VSOccluder *occ = _occluder_pool.request(pool_id);
+	VSOccluder_Instance *occ = _occluder_instance_pool.request(pool_id);
 	occ->create();
 
-	// specific type
-	occ->type = p_type;
-	CRASH_COND(p_type == VSOccluder::OT_UNDEFINED);
-
-	OccluderHandle handle = pool_id + 1;
+	OccluderInstanceHandle handle = pool_id + 1;
 	return handle;
 }
 
-void PortalRenderer::occluder_set_active(OccluderHandle p_handle, bool p_active) {
+void PortalRenderer::occluder_instance_link(OccluderInstanceHandle p_handle, OccluderResourceHandle p_resource_handle) {
 	p_handle--;
-	VSOccluder &occ = _occluder_pool[p_handle];
+	VSOccluder_Instance &occ = _occluder_instance_pool[p_handle];
+
+	// Unlink with any already linked, and destroy world resources
+	if (occ.resource_pool_id != UINT32_MAX) {
+		// Watch for bugs in future with the room within, this is not changed here,
+		// but could potentially be removed and re-added in future if we use sprawling.
+		occluder_instance_destroy(p_handle + 1, false);
+		occ.resource_pool_id = UINT32_MAX;
+	}
+
+	p_resource_handle--;
+	VSOccluder_Resource &res = VSG::scene->get_portal_resources().get_pool_occluder_resource(p_resource_handle);
+
+	occ.resource_pool_id = p_resource_handle;
+	occ.type = res.type;
+	occ.revision = 0;
+}
+
+void PortalRenderer::occluder_instance_set_active(OccluderInstanceHandle p_handle, bool p_active) {
+	p_handle--;
+	VSOccluder_Instance &occ = _occluder_instance_pool[p_handle];
 
 	if (occ.active == p_active) {
 		return;
@@ -493,18 +512,23 @@ void PortalRenderer::occluder_set_active(OccluderHandle p_handle, bool p_active)
 	occluder_refresh_room_within(p_handle);
 }
 
-void PortalRenderer::occluder_set_transform(OccluderHandle p_handle, const Transform &p_xform) {
+void PortalRenderer::occluder_instance_set_transform(OccluderInstanceHandle p_handle, const Transform &p_xform) {
 	p_handle--;
-	VSOccluder &occ = _occluder_pool[p_handle];
+	VSOccluder_Instance &occ = _occluder_instance_pool[p_handle];
 	occ.xform = p_xform;
 
 	// mark as dirty as the world space spheres will be out of date
-	occ.dirty = true;
+	occ.revision = 0;
+
+	// The room within is based on the xform, rather than the AABB so this
+	// should still work even though the world space transform is deferred.
+	// N.B. Occluders are a single room based on the center of the Occluder transform,
+	// this may need to be improved at a later date.
 	occluder_refresh_room_within(p_handle);
 }
 
 void PortalRenderer::occluder_refresh_room_within(uint32_t p_occluder_pool_id) {
-	VSOccluder &occ = _occluder_pool[p_occluder_pool_id];
+	VSOccluder_Instance &occ = _occluder_instance_pool[p_occluder_pool_id];
 
 	// if we aren't loaded, the room within can't be valid
 	if (!_loaded) {
@@ -547,156 +571,47 @@ void PortalRenderer::occluder_refresh_room_within(uint32_t p_occluder_pool_id) {
 	}
 }
 
-void PortalRenderer::occluder_update_mesh(OccluderHandle p_handle, const Geometry::OccluderMeshData &p_mesh_data) {
+void PortalRenderer::occluder_instance_destroy(OccluderInstanceHandle p_handle, bool p_free) {
 	p_handle--;
-	VSOccluder &occ = _occluder_pool[p_handle];
-	ERR_FAIL_COND(occ.type != VSOccluder::OT_MESH);
-
-	// needs world points updating next time
-	occ.dirty = true;
-
-	const LocalVectori<Geometry::OccluderMeshData::Face> &faces = p_mesh_data.faces;
-	const LocalVectori<Vector3> &vertices = p_mesh_data.vertices;
-
-	// first deal with the situation where the number of polys has changed (rare)
-	if (occ.list_ids.size() != faces.size()) {
-		// not the most efficient, but works...
-		// remove existing
-		for (int n = 0; n < occ.list_ids.size(); n++) {
-			uint32_t id = occ.list_ids[n];
-			_occluder_mesh_pool.free(id);
-		}
 
-		occ.list_ids.clear();
-		// create new
-		for (int n = 0; n < faces.size(); n++) {
-			uint32_t id;
-			VSOccluder_Mesh *poly = _occluder_mesh_pool.request(id);
-			poly->create();
-			occ.list_ids.push_back(id);
-		}
+	if (p_free) {
+		_occluder_remove_from_rooms(p_handle);
 	}
 
-	// new data
-	for (int n = 0; n < occ.list_ids.size(); n++) {
-		uint32_t id = occ.list_ids[n];
-
-		VSOccluder_Mesh &opoly = _occluder_mesh_pool[id];
-		Occlusion::PolyPlane &poly = opoly.poly_local;
-
-		// source face
-		const Geometry::OccluderMeshData::Face &face = faces[n];
-		opoly.two_way = face.two_way;
-
-		// make sure the number of holes is correct
-		if (face.holes.size() != opoly.num_holes) {
-			// slow but hey ho
-			// delete existing holes
-			for (int i = 0; i < opoly.num_holes; i++) {
-				_occluder_hole_pool.free(opoly.hole_pool_ids[i]);
-				opoly.hole_pool_ids[i] = UINT32_MAX;
-			}
-			// create any new holes
-			opoly.num_holes = face.holes.size();
-			for (int i = 0; i < opoly.num_holes; i++) {
-				uint32_t hole_id;
-				VSOccluder_Hole *hole = _occluder_hole_pool.request(hole_id);
-				opoly.hole_pool_ids[i] = hole_id;
-				hole->create();
-			}
-		}
-
-		poly.plane = face.plane;
-
-		poly.num_verts = MIN(face.indices.size(), Occlusion::PolyPlane::MAX_POLY_VERTS);
-
-		// make sure the world poly also has the correct num verts
-		opoly.poly_world.num_verts = poly.num_verts;
-
-		for (int c = 0; c < poly.num_verts; c++) {
-			int vert_index = face.indices[c];
-
-			if (vert_index < vertices.size()) {
-				poly.verts[c] = vertices[vert_index];
-			} else {
-				WARN_PRINT_ONCE("occluder_update_mesh : poly index out of range");
-			}
-		}
-
-		// holes
-		for (int h = 0; h < opoly.num_holes; h++) {
-			VSOccluder_Hole &dhole = get_pool_occluder_hole(opoly.hole_pool_ids[h]);
-			const Geometry::OccluderMeshData::Hole &shole = face.holes[h];
-
-			dhole.poly_local.num_verts = shole.indices.size();
-			dhole.poly_local.num_verts = MIN(dhole.poly_local.num_verts, Occlusion::Poly::MAX_POLY_VERTS);
-			dhole.poly_world.num_verts = dhole.poly_local.num_verts;
-
-			for (int c = 0; c < dhole.poly_local.num_verts; c++) {
-				int vert_index = shole.indices[c];
-				if (vert_index < vertices.size()) {
-					dhole.poly_local.verts[c] = vertices[vert_index];
-				} else {
-					WARN_PRINT_ONCE("occluder_update_mesh : hole index out of range");
-				}
-			}
-		}
-	}
-}
-
-void PortalRenderer::occluder_update_spheres(OccluderHandle p_handle, const Vector<Plane> &p_spheres) {
-	p_handle--;
-	VSOccluder &occ = _occluder_pool[p_handle];
-	ERR_FAIL_COND(occ.type != VSOccluder::OT_SPHERE);
-
-	// first deal with the situation where the number of spheres has changed (rare)
-	if (occ.list_ids.size() != p_spheres.size()) {
-		// not the most efficient, but works...
-		// remove existing
-		for (int n = 0; n < occ.list_ids.size(); n++) {
-			uint32_t id = occ.list_ids[n];
-			_occluder_sphere_pool.free(id);
-		}
-
-		occ.list_ids.clear();
-		// create new
-		for (int n = 0; n < p_spheres.size(); n++) {
-			uint32_t id;
-			VSOccluder_Sphere *sphere = _occluder_sphere_pool.request(id);
-			sphere->create();
-			occ.list_ids.push_back(id);
-		}
-	}
-
-	// new positions
-	for (int n = 0; n < occ.list_ids.size(); n++) {
-		uint32_t id = occ.list_ids[n];
-		VSOccluder_Sphere &sphere = _occluder_sphere_pool[id];
-		sphere.local.from_plane(p_spheres[n]);
-	}
-
-	// mark as dirty as the world space spheres will be out of date
-	occ.dirty = true;
-}
-
-void PortalRenderer::occluder_destroy(OccluderHandle p_handle) {
-	p_handle--;
-
 	// depending on the occluder type, remove the spheres etc
-	VSOccluder &occ = _occluder_pool[p_handle];
+	VSOccluder_Instance &occ = _occluder_instance_pool[p_handle];
 	switch (occ.type) {
-		case VSOccluder::OT_SPHERE: {
-			occluder_update_spheres(p_handle + 1, Vector<Plane>());
+		case VSOccluder_Instance::OT_SPHERE: {
+			// free any spheres owned by the occluder
+			for (int n = 0; n < occ.list_ids.size(); n++) {
+				uint32_t id = occ.list_ids[n];
+				_occluder_world_sphere_pool.free(id);
+			}
+			occ.list_ids.clear();
 		} break;
-		case VSOccluder::OT_MESH: {
-			occluder_update_mesh(p_handle + 1, Geometry::OccluderMeshData());
+		case VSOccluder_Instance::OT_MESH: {
+			// free any polys owned by the occluder
+			for (int n = 0; n < occ.list_ids.size(); n++) {
+				uint32_t id = occ.list_ids[n];
+				VSOccluder_Poly &poly = _occluder_world_poly_pool[id];
+
+				// free any holes owned by the poly
+				for (int h = 0; h < poly.num_holes; h++) {
+					_occluder_world_hole_pool.free(poly.hole_pool_ids[h]);
+				}
+				// blanks
+				poly.create();
+				_occluder_world_poly_pool.free(id);
+			}
+			occ.list_ids.clear();
 		} break;
 		default: {
 		} break;
 	}
 
-	_occluder_remove_from_rooms(p_handle);
-	_occluder_pool.free(p_handle);
+	if (p_free) {
+		_occluder_instance_pool.free(p_handle);
+	}
 }
 
 // Rooms
@@ -1047,9 +962,9 @@ void PortalRenderer::_load_finalize_roaming() {
 		rghost_update(_rghost_pool.get_active_id(n) + 1, aabb, true);
 	}
 
-	for (unsigned int n = 0; n < _occluder_pool.active_size(); n++) {
-		VSOccluder &occ = _occluder_pool.get_active(n);
-		int occluder_id = _occluder_pool.get_active_id(n);
+	for (unsigned int n = 0; n < _occluder_instance_pool.active_size(); n++) {
+		VSOccluder_Instance &occ = _occluder_instance_pool.get_active(n);
+		int occluder_id = _occluder_instance_pool.get_active_id(n);
 
 		// make sure occluder is in the correct room
 		occ.room_id = find_room_within(occ.pt_center, -1);

+ 136 - 45
servers/visual/portals/portal_renderer.h

@@ -38,6 +38,7 @@
 #include "core/vector.h"
 #include "portal_gameplay_monitor.h"
 #include "portal_pvs.h"
+#include "portal_resources.h"
 #include "portal_rooms_bsp.h"
 #include "portal_tracer.h"
 #include "portal_types.h"
@@ -187,12 +188,11 @@ public:
 	void rghost_destroy(RGhostHandle p_handle);
 
 	// occluders
-	OccluderHandle occluder_create(VSOccluder::Type p_type);
-	void occluder_update_spheres(OccluderHandle p_handle, const Vector<Plane> &p_spheres);
-	void occluder_update_mesh(OccluderHandle p_handle, const Geometry::OccluderMeshData &p_mesh_data);
-	void occluder_set_transform(OccluderHandle p_handle, const Transform &p_xform);
-	void occluder_set_active(OccluderHandle p_handle, bool p_active);
-	void occluder_destroy(OccluderHandle p_handle);
+	OccluderInstanceHandle occluder_instance_create();
+	void occluder_instance_link(OccluderInstanceHandle p_handle, OccluderResourceHandle p_resource_handle);
+	void occluder_instance_set_transform(OccluderInstanceHandle p_handle, const Transform &p_xform);
+	void occluder_instance_set_active(OccluderInstanceHandle p_handle, bool p_active);
+	void occluder_instance_destroy(OccluderInstanceHandle p_handle, bool p_free = true);
 
 	// editor only .. slow
 	Geometry::MeshData occlusion_debug_get_current_polys() const { return _tracer.get_occlusion_culler().debug_get_current_polys(); }
@@ -216,13 +216,13 @@ public:
 
 	int cull_convex_implementation(const Vector3 &p_point, const Vector3 &p_cam_dir, const CameraMatrix &p_cam_matrix, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_result_max, uint32_t p_mask, int32_t &r_previous_room_id_hint);
 
-	bool occlusion_is_active() const { return _occluder_pool.active_size() && use_occlusion_culling; }
+	bool occlusion_is_active() const { return _occluder_instance_pool.active_size() && use_occlusion_culling; }
 
 	// special function for occlusion culling only that does not use portals / rooms,
 	// but allows using occluders with the main scene
 	int occlusion_cull(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results) {
 		// inactive?
-		if (!_occluder_pool.active_size() || !use_occlusion_culling) {
+		if (!_occluder_instance_pool.active_size() || !use_occlusion_culling) {
 			return p_num_results;
 		}
 
@@ -257,14 +257,6 @@ public:
 	RGhost &get_pool_rghost(uint32_t p_pool_id) { return _rghost_pool[p_pool_id]; }
 	const RGhost &get_pool_rghost(uint32_t p_pool_id) const { return _rghost_pool[p_pool_id]; }
 
-	const LocalVector<uint32_t, uint32_t> &get_occluders_active_list() const { return _occluder_pool.get_active_list(); }
-	const VSOccluder &get_pool_occluder(uint32_t p_pool_id) const { return _occluder_pool[p_pool_id]; }
-	VSOccluder &get_pool_occluder(uint32_t p_pool_id) { return _occluder_pool[p_pool_id]; }
-	const VSOccluder_Sphere &get_pool_occluder_sphere(uint32_t p_pool_id) const { return _occluder_sphere_pool[p_pool_id]; }
-	const VSOccluder_Mesh &get_pool_occluder_mesh(uint32_t p_pool_id) const { return _occluder_mesh_pool[p_pool_id]; }
-	const VSOccluder_Hole &get_pool_occluder_hole(uint32_t p_pool_id) const { return _occluder_hole_pool[p_pool_id]; }
-	VSOccluder_Hole &get_pool_occluder_hole(uint32_t p_pool_id) { return _occluder_hole_pool[p_pool_id]; }
-
 	VSStaticGhost &get_static_ghost(uint32_t p_id) { return _static_ghosts[p_id]; }
 
 	VSRoomGroup &get_roomgroup(uint32_t p_pool_id) { return _roomgroup_pool[p_pool_id]; }
@@ -274,6 +266,15 @@ public:
 
 	bool get_cull_using_pvs() const { return _cull_using_pvs; }
 
+	// occluders
+	const LocalVector<uint32_t, uint32_t> &get_occluders_active_list() const { return _occluder_instance_pool.get_active_list(); }
+	const VSOccluder_Instance &get_pool_occluder_instance(uint32_t p_pool_id) const { return _occluder_instance_pool[p_pool_id]; }
+	VSOccluder_Instance &get_pool_occluder_instance(uint32_t p_pool_id) { return _occluder_instance_pool[p_pool_id]; }
+	const VSOccluder_Sphere &get_pool_occluder_world_sphere(uint32_t p_pool_id) const { return _occluder_world_sphere_pool[p_pool_id]; }
+	const VSOccluder_Poly &get_pool_occluder_world_poly(uint32_t p_pool_id) const { return _occluder_world_poly_pool[p_pool_id]; }
+	const VSOccluder_Hole &get_pool_occluder_world_hole(uint32_t p_pool_id) const { return _occluder_world_hole_pool[p_pool_id]; }
+	VSOccluder_Hole &get_pool_occluder_world_hole(uint32_t p_pool_id) { return _occluder_world_hole_pool[p_pool_id]; }
+
 private:
 	int find_room_within(const Vector3 &p_pos, int p_previous_room_id = -1) {
 		return _rooms_lookup_bsp.find_room_within(*this, p_pos, p_previous_room_id);
@@ -318,10 +319,10 @@ private:
 	LocalVector<uint32_t, int32_t> _moving_list_roaming;
 
 	// occluders
-	TrackedPooledList<VSOccluder> _occluder_pool;
-	TrackedPooledList<VSOccluder_Sphere, uint32_t, true> _occluder_sphere_pool;
-	TrackedPooledList<VSOccluder_Mesh, uint32_t, true> _occluder_mesh_pool;
-	TrackedPooledList<VSOccluder_Hole, uint32_t, true> _occluder_hole_pool;
+	TrackedPooledList<VSOccluder_Instance> _occluder_instance_pool;
+	TrackedPooledList<VSOccluder_Sphere, uint32_t, true> _occluder_world_sphere_pool;
+	TrackedPooledList<VSOccluder_Poly, uint32_t, true> _occluder_world_poly_pool;
+	TrackedPooledList<VSOccluder_Hole, uint32_t, true> _occluder_world_hole_pool;
 
 	PVS _pvs;
 
@@ -353,16 +354,47 @@ public:
 	static String _rid_to_string(RID p_rid);
 	static String _addr_to_string(const void *p_addr);
 
-	void occluder_ensure_up_to_date_sphere(VSOccluder &r_occluder);
-	void occluder_ensure_up_to_date_polys(VSOccluder &r_occluder);
+	void occluder_ensure_up_to_date_sphere(const PortalResources &p_resources, VSOccluder_Instance &r_occluder);
+	void occluder_ensure_up_to_date_polys(const PortalResources &p_resources, VSOccluder_Instance &r_occluder);
 	void occluder_refresh_room_within(uint32_t p_occluder_pool_id);
 };
 
-inline void PortalRenderer::occluder_ensure_up_to_date_sphere(VSOccluder &r_occluder) {
-	if (!r_occluder.dirty) {
+inline void PortalRenderer::occluder_ensure_up_to_date_sphere(const PortalResources &p_resources, VSOccluder_Instance &r_occluder) {
+	// occluder is not bound to a resource, cannot be used
+	if (r_occluder.resource_pool_id == UINT32_MAX) {
 		return;
 	}
-	r_occluder.dirty = false;
+
+	// get the resource
+	const VSOccluder_Resource &res = p_resources.get_pool_occluder_resource(r_occluder.resource_pool_id);
+
+	// dirty?
+	if (r_occluder.revision == res.revision) {
+		return;
+	}
+	r_occluder.revision = res.revision;
+
+	// must be same type, if not an error has occurred
+	ERR_FAIL_COND(res.type != r_occluder.type);
+
+	// first make sure the instance has the correct number of world space spheres
+	if (r_occluder.list_ids.size() != res.list_ids.size()) {
+		// not the most efficient, but works...
+		// remove existing
+		for (int n = 0; n < r_occluder.list_ids.size(); n++) {
+			uint32_t id = r_occluder.list_ids[n];
+			_occluder_world_sphere_pool.free(id);
+		}
+
+		r_occluder.list_ids.clear();
+		// create new
+		for (int n = 0; n < res.list_ids.size(); n++) {
+			uint32_t id;
+			VSOccluder_Sphere *sphere = _occluder_world_sphere_pool.request(id);
+			sphere->create();
+			r_occluder.list_ids.push_back(id);
+		}
+	}
 
 	const Transform &tr = r_occluder.xform;
 
@@ -375,15 +407,16 @@ inline void PortalRenderer::occluder_ensure_up_to_date_sphere(VSOccluder &r_occl
 
 	// transform spheres
 	for (int n = 0; n < r_occluder.list_ids.size(); n++) {
-		uint32_t pool_id = r_occluder.list_ids[n];
-		VSOccluder_Sphere &osphere = _occluder_sphere_pool[pool_id];
+		uint32_t world_pool_id = r_occluder.list_ids[n];
+		VSOccluder_Sphere &world_osphere = _occluder_world_sphere_pool[world_pool_id];
+		const VSOccluder_Sphere &local_osphere = p_resources.get_pool_occluder_local_sphere(res.list_ids[n]);
 
-		osphere.world.pos = tr.xform(osphere.local.pos);
-		osphere.world.radius = osphere.local.radius * scale;
+		world_osphere.pos = tr.xform(local_osphere.pos);
+		world_osphere.radius = local_osphere.radius * scale;
 
-		Vector3 bradius = Vector3(osphere.world.radius, osphere.world.radius, osphere.world.radius);
-		Vector3 bmin = osphere.world.pos - bradius;
-		Vector3 bmax = osphere.world.pos + bradius;
+		Vector3 bradius = Vector3(world_osphere.radius, world_osphere.radius, world_osphere.radius);
+		Vector3 bmin = world_osphere.pos - bradius;
+		Vector3 bmax = world_osphere.pos + bradius;
 
 		bb_min.x = MIN(bb_min.x, bmin.x);
 		bb_min.y = MIN(bb_min.y, bmin.y);
@@ -397,33 +430,91 @@ inline void PortalRenderer::occluder_ensure_up_to_date_sphere(VSOccluder &r_occl
 	r_occluder.aabb.size = bb_max - bb_min;
 }
 
-inline void PortalRenderer::occluder_ensure_up_to_date_polys(VSOccluder &r_occluder) {
-	if (!r_occluder.dirty) {
+inline void PortalRenderer::occluder_ensure_up_to_date_polys(const PortalResources &p_resources, VSOccluder_Instance &r_occluder) {
+	// occluder is not bound to a resource, cannot be used
+	if (r_occluder.resource_pool_id == UINT32_MAX) {
 		return;
 	}
-	r_occluder.dirty = false;
+
+	// get the resource
+	const VSOccluder_Resource &res = p_resources.get_pool_occluder_resource(r_occluder.resource_pool_id);
+
+	// dirty?
+	if (r_occluder.revision == res.revision) {
+		return;
+	}
+	r_occluder.revision = res.revision;
+
+	// must be same type, if not an error has occurred
+	ERR_FAIL_COND(res.type != r_occluder.type);
+
+	// first make sure the instance has the correct number of world space spheres
+	if (r_occluder.list_ids.size() != res.list_ids.size()) {
+		// not the most efficient, but works...
+		// remove existing
+		for (int n = 0; n < r_occluder.list_ids.size(); n++) {
+			uint32_t id = r_occluder.list_ids[n];
+			_occluder_world_poly_pool.free(id);
+		}
+
+		r_occluder.list_ids.clear();
+		// create new
+		for (int n = 0; n < res.list_ids.size(); n++) {
+			uint32_t id;
+			VSOccluder_Poly *poly = _occluder_world_poly_pool.request(id);
+			poly->create();
+			r_occluder.list_ids.push_back(id);
+		}
+	}
 
 	const Transform &tr = r_occluder.xform;
 
 	for (int n = 0; n < r_occluder.list_ids.size(); n++) {
-		uint32_t pool_id = r_occluder.list_ids[n];
+		uint32_t world_pool_id = r_occluder.list_ids[n];
+		uint32_t local_pool_id = res.list_ids[n];
 
-		VSOccluder_Mesh &opoly = _occluder_mesh_pool[pool_id];
+		VSOccluder_Poly &world_opoly = _occluder_world_poly_pool[world_pool_id];
+		const VSOccluder_Poly &local_opoly = p_resources._occluder_local_poly_pool[local_pool_id];
 
-		for (int i = 0; i < opoly.poly_local.num_verts; i++) {
-			opoly.poly_world.verts[i] = tr.xform(opoly.poly_local.verts[i]);
+		world_opoly.poly.num_verts = local_opoly.poly.num_verts;
+		world_opoly.two_way = local_opoly.two_way;
+
+		for (int i = 0; i < local_opoly.poly.num_verts; i++) {
+			world_opoly.poly.verts[i] = tr.xform(local_opoly.poly.verts[i]);
 		}
 
-		opoly.poly_world.plane = tr.xform(opoly.poly_local.plane);
+		world_opoly.poly.plane = tr.xform(local_opoly.poly.plane);
+
+		// number of holes must be correct for each poly
+		if (world_opoly.num_holes != local_opoly.num_holes) {
+			// remove existing
+			for (int h = 0; h < world_opoly.num_holes; h++) {
+				uint32_t id = world_opoly.hole_pool_ids[h];
+				_occluder_world_hole_pool.free(id);
+				// not strictly necessary
+				world_opoly.hole_pool_ids[h] = UINT32_MAX;
+			}
+
+			world_opoly.num_holes = local_opoly.num_holes;
+			for (int h = 0; h < world_opoly.num_holes; h++) {
+				uint32_t id;
+				VSOccluder_Hole *hole = _occluder_world_hole_pool.request(id);
+				hole->create();
+				world_opoly.hole_pool_ids[h] = id;
+			}
+		}
 
 		// holes
-		for (int h = 0; h < opoly.num_holes; h++) {
-			uint32_t hid = opoly.hole_pool_ids[h];
+		for (int h = 0; h < world_opoly.num_holes; h++) {
+			uint32_t world_hid = world_opoly.hole_pool_ids[h];
+			uint32_t local_hid = local_opoly.hole_pool_ids[h];
+			VSOccluder_Hole &world_hole = _occluder_world_hole_pool[world_hid];
+			const VSOccluder_Hole &local_hole = p_resources._occluder_local_hole_pool[local_hid];
 
-			VSOccluder_Hole &hole = _occluder_hole_pool[hid];
+			world_hole.num_verts = local_hole.num_verts;
 
-			for (int i = 0; i < hole.poly_local.num_verts; i++) {
-				hole.poly_world.verts[i] = tr.xform(hole.poly_local.verts[i]);
+			for (int i = 0; i < world_hole.num_verts; i++) {
+				world_hole.verts[i] = tr.xform(local_hole.verts[i]);
 			}
 		}
 	}

+ 216 - 0
servers/visual/portals/portal_resources.cpp

@@ -0,0 +1,216 @@
+/*************************************************************************/
+/*  portal_resources.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 "portal_resources.h"
+
+OccluderResourceHandle PortalResources::occluder_resource_create() {
+	uint32_t pool_id = 0;
+	VSOccluder_Resource *occ = _occluder_resource_pool.request(pool_id);
+	occ->create();
+
+	OccluderResourceHandle handle = pool_id + 1;
+	return handle;
+}
+
+void PortalResources::occluder_resource_destroy(OccluderResourceHandle p_handle) {
+	p_handle--;
+
+	// Depending on the occluder resource type, remove the spheres, polys, holes etc
+	// We can reuse the update methods for this.
+	VSOccluder_Resource &occ = _occluder_resource_pool[p_handle];
+	switch (occ.type) {
+		case VSOccluder_Instance::OT_SPHERE: {
+			occluder_resource_update_spheres(p_handle + 1, Vector<Plane>());
+		} break;
+		case VSOccluder_Instance::OT_MESH: {
+			occluder_resource_update_mesh(p_handle + 1, Geometry::OccluderMeshData());
+		} break;
+		default: {
+		} break;
+	}
+
+	// This also clears the occluder
+	occ.create();
+
+	_occluder_resource_pool.free(p_handle);
+}
+
+void PortalResources::occluder_resource_prepare(OccluderResourceHandle p_handle, VSOccluder_Instance::Type p_type) {
+	p_handle--;
+
+	// depending on the occluder type, remove the spheres etc
+	VSOccluder_Resource &occ = _occluder_resource_pool[p_handle];
+
+	if (occ.type != VSOccluder_Instance::OT_UNDEFINED) {
+		ERR_PRINT_ONCE("occluder_resource_prepare should be called only once.");
+	}
+
+	occ.type = p_type;
+	ERR_FAIL_COND(p_type == VSOccluder_Instance::OT_UNDEFINED);
+}
+
+void PortalResources::occluder_resource_update_spheres(OccluderResourceHandle p_handle, const Vector<Plane> &p_spheres) {
+	p_handle--;
+	VSOccluder_Resource &occ = _occluder_resource_pool[p_handle];
+	ERR_FAIL_COND(occ.type != VSOccluder_Resource::OT_SPHERE);
+
+	// first deal with the situation where the number of spheres has changed (rare)
+	if (occ.list_ids.size() != p_spheres.size()) {
+		// not the most efficient, but works...
+		// remove existing
+		for (int n = 0; n < occ.list_ids.size(); n++) {
+			uint32_t id = occ.list_ids[n];
+			_occluder_local_sphere_pool.free(id);
+		}
+
+		occ.list_ids.clear();
+		// create new
+		for (int n = 0; n < p_spheres.size(); n++) {
+			uint32_t id;
+			VSOccluder_Sphere *sphere = _occluder_local_sphere_pool.request(id);
+			sphere->create();
+			occ.list_ids.push_back(id);
+		}
+	}
+
+	// new positions
+	for (int n = 0; n < occ.list_ids.size(); n++) {
+		uint32_t id = occ.list_ids[n];
+		VSOccluder_Sphere &sphere = _occluder_local_sphere_pool[id];
+		sphere.from_plane(p_spheres[n]);
+	}
+
+	// mark as dirty as the world space spheres will be out of date next time this resource is used
+	occ.revision += 1;
+}
+
+void PortalResources::occluder_resource_update_mesh(OccluderResourceHandle p_handle, const Geometry::OccluderMeshData &p_mesh_data) {
+	p_handle--;
+	VSOccluder_Resource &occ = _occluder_resource_pool[p_handle];
+	ERR_FAIL_COND(occ.type != VSOccluder_Resource::OT_MESH);
+
+	// mark as dirty, needs world points updating next time this resource is used
+	occ.revision += 1;
+
+	const LocalVectori<Geometry::OccluderMeshData::Face> &faces = p_mesh_data.faces;
+	const LocalVectori<Vector3> &vertices = p_mesh_data.vertices;
+
+	// first deal with the situation where the number of polys has changed (rare)
+	if (occ.list_ids.size() != faces.size()) {
+		// not the most efficient, but works...
+		// remove existing
+		for (int n = 0; n < occ.list_ids.size(); n++) {
+			uint32_t id = occ.list_ids[n];
+
+			// must also free the holes
+			VSOccluder_Poly &opoly = _occluder_local_poly_pool[id];
+			for (int h = 0; h < opoly.num_holes; h++) {
+				_occluder_local_hole_pool.free(opoly.hole_pool_ids[h]);
+
+				// perhaps debug only
+				opoly.hole_pool_ids[h] = UINT32_MAX;
+			}
+
+			_occluder_local_poly_pool.free(id);
+		}
+
+		occ.list_ids.clear();
+		// create new
+		for (int n = 0; n < faces.size(); n++) {
+			uint32_t id;
+			VSOccluder_Poly *poly = _occluder_local_poly_pool.request(id);
+			poly->create();
+			occ.list_ids.push_back(id);
+		}
+	}
+
+	// new data
+	for (int n = 0; n < occ.list_ids.size(); n++) {
+		uint32_t id = occ.list_ids[n];
+
+		VSOccluder_Poly &opoly = _occluder_local_poly_pool[id];
+		Occlusion::PolyPlane &poly = opoly.poly;
+
+		// source face
+		const Geometry::OccluderMeshData::Face &face = faces[n];
+		opoly.two_way = face.two_way;
+
+		// make sure the number of holes is correct
+		if (face.holes.size() != opoly.num_holes) {
+			// slow but hey ho
+			// delete existing holes
+			for (int i = 0; i < opoly.num_holes; i++) {
+				_occluder_local_hole_pool.free(opoly.hole_pool_ids[i]);
+				opoly.hole_pool_ids[i] = UINT32_MAX;
+			}
+			// create any new holes
+			opoly.num_holes = face.holes.size();
+			for (int i = 0; i < opoly.num_holes; i++) {
+				uint32_t hole_id;
+				VSOccluder_Hole *hole = _occluder_local_hole_pool.request(hole_id);
+				opoly.hole_pool_ids[i] = hole_id;
+				hole->create();
+			}
+		}
+
+		// set up the poly basics, plane and verts
+		poly.plane = face.plane;
+		poly.num_verts = MIN(face.indices.size(), Occlusion::PolyPlane::MAX_POLY_VERTS);
+
+		for (int c = 0; c < poly.num_verts; c++) {
+			int vert_index = face.indices[c];
+
+			if (vert_index < vertices.size()) {
+				poly.verts[c] = vertices[vert_index];
+			} else {
+				WARN_PRINT_ONCE("occluder_update_mesh : poly index out of range");
+			}
+		}
+
+		// set up any holes that are present
+		for (int h = 0; h < opoly.num_holes; h++) {
+			VSOccluder_Hole &dhole = get_pool_occluder_local_hole(opoly.hole_pool_ids[h]);
+			const Geometry::OccluderMeshData::Hole &shole = face.holes[h];
+
+			dhole.num_verts = shole.indices.size();
+			dhole.num_verts = MIN(dhole.num_verts, Occlusion::Poly::MAX_POLY_VERTS);
+
+			for (int c = 0; c < dhole.num_verts; c++) {
+				int vert_index = shole.indices[c];
+				if (vert_index < vertices.size()) {
+					dhole.verts[c] = vertices[vert_index];
+				} else {
+					WARN_PRINT_ONCE("occluder_update_mesh : hole index out of range");
+				}
+			} // for c through hole verts
+		} // for h through holes
+
+	} // for n through occluders
+}

+ 68 - 0
servers/visual/portals/portal_resources.h

@@ -0,0 +1,68 @@
+/*************************************************************************/
+/*  portal_resources.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 PORTAL_RESOURCES_H
+#define PORTAL_RESOURCES_H
+
+#include "core/math/geometry.h"
+#include "portal_types.h"
+
+// Although the portal renderer is owned by a scenario,
+// resources are not associated with a scenario and can be shared
+// potentially across multiple scenarios. They must therefore be held in
+// some form of global.
+
+class PortalResources {
+	friend class PortalRenderer;
+
+public:
+	OccluderResourceHandle occluder_resource_create();
+	void occluder_resource_prepare(OccluderResourceHandle p_handle, VSOccluder_Instance::Type p_type);
+	void occluder_resource_update_spheres(OccluderResourceHandle p_handle, const Vector<Plane> &p_spheres);
+	void occluder_resource_update_mesh(OccluderResourceHandle p_handle, const Geometry::OccluderMeshData &p_mesh_data);
+	void occluder_resource_destroy(OccluderResourceHandle p_handle);
+
+	const VSOccluder_Resource &get_pool_occluder_resource(uint32_t p_pool_id) const { return _occluder_resource_pool[p_pool_id]; }
+	VSOccluder_Resource &get_pool_occluder_resource(uint32_t p_pool_id) { return _occluder_resource_pool[p_pool_id]; }
+
+	// Local space is shared resources
+	const VSOccluder_Sphere &get_pool_occluder_local_sphere(uint32_t p_pool_id) const { return _occluder_local_sphere_pool[p_pool_id]; }
+	const VSOccluder_Poly &get_pool_occluder_local_poly(uint32_t p_pool_id) const { return _occluder_local_poly_pool[p_pool_id]; }
+	const VSOccluder_Hole &get_pool_occluder_local_hole(uint32_t p_pool_id) const { return _occluder_local_hole_pool[p_pool_id]; }
+	VSOccluder_Hole &get_pool_occluder_local_hole(uint32_t p_pool_id) { return _occluder_local_hole_pool[p_pool_id]; }
+
+private:
+	TrackedPooledList<VSOccluder_Resource> _occluder_resource_pool;
+	TrackedPooledList<VSOccluder_Sphere, uint32_t, true> _occluder_local_sphere_pool;
+	TrackedPooledList<VSOccluder_Poly, uint32_t, true> _occluder_local_poly_pool;
+	TrackedPooledList<VSOccluder_Hole, uint32_t, true> _occluder_local_hole_pool;
+};
+
+#endif // PORTAL_RESOURCES_H

+ 33 - 31
servers/visual/portals/portal_types.h

@@ -55,7 +55,8 @@ typedef uint32_t RoomHandle;
 typedef uint32_t RoomGroupHandle;
 typedef uint32_t OcclusionHandle;
 typedef uint32_t RGhostHandle;
-typedef uint32_t OccluderHandle;
+typedef uint32_t OccluderInstanceHandle;
+typedef uint32_t OccluderResourceHandle;
 
 struct VSPortal {
 	enum ClipResult {
@@ -380,12 +381,12 @@ struct VSRoom {
 	LocalVector<uint32_t, int32_t> _roomgroup_ids;
 };
 
-struct VSOccluder {
+// Possibly shared data, in local space
+struct VSOccluder_Resource {
 	void create() {
 		type = OT_UNDEFINED;
-		room_id = -1;
-		dirty = false;
-		active = true;
+		revision = 0;
+		list_ids.clear();
 	}
 
 	// these should match the values in VisualServer::OccluderType
@@ -396,6 +397,28 @@ struct VSOccluder {
 		OT_NUM_TYPES,
 	} type;
 
+	// If the revision of the instance and the resource don't match,
+	// then the local versions have been updated and need transforming
+	// to world space in the instance (i.e. it is dirty)
+	uint32_t revision;
+
+	// ids of multiple objects in the appropriate occluder pool:
+	// local space for resources, and world space for occluder instances
+	LocalVector<uint32_t, int32_t> list_ids;
+};
+
+struct VSOccluder_Instance : public VSOccluder_Resource {
+	void create() {
+		VSOccluder_Resource::create();
+		room_id = -1;
+		active = true;
+		resource_pool_id = UINT32_MAX;
+	}
+
+	// Occluder instance can be bound to one resource (which will include data in local space)
+	// This should be set back to NULL if the resource is deleted
+	uint32_t resource_pool_id;
+
 	// which is the primary room this group of occluders is in
 	// (it may sprawl into multiple rooms)
 	int32_t room_id;
@@ -409,14 +432,8 @@ struct VSOccluder {
 	// global xform
 	Transform xform;
 
-	// whether world space need calculating
-	bool dirty;
-
 	// controlled by the visible flag on the occluder
 	bool active;
-
-	// ids of multiple objects in the appropriate occluder pool
-	LocalVector<uint32_t, int32_t> list_ids;
 };
 
 namespace Occlusion {
@@ -472,42 +489,27 @@ struct PolyPlane : public Poly {
 
 } // namespace Occlusion
 
-struct VSOccluder_Sphere {
-	void create() {
-		local.create();
-		world.create();
-	}
-
-	Occlusion::Sphere local;
-	Occlusion::Sphere world;
+struct VSOccluder_Sphere : public Occlusion::Sphere {
 };
 
-struct VSOccluder_Mesh {
+struct VSOccluder_Poly {
 	static const int MAX_POLY_HOLES = PortalDefines::OCCLUSION_POLY_MAX_HOLES;
 	void create() {
-		poly_local.create();
-		poly_world.create();
+		poly.create();
 		num_holes = 0;
 		two_way = false;
 		for (int n = 0; n < MAX_POLY_HOLES; n++) {
 			hole_pool_ids[n] = UINT32_MAX;
 		}
 	}
-	Occlusion::PolyPlane poly_local;
-	Occlusion::PolyPlane poly_world;
+	Occlusion::PolyPlane poly;
 	bool two_way;
 
 	int num_holes;
 	uint32_t hole_pool_ids[MAX_POLY_HOLES];
 };
 
-struct VSOccluder_Hole {
-	void create() {
-		poly_local.create();
-		poly_world.create();
-	}
-	Occlusion::Poly poly_local;
-	Occlusion::Poly poly_world;
+struct VSOccluder_Hole : public Occlusion::Poly {
 };
 
 #endif

+ 10 - 6
servers/visual/visual_server_raster.h

@@ -603,12 +603,16 @@ public:
 	BIND2(roomgroup_add_room, RID, RID)
 
 	// Occluders
-	BIND0R(RID, occluder_create)
-	BIND3(occluder_set_scenario, RID, RID, OccluderType)
-	BIND2(occluder_spheres_update, RID, const Vector<Plane> &)
-	BIND2(occluder_mesh_update, RID, const Geometry::OccluderMeshData &)
-	BIND2(occluder_set_transform, RID, const Transform &)
-	BIND2(occluder_set_active, RID, bool)
+	BIND0R(RID, occluder_instance_create)
+	BIND2(occluder_instance_set_scenario, RID, RID)
+	BIND2(occluder_instance_link_resource, RID, RID)
+	BIND2(occluder_instance_set_transform, RID, const Transform &)
+	BIND2(occluder_instance_set_active, RID, bool)
+
+	BIND0R(RID, occluder_resource_create)
+	BIND2(occluder_resource_prepare, RID, OccluderType)
+	BIND2(occluder_resource_spheres_update, RID, const Vector<Plane> &)
+	BIND2(occluder_resource_mesh_update, RID, const Geometry::OccluderMeshData &)
 	BIND1(set_use_occlusion_culling, bool)
 	BIND1RC(Geometry::MeshData, occlusion_debug_get_current_polys, RID)
 

+ 67 - 39
servers/visual/visual_server_scene.cpp

@@ -1517,65 +1517,88 @@ void VisualServerScene::roomgroup_add_room(RID p_roomgroup, RID p_room) {
 }
 
 // Occluders
-RID VisualServerScene::occluder_create() {
-	Occluder *ro = memnew(Occluder);
+RID VisualServerScene::occluder_instance_create() {
+	OccluderInstance *ro = memnew(OccluderInstance);
 	ERR_FAIL_COND_V(!ro, RID());
-	RID occluder_rid = occluder_owner.make_rid(ro);
+	RID occluder_rid = occluder_instance_owner.make_rid(ro);
 	return occluder_rid;
 }
 
-void VisualServerScene::occluder_set_scenario(RID p_occluder, RID p_scenario, VisualServer::OccluderType p_type) {
-	Occluder *ro = occluder_owner.getornull(p_occluder);
-	ERR_FAIL_COND(!ro);
+void VisualServerScene::occluder_instance_link_resource(RID p_occluder_instance, RID p_occluder_resource) {
+	OccluderInstance *oi = occluder_instance_owner.getornull(p_occluder_instance);
+	ERR_FAIL_COND(!oi);
+	ERR_FAIL_COND(!oi->scenario);
+
+	OccluderResource *res = occluder_resource_owner.getornull(p_occluder_resource);
+	ERR_FAIL_COND(!res);
+
+	oi->scenario->_portal_renderer.occluder_instance_link(oi->scenario_occluder_id, res->occluder_resource_id);
+}
+
+void VisualServerScene::occluder_instance_set_scenario(RID p_occluder_instance, RID p_scenario) {
+	OccluderInstance *oi = occluder_instance_owner.getornull(p_occluder_instance);
+	ERR_FAIL_COND(!oi);
 	Scenario *scenario = scenario_owner.getornull(p_scenario);
 
 	// noop?
-	if (ro->scenario == scenario) {
+	if (oi->scenario == scenario) {
 		return;
 	}
 
 	// if the portal is in a scenario already, remove it
-	if (ro->scenario) {
-		ro->scenario->_portal_renderer.occluder_destroy(ro->scenario_occluder_id);
-		ro->scenario = nullptr;
-		ro->scenario_occluder_id = 0;
+	if (oi->scenario) {
+		oi->scenario->_portal_renderer.occluder_instance_destroy(oi->scenario_occluder_id);
+		oi->scenario = nullptr;
+		oi->scenario_occluder_id = 0;
 	}
 
 	// create when entering the world
 	if (scenario) {
-		ro->scenario = scenario;
-
-		// defer the actual creation to here
-		ro->scenario_occluder_id = scenario->_portal_renderer.occluder_create((VSOccluder::Type)p_type);
+		oi->scenario = scenario;
+		oi->scenario_occluder_id = scenario->_portal_renderer.occluder_instance_create();
 	}
 }
 
-void VisualServerScene::occluder_set_active(RID p_occluder, bool p_active) {
-	Occluder *ro = occluder_owner.getornull(p_occluder);
-	ERR_FAIL_COND(!ro);
-	ERR_FAIL_COND(!ro->scenario);
-	ro->scenario->_portal_renderer.occluder_set_active(ro->scenario_occluder_id, p_active);
+void VisualServerScene::occluder_instance_set_active(RID p_occluder_instance, bool p_active) {
+	OccluderInstance *oi = occluder_instance_owner.getornull(p_occluder_instance);
+	ERR_FAIL_COND(!oi);
+	ERR_FAIL_COND(!oi->scenario);
+	oi->scenario->_portal_renderer.occluder_instance_set_active(oi->scenario_occluder_id, p_active);
+}
+
+void VisualServerScene::occluder_instance_set_transform(RID p_occluder_instance, const Transform &p_xform) {
+	OccluderInstance *oi = occluder_instance_owner.getornull(p_occluder_instance);
+	ERR_FAIL_COND(!oi);
+	ERR_FAIL_COND(!oi->scenario);
+	oi->scenario->_portal_renderer.occluder_instance_set_transform(oi->scenario_occluder_id, p_xform);
+}
+
+RID VisualServerScene::occluder_resource_create() {
+	OccluderResource *res = memnew(OccluderResource);
+	ERR_FAIL_COND_V(!res, RID());
+
+	res->occluder_resource_id = _portal_resources.occluder_resource_create();
+
+	RID occluder_resource_rid = occluder_resource_owner.make_rid(res);
+	return occluder_resource_rid;
 }
 
-void VisualServerScene::occluder_set_transform(RID p_occluder, const Transform &p_xform) {
-	Occluder *ro = occluder_owner.getornull(p_occluder);
-	ERR_FAIL_COND(!ro);
-	ERR_FAIL_COND(!ro->scenario);
-	ro->scenario->_portal_renderer.occluder_set_transform(ro->scenario_occluder_id, p_xform);
+void VisualServerScene::occluder_resource_prepare(RID p_occluder_resource, VisualServer::OccluderType p_type) {
+	OccluderResource *res = occluder_resource_owner.getornull(p_occluder_resource);
+	ERR_FAIL_COND(!res);
+	_portal_resources.occluder_resource_prepare(res->occluder_resource_id, (VSOccluder_Instance::Type)p_type);
 }
 
-void VisualServerScene::occluder_spheres_update(RID p_occluder, const Vector<Plane> &p_spheres) {
-	Occluder *ro = occluder_owner.getornull(p_occluder);
-	ERR_FAIL_COND(!ro);
-	ERR_FAIL_COND(!ro->scenario);
-	ro->scenario->_portal_renderer.occluder_update_spheres(ro->scenario_occluder_id, p_spheres);
+void VisualServerScene::occluder_resource_spheres_update(RID p_occluder_resource, const Vector<Plane> &p_spheres) {
+	OccluderResource *res = occluder_resource_owner.getornull(p_occluder_resource);
+	ERR_FAIL_COND(!res);
+	_portal_resources.occluder_resource_update_spheres(res->occluder_resource_id, p_spheres);
 }
 
-void VisualServerScene::occluder_mesh_update(RID p_occluder, const Geometry::OccluderMeshData &p_mesh_data) {
-	Occluder *ro = occluder_owner.getornull(p_occluder);
-	ERR_FAIL_COND(!ro);
-	ERR_FAIL_COND(!ro->scenario);
-	ro->scenario->_portal_renderer.occluder_update_mesh(ro->scenario_occluder_id, p_mesh_data);
+void VisualServerScene::occluder_resource_mesh_update(RID p_occluder_resource, const Geometry::OccluderMeshData &p_mesh_data) {
+	OccluderResource *res = occluder_resource_owner.getornull(p_occluder_resource);
+	ERR_FAIL_COND(!res);
+	_portal_resources.occluder_resource_update_mesh(res->occluder_resource_id, p_mesh_data);
 }
 
 void VisualServerScene::set_use_occlusion_culling(bool p_enable) {
@@ -4505,10 +4528,15 @@ bool VisualServerScene::free(RID p_rid) {
 		RoomGroup *roomgroup = roomgroup_owner.get(p_rid);
 		roomgroup_owner.free(p_rid);
 		memdelete(roomgroup);
-	} else if (occluder_owner.owns(p_rid)) {
-		Occluder *ro = occluder_owner.get(p_rid);
-		occluder_owner.free(p_rid);
-		memdelete(ro);
+	} else if (occluder_instance_owner.owns(p_rid)) {
+		OccluderInstance *occ_inst = occluder_instance_owner.get(p_rid);
+		occluder_instance_owner.free(p_rid);
+		memdelete(occ_inst);
+	} else if (occluder_resource_owner.owns(p_rid)) {
+		OccluderResource *occ_res = occluder_resource_owner.get(p_rid);
+		occ_res->destroy(_portal_resources);
+		occluder_resource_owner.free(p_rid);
+		memdelete(occ_res);
 	} else {
 		return false;
 	}

+ 30 - 11
servers/visual/visual_server_scene.h

@@ -729,29 +729,47 @@ public:
 	virtual void roomgroup_add_room(RID p_roomgroup, RID p_room);
 
 	// Occluders
-	struct Occluder : RID_Data {
+	struct OccluderInstance : RID_Data {
 		uint32_t scenario_occluder_id = 0;
 		Scenario *scenario = nullptr;
-		virtual ~Occluder() {
+		virtual ~OccluderInstance() {
 			if (scenario) {
-				scenario->_portal_renderer.occluder_destroy(scenario_occluder_id);
+				scenario->_portal_renderer.occluder_instance_destroy(scenario_occluder_id);
 				scenario = nullptr;
 				scenario_occluder_id = 0;
 			}
 		}
 	};
-	RID_Owner<Occluder> occluder_owner;
-
-	virtual RID occluder_create();
-	virtual void occluder_set_scenario(RID p_occluder, RID p_scenario, VisualServer::OccluderType p_type);
-	virtual void occluder_spheres_update(RID p_occluder, const Vector<Plane> &p_spheres);
-	virtual void occluder_mesh_update(RID p_occluder, const Geometry::OccluderMeshData &p_mesh_data);
-	virtual void occluder_set_transform(RID p_occluder, const Transform &p_xform);
-	virtual void occluder_set_active(RID p_occluder, bool p_active);
+	RID_Owner<OccluderInstance> occluder_instance_owner;
+
+	struct OccluderResource : RID_Data {
+		uint32_t occluder_resource_id = 0;
+		void destroy(PortalResources &r_portal_resources) {
+			r_portal_resources.occluder_resource_destroy(occluder_resource_id);
+			occluder_resource_id = 0;
+		}
+		virtual ~OccluderResource() {
+			DEV_ASSERT(occluder_resource_id == 0);
+		}
+	};
+	RID_Owner<OccluderResource> occluder_resource_owner;
+
+	virtual RID occluder_instance_create();
+	virtual void occluder_instance_set_scenario(RID p_occluder_instance, RID p_scenario);
+	virtual void occluder_instance_link_resource(RID p_occluder_instance, RID p_occluder_resource);
+	virtual void occluder_instance_set_transform(RID p_occluder_instance, const Transform &p_xform);
+	virtual void occluder_instance_set_active(RID p_occluder_instance, bool p_active);
+
+	virtual RID occluder_resource_create();
+	virtual void occluder_resource_prepare(RID p_occluder_resource, VisualServer::OccluderType p_type);
+	virtual void occluder_resource_spheres_update(RID p_occluder_resource, const Vector<Plane> &p_spheres);
+	virtual void occluder_resource_mesh_update(RID p_occluder_resource, const Geometry::OccluderMeshData &p_mesh_data);
 	virtual void set_use_occlusion_culling(bool p_enable);
 
 	// editor only .. slow
 	virtual Geometry::MeshData occlusion_debug_get_current_polys(RID p_scenario) const;
+	const PortalResources &get_portal_resources() const { return _portal_resources; }
+	PortalResources &get_portal_resources() { return _portal_resources; }
 
 	// Rooms
 	struct Room : RID_Data {
@@ -876,6 +894,7 @@ public:
 private:
 	bool _use_bvh;
 	VisualServerCallbacks *_visual_server_callbacks;
+	PortalResources _portal_resources;
 
 public:
 	VisualServerScene();

+ 2 - 1
servers/visual/visual_server_wrap_mt.cpp

@@ -174,7 +174,8 @@ void VisualServerWrapMT::finish() {
 	roomgroup_free_cached_ids();
 	portal_free_cached_ids();
 	ghost_free_cached_ids();
-	occluder_free_cached_ids();
+	occluder_instance_free_cached_ids();
+	occluder_resource_free_cached_ids();
 }
 
 void VisualServerWrapMT::set_use_vsync_callback(bool p_enable) {

+ 10 - 6
servers/visual/visual_server_wrap_mt.h

@@ -518,12 +518,16 @@ public:
 	FUNC2(roomgroup_add_room, RID, RID)
 
 	// Occluders
-	FUNCRID(occluder)
-	FUNC3(occluder_set_scenario, RID, RID, OccluderType)
-	FUNC2(occluder_spheres_update, RID, const Vector<Plane> &)
-	FUNC2(occluder_mesh_update, RID, const Geometry::OccluderMeshData &)
-	FUNC2(occluder_set_transform, RID, const Transform &)
-	FUNC2(occluder_set_active, RID, bool)
+	FUNCRID(occluder_instance)
+	FUNC2(occluder_instance_set_scenario, RID, RID)
+	FUNC2(occluder_instance_link_resource, RID, RID)
+	FUNC2(occluder_instance_set_transform, RID, const Transform &)
+	FUNC2(occluder_instance_set_active, RID, bool)
+
+	FUNCRID(occluder_resource)
+	FUNC2(occluder_resource_prepare, RID, OccluderType)
+	FUNC2(occluder_resource_spheres_update, RID, const Vector<Plane> &)
+	FUNC2(occluder_resource_mesh_update, RID, const Geometry::OccluderMeshData &)
 	FUNC1(set_use_occlusion_culling, bool)
 	FUNC1RC(Geometry::MeshData, occlusion_debug_get_current_polys, RID)
 

+ 11 - 6
servers/visual_server.h

@@ -917,12 +917,17 @@ public:
 		OCCLUDER_TYPE_NUM_TYPES,
 	};
 
-	virtual RID occluder_create() = 0;
-	virtual void occluder_set_scenario(RID p_occluder, RID p_scenario, VisualServer::OccluderType p_type) = 0;
-	virtual void occluder_spheres_update(RID p_occluder, const Vector<Plane> &p_spheres) = 0;
-	virtual void occluder_mesh_update(RID p_occluder, const Geometry::OccluderMeshData &p_mesh_data) = 0;
-	virtual void occluder_set_transform(RID p_occluder, const Transform &p_xform) = 0;
-	virtual void occluder_set_active(RID p_occluder, bool p_active) = 0;
+	virtual RID occluder_instance_create() = 0;
+	virtual void occluder_instance_set_scenario(RID p_occluder_instance, RID p_scenario) = 0;
+	virtual void occluder_instance_link_resource(RID p_occluder_instance, RID p_occluder_resource) = 0;
+	virtual void occluder_instance_set_transform(RID p_occluder_instance, const Transform &p_xform) = 0;
+	virtual void occluder_instance_set_active(RID p_occluder_instance, bool p_active) = 0;
+
+	virtual RID occluder_resource_create() = 0;
+	virtual void occluder_resource_prepare(RID p_occluder_resource, VisualServer::OccluderType p_type) = 0;
+	virtual void occluder_resource_spheres_update(RID p_occluder_resource, const Vector<Plane> &p_spheres) = 0;
+	virtual void occluder_resource_mesh_update(RID p_occluder_resource, const Geometry::OccluderMeshData &p_mesh_data) = 0;
+
 	virtual void set_use_occlusion_culling(bool p_enable) = 0;
 	virtual Geometry::MeshData occlusion_debug_get_current_polys(RID p_scenario) const = 0;