From 8e33549969d1c0d1dcd7e1b2db3d6c980b16e761 Mon Sep 17 00:00:00 2001 From: chocola-mint <56677134+chocola-mint@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:44:12 +0900 Subject: [PATCH] Reuse Sprite3D meshes across nodes when possible. --- scene/3d/sprite_3d.cpp | 71 +++++++++++++++++++++++++++++++++++++++++- scene/3d/sprite_3d.h | 27 +++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index fb62b46e863..963b6b390d9 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -32,6 +32,8 @@ #include "scene/resources/atlas_texture.h" +HashMap SpriteBase3D::shared_sprites; + Color SpriteBase3D::_get_color_accum() { if (!color_dirty) { return color_accum; @@ -221,6 +223,68 @@ void SpriteBase3D::draw_texture_rect(Ref p_texture, Rect2 p_dst_rect, uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0)) }; + SpriteMeshKey sprite_mesh_key; + memcpy(&sprite_mesh_key.vertices, vertices, sizeof(Vector2) * 4); + memcpy(&sprite_mesh_key.uvs, uvs, sizeof(Vector2) * 4); + sprite_mesh_key.v_color = *(uint32_t *)v_color; + sprite_mesh_key.v_normal = v_normal; + sprite_mesh_key.alpha_cut_disabled = get_alpha_cut_mode() == ALPHA_CUT_DISABLED; + if (!using_sprite) { + // Not using another sprite -> this sprite owns its current mesh. + if (last_sprite_mesh_key != sprite_mesh_key) { + // Sprite mesh data changed. + shared_sprites.erase(last_sprite_mesh_key); + if (!users.is_empty()) { + // Select a successor and make every other user use that successor's mesh instead. + SpriteBase3D *successor = nullptr; + int i = 0; + for (; i < users.size(); i++) { + // There may be sprites that have changed the sprite they're using earlier this frame, so we have to filter them out. + if (users[i]->using_sprite == this) { + successor = users[i]; + shared_sprites.insert(last_sprite_mesh_key, successor); + // Copy mesh data to successor. Need to store data in vertex and attribute buffer and not just update the RenderingServer mesh directly so they can be copied again later. + // memcpy is used directly because buffer sizes are guaranteed to be identical across all SpriteBase3Ds. + memcpy(successor->vertex_buffer.ptrw(), vertex_buffer.ptr(), vertex_buffer.size()); + memcpy(successor->attribute_buffer.ptrw(), attribute_buffer.ptr(), attribute_buffer.size()); + RS::get_singleton()->mesh_surface_update_vertex_region(successor->mesh, 0, 0, successor->vertex_buffer); + RS::get_singleton()->mesh_surface_update_attribute_region(successor->mesh, 0, 0, successor->attribute_buffer); + RS::get_singleton()->mesh_set_custom_aabb(successor->mesh, aabb); + if (last_sprite_mesh_key.alpha_cut_disabled) { + RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority()); + RS::get_singleton()->mesh_surface_set_material(successor->mesh, 0, get_material()); + } + successor->using_sprite = nullptr; + successor->set_base(successor->mesh); + break; + } + } + // Propagate the change to remaining users that haven't changed their using_sprite. + // Note: This works even if the same user is registered twice. Very rare but can still happen. + for (; i < users.size(); i++) { + if (users[i]->using_sprite == this) { + users[i]->set_base(successor->mesh); + users[i]->using_sprite = successor; + } + } + // Now every user has moved on, so we can clear the users list. + users.clear(); + } + } + } + // Try to see if there's any sprite whose mesh can be used instead. + SpriteBase3D **sprite_ptr = shared_sprites.getptr(sprite_mesh_key); + last_sprite_mesh_key = sprite_mesh_key; + if (sprite_ptr && *sprite_ptr != using_sprite) { + // Found sprite that can be reused. + using_sprite = *sprite_ptr; + set_base(using_sprite->mesh); + using_sprite->users.push_back(this); + // We don't need to remove this sprite from the previous shared sprite's users list, as they will be detected and filtered out later. + return; + } + // Otherwise, setup mesh data and register this sprite's mesh for sharing. + shared_sprites.insert(sprite_mesh_key, this); for (int i = 0; i < 4; i++) { Vector3 vtx; vtx[x_axis] = vertices[i][0]; @@ -243,7 +307,8 @@ void SpriteBase3D::draw_texture_rect(Ref p_texture, Rect2 p_dst_rect, memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_COLOR]], v_color, 4); } - RID mesh_new = get_mesh(); + RID mesh_new = mesh; + set_base(mesh_new); RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer); RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer); @@ -753,6 +818,10 @@ SpriteBase3D::~SpriteBase3D() { ERR_FAIL_NULL(RenderingServer::get_singleton()); RenderingServer::get_singleton()->free(mesh); RenderingServer::get_singleton()->free(material); + + if (!using_sprite) { + shared_sprites.erase(last_sprite_mesh_key); + } } /////////////////////////////////////////// diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index 87327caca27..b788a594f98 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -34,6 +34,26 @@ #include "scene/3d/visual_instance_3d.h" #include "scene/resources/sprite_frames.h" +struct SpriteMeshKey { + Vector2 vertices[4]; + Vector2 uvs[4]; + uint32_t v_color = 0; + uint32_t v_normal = 0; + bool alpha_cut_disabled = false; + bool operator==(const SpriteMeshKey &other) const { + return memcmp(this, &other, sizeof(SpriteMeshKey)) == 0; + } + bool operator!=(const SpriteMeshKey &other) const { + return memcmp(this, &other, sizeof(SpriteMeshKey)) != 0; + } +}; + +struct SpriteMeshHasher { + static _FORCE_INLINE_ uint32_t hash(const SpriteMeshKey &p_mesh_key) { + return hash_murmur3_buffer(&p_mesh_key, sizeof(SpriteMeshKey)); + } +}; + class SpriteBase3D : public GeometryInstance3D { GDCLASS(SpriteBase3D, GeometryInstance3D); @@ -59,6 +79,11 @@ public: }; private: + static HashMap shared_sprites; + Vector users; + SpriteMeshKey last_sprite_mesh_key; + SpriteBase3D *using_sprite = nullptr; + bool color_dirty = true; Color color_accum; @@ -105,7 +130,7 @@ protected: virtual void _draw() = 0; void draw_texture_rect(Ref p_texture, Rect2 p_dst_rect, Rect2 p_src_rect); _FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; } - _FORCE_INLINE_ RID &get_mesh() { return mesh; } + _FORCE_INLINE_ RID &get_mesh() { return using_sprite ? using_sprite->mesh : mesh; } _FORCE_INLINE_ RID &get_material() { return material; } uint32_t mesh_surface_offsets[RS::ARRAY_MAX];