1
0
Fork 0

Reuse Sprite3D meshes across nodes when possible.

This commit is contained in:
chocola-mint 2025-02-26 16:44:12 +09:00
parent 53faed5351
commit 8e33549969
2 changed files with 96 additions and 2 deletions

View File

@ -32,6 +32,8 @@
#include "scene/resources/atlas_texture.h"
HashMap<SpriteMeshKey, SpriteBase3D *, SpriteMeshHasher> SpriteBase3D::shared_sprites;
Color SpriteBase3D::_get_color_accum() {
if (!color_dirty) {
return color_accum;
@ -221,6 +223,68 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> 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<Texture2D> 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);
}
}
///////////////////////////////////////////

View File

@ -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<SpriteMeshKey, SpriteBase3D *, SpriteMeshHasher> shared_sprites;
Vector<SpriteBase3D *> 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<Texture2D> 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];