2
0
Эх сурвалжийг харах

Fix Occluder to properly share resources

In order to properly support the resource sharing paradigm, Occluders are split into Instances and Resources in the VisualServer. Instances are owned by a Scenario, and Resources are global. OccluderShape resources can now correctly be shared by multiple OccluderInstances.
lawnjelly 3 жил өмнө
parent
commit
3c2df49832

+ 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

@@ -592,12 +592,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

@@ -1192,65 +1192,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) {
@@ -4153,10 +4176,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

@@ -678,29 +678,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 {
@@ -821,6 +839,7 @@ public:
 private:
 	bool _use_bvh;
 	VisualServerCallbacks *_visual_server_callbacks;
+	PortalResources _portal_resources;
 
 public:
 	VisualServerScene();

+ 2 - 1
servers/visual/visual_server_wrap_mt.cpp

@@ -144,7 +144,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

@@ -505,12 +505,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

@@ -905,12 +905,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;