Skip to content

Commit c0a09f7

Browse files
committed
Reuse Sprite3D meshes across nodes when possible.
1 parent 1753893 commit c0a09f7

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

scene/3d/sprite_3d.cpp

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
#include "scene/resources/atlas_texture.h"
3434

35+
HashMap<SpriteMeshKey, SpriteBase3D *, SpriteMeshHasher> SpriteBase3D::shared_sprites;
36+
3537
Color SpriteBase3D::_get_color_accum() {
3638
if (!color_dirty) {
3739
return color_accum;
@@ -221,6 +223,73 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
221223
uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0))
222224
};
223225

226+
SpriteMeshKey sprite_mesh_key;
227+
memcpy(&sprite_mesh_key.vertices, vertices, sizeof(Vector2) * 4);
228+
memcpy(&sprite_mesh_key.uvs, uvs, sizeof(Vector2) * 4);
229+
sprite_mesh_key.v_color = *(uint32_t *)v_color;
230+
sprite_mesh_key.v_normal = v_normal;
231+
sprite_mesh_key.alpha_cut_disabled = get_alpha_cut_mode() == ALPHA_CUT_DISABLED;
232+
if (!using_sprite) {
233+
// Not using another sprite -> this sprite owns its current mesh.
234+
if (last_sprite_mesh_key != sprite_mesh_key) {
235+
// Sprite mesh data changed.
236+
shared_sprites.erase(last_sprite_mesh_key);
237+
if (!users.is_empty()) {
238+
// Select a successor and make every other user use that successor's mesh instead.
239+
SpriteBase3D *successor = nullptr;
240+
int i = 0;
241+
for (; i < users.size(); i++) {
242+
// There may be sprites that have changed the sprite they're using earlier this frame, so we have to filter them out.
243+
if (users[i]->using_sprite == this) {
244+
successor = users[i];
245+
shared_sprites.insert(last_sprite_mesh_key, successor);
246+
// 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.
247+
// memcpy is used directly because buffer sizes are guaranteed to be identical across all SpriteBase3Ds.
248+
memcpy(successor->vertex_buffer.ptrw(), vertex_buffer.ptr(), vertex_buffer.size());
249+
memcpy(successor->attribute_buffer.ptrw(), attribute_buffer.ptr(), attribute_buffer.size());
250+
RS::get_singleton()->mesh_surface_update_vertex_region(successor->mesh, 0, 0, successor->vertex_buffer);
251+
RS::get_singleton()->mesh_surface_update_attribute_region(successor->mesh, 0, 0, successor->attribute_buffer);
252+
RS::get_singleton()->mesh_set_custom_aabb(successor->mesh, aabb);
253+
if (last_sprite_mesh_key.alpha_cut_disabled) {
254+
RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
255+
RS::get_singleton()->mesh_surface_set_material(successor->mesh, 0, get_material());
256+
}
257+
successor->using_sprite = nullptr;
258+
successor->set_base(successor->mesh);
259+
break;
260+
}
261+
}
262+
// Propagate the change to remaining users that haven't changed their using_sprite.
263+
// Note: This works even if the same user is registered twice. Very rare but can still happen.
264+
for (; i < users.size(); i++) {
265+
if (users[i]->using_sprite == this) {
266+
users[i]->set_base(successor->mesh);
267+
users[i]->using_sprite = successor;
268+
}
269+
}
270+
// Now every user has moved on, we can clear the users list.
271+
users.clear();
272+
}
273+
}
274+
}
275+
// Try to see if there's any sprite whose mesh can be used instead.
276+
SpriteBase3D **sprite_ptr = shared_sprites.getptr(sprite_mesh_key);
277+
last_sprite_mesh_key = sprite_mesh_key;
278+
if (sprite_ptr) {
279+
if (*sprite_ptr != using_sprite) {
280+
// Found new sprite that can be reused.
281+
using_sprite = *sprite_ptr;
282+
set_base(using_sprite->mesh);
283+
using_sprite->users.push_back(this);
284+
// 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.
285+
return;
286+
} else {
287+
// Keep using same sprite.
288+
return;
289+
}
290+
}
291+
// Otherwise, setup mesh data and register this sprite's mesh for sharing.
292+
shared_sprites.insert(sprite_mesh_key, this);
224293
for (int i = 0; i < 4; i++) {
225294
Vector3 vtx;
226295
vtx[x_axis] = vertices[i][0];
@@ -243,7 +312,8 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
243312
memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_COLOR]], v_color, 4);
244313
}
245314

246-
RID mesh_new = get_mesh();
315+
RID mesh_new = mesh;
316+
set_base(mesh_new);
247317
RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer);
248318
RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer);
249319

@@ -753,6 +823,10 @@ SpriteBase3D::~SpriteBase3D() {
753823
ERR_FAIL_NULL(RenderingServer::get_singleton());
754824
RenderingServer::get_singleton()->free(mesh);
755825
RenderingServer::get_singleton()->free(material);
826+
827+
if (!using_sprite) {
828+
shared_sprites.erase(last_sprite_mesh_key);
829+
}
756830
}
757831

758832
///////////////////////////////////////////

scene/3d/sprite_3d.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@
3434
#include "scene/3d/visual_instance_3d.h"
3535
#include "scene/resources/sprite_frames.h"
3636

37+
struct SpriteMeshKey {
38+
Vector2 vertices[4];
39+
Vector2 uvs[4];
40+
uint32_t v_color = 0;
41+
uint32_t v_normal = 0;
42+
bool alpha_cut_disabled = false;
43+
bool operator==(const SpriteMeshKey &other) const {
44+
return memcmp(this, &other, sizeof(SpriteMeshKey)) == 0;
45+
}
46+
bool operator!=(const SpriteMeshKey &other) const {
47+
return memcmp(this, &other, sizeof(SpriteMeshKey)) != 0;
48+
}
49+
};
50+
51+
struct SpriteMeshHasher {
52+
static _FORCE_INLINE_ uint32_t hash(const SpriteMeshKey &p_mesh_key) {
53+
return hash_murmur3_buffer(&p_mesh_key, sizeof(SpriteMeshKey));
54+
}
55+
};
56+
3757
class SpriteBase3D : public GeometryInstance3D {
3858
GDCLASS(SpriteBase3D, GeometryInstance3D);
3959

@@ -59,6 +79,11 @@ class SpriteBase3D : public GeometryInstance3D {
5979
};
6080

6181
private:
82+
static HashMap<SpriteMeshKey, SpriteBase3D *, SpriteMeshHasher> shared_sprites;
83+
Vector<SpriteBase3D *> users;
84+
SpriteMeshKey last_sprite_mesh_key;
85+
SpriteBase3D *using_sprite = nullptr;
86+
6287
bool color_dirty = true;
6388
Color color_accum;
6489

@@ -105,7 +130,7 @@ class SpriteBase3D : public GeometryInstance3D {
105130
virtual void _draw() = 0;
106131
void draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect);
107132
_FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; }
108-
_FORCE_INLINE_ RID &get_mesh() { return mesh; }
133+
_FORCE_INLINE_ RID &get_mesh() { return using_sprite ? using_sprite->mesh : mesh; }
109134
_FORCE_INLINE_ RID &get_material() { return material; }
110135

111136
uint32_t mesh_surface_offsets[RS::ARRAY_MAX];

0 commit comments

Comments
 (0)