From ad577e3c7e5e334144cbe15b46c670aaabb9b871 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Tue, 1 Aug 2023 08:42:30 +0100 Subject: [PATCH] Fix 2D MultiMesh hierarchical culling Fixes updating local bounds for MultiMeshes used in canvas items by introducing a back link. --- drivers/dummy/rasterizer_dummy.h | 1 + drivers/gles2/rasterizer_storage_gles2.cpp | 36 +++++++++++++++++++- drivers/gles2/rasterizer_storage_gles2.h | 2 ++ drivers/gles3/rasterizer_storage_gles3.cpp | 38 ++++++++++++++++++++-- drivers/gles3/rasterizer_storage_gles3.h | 2 ++ servers/visual/rasterizer.h | 25 ++++++++++++++ servers/visual/visual_server_canvas.cpp | 18 +++++++++- servers/visual/visual_server_canvas.h | 4 ++- 8 files changed, 121 insertions(+), 5 deletions(-) diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 33be5c5fe45..7391122b9cd 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -455,6 +455,7 @@ public: AABB _multimesh_get_aabb(RID p_multimesh) const { return AABB(); } MMInterpolator *_multimesh_get_interpolator(RID p_multimesh) const { return nullptr; } + void multimesh_attach_canvas_item(RID p_multimesh, RID p_canvas_item, bool p_attach) {} /* IMMEDIATE API */ diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 144621fb184..d8eac06e94d 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -3388,6 +3388,24 @@ RasterizerStorage::MMInterpolator *RasterizerStorageGLES2::_multimesh_get_interp return &multimesh->interpolator; } +void RasterizerStorageGLES2::multimesh_attach_canvas_item(RID p_multimesh, RID p_canvas_item, bool p_attach) { + MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); + ERR_FAIL_NULL(multimesh); + ERR_FAIL_COND(!p_canvas_item.is_valid()); + + if (p_attach) { + int64_t found = multimesh->linked_canvas_items.find(p_canvas_item); + if (found == -1) { + multimesh->linked_canvas_items.push_back(p_canvas_item); + } + } else { + int64_t found = multimesh->linked_canvas_items.find(p_canvas_item); + if (found != -1) { + multimesh->linked_canvas_items.remove_unordered(found); + } + } +} + void RasterizerStorageGLES2::update_dirty_multimeshes() { while (multimesh_update_list.first()) { MultiMesh *multimesh = multimesh_update_list.first()->self(); @@ -3457,6 +3475,14 @@ void RasterizerStorageGLES2::update_dirty_multimeshes() { } multimesh->aabb = aabb; + + // Inform any linked canvas items that bounds have changed + // (for hierarchical culling). + int num_linked = multimesh->linked_canvas_items.size(); + for (int n = 0; n < num_linked; n++) { + const RID &rid = multimesh->linked_canvas_items[n]; + VSG::canvas->_canvas_item_invalidate_local_bound(rid); + } } multimesh->dirty_aabb = false; @@ -4128,7 +4154,7 @@ void RasterizerStorageGLES2::update_dirty_skeletons() { int num_linked = skeleton->linked_canvas_items.size(); for (int n = 0; n < num_linked; n++) { const RID &rid = skeleton->linked_canvas_items[n]; - VSG::canvas->_canvas_item_skeleton_moved(rid); + VSG::canvas->_canvas_item_invalidate_local_bound(rid); } ele = ele->next(); @@ -6052,6 +6078,14 @@ bool RasterizerStorageGLES2::free(RID p_rid) { _interpolation_data.notify_free_multimesh(p_rid); MultiMesh *multimesh = multimesh_owner.get(p_rid); + + // remove any references in linked canvas items + int num_linked = multimesh->linked_canvas_items.size(); + for (int n = 0; n < num_linked; n++) { + const RID &rid = multimesh->linked_canvas_items[n]; + VSG::canvas->_canvas_item_remove_references(rid, p_rid); + } + multimesh->instance_remove_deps(); if (multimesh->mesh.is_valid()) { diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index f532c2eacea..fc95f22717d 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -789,6 +789,7 @@ public: bool dirty_data; MMInterpolator interpolator; + LocalVector linked_canvas_items; MultiMesh() : size(0), @@ -835,6 +836,7 @@ public: virtual AABB _multimesh_get_aabb(RID p_multimesh) const; virtual MMInterpolator *_multimesh_get_interpolator(RID p_multimesh) const; + virtual void multimesh_attach_canvas_item(RID p_multimesh, RID p_canvas_item, bool p_attach); void update_dirty_multimeshes(); diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 54a288302ee..e1836339419 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -4923,6 +4923,24 @@ RasterizerStorage::MMInterpolator *RasterizerStorageGLES3::_multimesh_get_interp return &multimesh->interpolator; } +void RasterizerStorageGLES3::multimesh_attach_canvas_item(RID p_multimesh, RID p_canvas_item, bool p_attach) { + MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); + ERR_FAIL_NULL(multimesh); + ERR_FAIL_COND(!p_canvas_item.is_valid()); + + if (p_attach) { + int64_t found = multimesh->linked_canvas_items.find(p_canvas_item); + if (found == -1) { + multimesh->linked_canvas_items.push_back(p_canvas_item); + } + } else { + int64_t found = multimesh->linked_canvas_items.find(p_canvas_item); + if (found != -1) { + multimesh->linked_canvas_items.remove_unordered(found); + } + } +} + void RasterizerStorageGLES3::update_dirty_multimeshes() { while (multimesh_update_list.first()) { MultiMesh *multimesh = multimesh_update_list.first()->self(); @@ -5001,6 +5019,14 @@ void RasterizerStorageGLES3::update_dirty_multimeshes() { } multimesh->aabb = aabb; + + // Inform any linked canvas items that bounds have changed + // (for hierarchical culling). + int num_linked = multimesh->linked_canvas_items.size(); + for (int n = 0; n < num_linked; n++) { + const RID &rid = multimesh->linked_canvas_items[n]; + VSG::canvas->_canvas_item_invalidate_local_bound(rid); + } } multimesh->dirty_aabb = false; multimesh->dirty_data = false; @@ -5348,7 +5374,7 @@ void RasterizerStorageGLES3::update_dirty_skeletons() { int num_linked = skeleton->linked_canvas_items.size(); for (int n = 0; n < num_linked; n++) { const RID &rid = skeleton->linked_canvas_items[n]; - VSG::canvas->_canvas_item_skeleton_moved(rid); + VSG::canvas->_canvas_item_invalidate_local_bound(rid); } ele = ele->next(); @@ -7865,8 +7891,16 @@ bool RasterizerStorageGLES3::free(RID p_rid) { // remove from interpolator _interpolation_data.notify_free_multimesh(p_rid); - // delete the texture MultiMesh *multimesh = multimesh_owner.get(p_rid); + + // remove any references in linked canvas items + int num_linked = multimesh->linked_canvas_items.size(); + for (int n = 0; n < num_linked; n++) { + const RID &rid = multimesh->linked_canvas_items[n]; + VSG::canvas->_canvas_item_remove_references(rid, p_rid); + } + + // delete the texture multimesh->instance_remove_deps(); if (multimesh->mesh.is_valid()) { diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index aecc70396a6..9e2367143a0 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -818,6 +818,7 @@ public: bool dirty_data; MMInterpolator interpolator; + LocalVector linked_canvas_items; MultiMesh() : size(0), @@ -867,6 +868,7 @@ public: virtual AABB _multimesh_get_aabb(RID p_multimesh) const; virtual MMInterpolator *_multimesh_get_interpolator(RID p_multimesh) const; + virtual void multimesh_attach_canvas_item(RID p_multimesh, RID p_canvas_item, bool p_attach); /* IMMEDIATE API */ diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index b912445fc79..042f1fce091 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -395,6 +395,7 @@ public: virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible); virtual int multimesh_get_visible_instances(RID p_multimesh) const; virtual AABB multimesh_get_aabb(RID p_multimesh) const; + virtual void multimesh_attach_canvas_item(RID p_multimesh, RID p_canvas_item, bool p_attach) = 0; virtual RID _multimesh_create() = 0; virtual void _multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE) = 0; @@ -817,6 +818,8 @@ public: TYPE_MULTIRECT, }; + virtual bool contains_reference(const RID &p_rid) const { return false; } + Type type; virtual ~Command() {} }; @@ -946,7 +949,15 @@ public: RID multimesh; RID texture; RID normal_map; + RID canvas_item; + virtual bool contains_reference(const RID &p_rid) const { return multimesh == p_rid; } CommandMultiMesh() { type = TYPE_MULTIMESH; } + virtual ~CommandMultiMesh() { + // Remove any backlinks from multimesh to canvas item. + if (multimesh.is_valid()) { + RasterizerStorage::base_singleton->multimesh_attach_canvas_item(multimesh, canvas_item, false); + } + } }; struct CommandParticles : public Command { @@ -1199,6 +1210,20 @@ public: return rect; } + void remove_references(const RID &p_rid) { + for (int i = commands.size() - 1; i >= 0; i--) { + if (commands[i]->contains_reference(p_rid)) { + memdelete(commands[i]); + + // This could possibly be unordered if occurring close + // to canvas_item deletion, but is + // unlikely to make much performance difference, + // and is safer. + commands.remove(i); + } + } + } + void clear() { for (int i = 0; i < commands.size(); i++) { memdelete(commands[i]); diff --git a/servers/visual/visual_server_canvas.cpp b/servers/visual/visual_server_canvas.cpp index 759615a6877..10d437e0246 100644 --- a/servers/visual/visual_server_canvas.cpp +++ b/servers/visual/visual_server_canvas.cpp @@ -1421,10 +1421,17 @@ void VisualServerCanvas::canvas_item_add_multimesh(RID p_item, RID p_mesh, RID p mm->multimesh = p_mesh; mm->texture = p_texture; mm->normal_map = p_normal_map; + mm->canvas_item = p_item; canvas_item->rect_dirty = true; canvas_item->commands.push_back(mm); _make_bound_dirty(canvas_item); + + // Attach to multimesh a backlink to enable updating + // the canvas item local bound when the multimesh changes. + if (p_mesh.is_valid()) { + VSG::storage->multimesh_attach_canvas_item(p_mesh, p_item, true); + } } void VisualServerCanvas::canvas_item_add_clip_ignore(RID p_item, bool p_ignore) { @@ -1588,7 +1595,16 @@ void VisualServerCanvas::canvas_item_attach_skeleton(RID p_item, RID p_skeleton) } } -void VisualServerCanvas::_canvas_item_skeleton_moved(RID p_item) { +// Canvas items may contain references to other resources (such as MultiMesh). +// If the resources are deleted first, and the canvas_item retains references, it +// will crash / error when it tries to access these. +void VisualServerCanvas::_canvas_item_remove_references(RID p_item, RID p_rid) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND(!canvas_item); + canvas_item->remove_references(p_rid); +} + +void VisualServerCanvas::_canvas_item_invalidate_local_bound(RID p_item) { Item *canvas_item = canvas_item_owner.getornull(p_item); ERR_FAIL_COND(!canvas_item); _make_bound_dirty(canvas_item); diff --git a/servers/visual/visual_server_canvas.h b/servers/visual/visual_server_canvas.h index 038ee1be5d9..4f613421568 100644 --- a/servers/visual/visual_server_canvas.h +++ b/servers/visual/visual_server_canvas.h @@ -256,7 +256,6 @@ public: void canvas_item_set_use_parent_material(RID p_item, bool p_enable); void canvas_item_attach_skeleton(RID p_item, RID p_skeleton); - void _canvas_item_skeleton_moved(RID p_item); void canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform); Rect2 _debug_canvas_item_get_rect(RID p_item); Rect2 _debug_canvas_item_get_local_bound(RID p_item); @@ -265,6 +264,9 @@ public: void canvas_item_reset_physics_interpolation(RID p_item); void canvas_item_transform_physics_interpolation(RID p_item, Transform2D p_transform); + void _canvas_item_invalidate_local_bound(RID p_item); + void _canvas_item_remove_references(RID p_item, RID p_rid); + RID canvas_light_create(); void canvas_light_attach_to_canvas(RID p_light, RID p_canvas); void canvas_light_set_enabled(RID p_light, bool p_enabled);