Skip to content

Commit 97ff301

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

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

scene/3d/sprite_3d.cpp

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

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

35+
static bool instancing_enabled = false;
36+
static HashMap<SpriteMeshKey, SpriteBase3D *, SpriteMeshHasher> shared_sprites;
37+
3538
Color SpriteBase3D::_get_color_accum() {
3639
if (!color_dirty) {
3740
return color_accum;
@@ -64,6 +67,51 @@ void SpriteBase3D::_propagate_color_changed() {
6467
}
6568
}
6669

70+
void SpriteBase3D::_stop_sharing_sprite() {
71+
shared_sprites.erase(last_sprite_mesh_key);
72+
sharing_own_mesh = false;
73+
if (!users.is_empty()) {
74+
// Select a successor and make every other user use that successor's mesh instead.
75+
SpriteBase3D *successor = nullptr;
76+
int i = 0;
77+
for (; i < users.size(); i++) {
78+
// There may be sprites that have changed the sprite they're using earlier this frame, so we have to filter them out.
79+
if (users[i] && users[i]->using_sprite == this) {
80+
successor = users[i];
81+
shared_sprites.insert(last_sprite_mesh_key, successor);
82+
// 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.
83+
// memcpy is used directly because buffer sizes are guaranteed to be identical across all SpriteBase3Ds.
84+
memcpy(successor->vertex_buffer.ptrw(), vertex_buffer.ptr(), vertex_buffer.size());
85+
memcpy(successor->attribute_buffer.ptrw(), attribute_buffer.ptr(), attribute_buffer.size());
86+
RS::get_singleton()->mesh_surface_update_vertex_region(successor->mesh, 0, 0, successor->vertex_buffer);
87+
RS::get_singleton()->mesh_surface_update_attribute_region(successor->mesh, 0, 0, successor->attribute_buffer);
88+
RS::get_singleton()->mesh_set_custom_aabb(successor->mesh, aabb);
89+
if (last_sprite_mesh_key.alpha_cut_disabled) {
90+
RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
91+
RS::get_singleton()->mesh_surface_set_material(successor->mesh, 0, get_material());
92+
}
93+
successor->using_sprite = nullptr;
94+
successor->using_sprite_user_index = -1;
95+
successor->set_base(successor->mesh);
96+
i++; // Skip the successor for the next for-loop.
97+
break;
98+
}
99+
}
100+
// Propagate the change to remaining users that haven't changed their using_sprite.
101+
// Note: This works even if the same user is registered twice. Very rare but can still happen.
102+
for (; i < users.size(); i++) {
103+
if (users[i] && users[i]->using_sprite == this) {
104+
users[i]->set_base(successor->mesh);
105+
users[i]->using_sprite = successor;
106+
users[i]->using_sprite_user_index = successor->users.size();
107+
successor->users.push_back(users[i]);
108+
}
109+
}
110+
// Now every user has moved on, we can clear the users list.
111+
users.clear();
112+
}
113+
}
114+
67115
void SpriteBase3D::_notification(int p_what) {
68116
switch (p_what) {
69117
case NOTIFICATION_ENTER_TREE: {
@@ -221,6 +269,44 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
221269
uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0))
222270
};
223271

272+
if (instancing_enabled) {
273+
SpriteMeshKey sprite_mesh_key;
274+
memcpy(&sprite_mesh_key.vertices, vertices, sizeof(Vector2) * 4);
275+
memcpy(&sprite_mesh_key.uvs, uvs, sizeof(Vector2) * 4);
276+
sprite_mesh_key.v_color = *(uint32_t *)v_color;
277+
sprite_mesh_key.v_normal = v_normal;
278+
sprite_mesh_key.alpha_cut_disabled = get_alpha_cut_mode() == ALPHA_CUT_DISABLED;
279+
if (sharing_own_mesh) {
280+
if (last_sprite_mesh_key != sprite_mesh_key) {
281+
// Sprite mesh data changed.
282+
_stop_sharing_sprite();
283+
} else {
284+
// Sprite mesh data unchanged.
285+
return;
286+
}
287+
}
288+
// Try to see if there's any sprite whose mesh can be used instead.
289+
SpriteBase3D **sprite_ptr = shared_sprites.getptr(sprite_mesh_key);
290+
last_sprite_mesh_key = sprite_mesh_key;
291+
if (sprite_ptr) {
292+
if (*sprite_ptr != using_sprite) {
293+
// Found new sprite that can be reused.
294+
using_sprite = *sprite_ptr;
295+
set_base(using_sprite->mesh);
296+
using_sprite_user_index = using_sprite->users.size();
297+
using_sprite->users.push_back(this);
298+
// 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.
299+
return;
300+
} else {
301+
// Keep using same sprite.
302+
return;
303+
}
304+
}
305+
// Otherwise, setup mesh data and register this sprite's mesh for sharing.
306+
shared_sprites.insert(sprite_mesh_key, this);
307+
sharing_own_mesh = true;
308+
}
309+
224310
for (int i = 0; i < 4; i++) {
225311
Vector3 vtx;
226312
vtx[x_axis] = vertices[i][0];
@@ -243,7 +329,8 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
243329
memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_COLOR]], v_color, 4);
244330
}
245331

246-
RID mesh_new = get_mesh();
332+
RID mesh_new = mesh;
333+
set_base(mesh_new);
247334
RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer);
248335
RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer);
249336

@@ -676,6 +763,13 @@ void SpriteBase3D::_bind_methods() {
676763
}
677764

678765
SpriteBase3D::SpriteBase3D() {
766+
static bool static_initialized = false;
767+
if (!static_initialized) {
768+
static_initialized = true;
769+
// Auto-instancing isn't supported in the compatibility renderer.
770+
instancing_enabled = RS::get_singleton()->get_current_rendering_method() != "gl_compatibility";
771+
}
772+
679773
for (int i = 0; i < FLAG_MAX; i++) {
680774
flags[i] = i == FLAG_TRANSPARENT || i == FLAG_DOUBLE_SIDED;
681775
}
@@ -753,6 +847,14 @@ SpriteBase3D::~SpriteBase3D() {
753847
ERR_FAIL_NULL(RenderingServer::get_singleton());
754848
RenderingServer::get_singleton()->free(mesh);
755849
RenderingServer::get_singleton()->free(material);
850+
851+
if (instancing_enabled && sharing_own_mesh) {
852+
_stop_sharing_sprite();
853+
}
854+
855+
if (using_sprite) {
856+
using_sprite->users.ptrw()[using_sprite_user_index] = nullptr;
857+
}
756858
}
757859

758860
///////////////////////////////////////////

scene/3d/sprite_3d.h

Lines changed: 28 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,12 @@ class SpriteBase3D : public GeometryInstance3D {
5979
};
6080

6181
private:
82+
Vector<SpriteBase3D *> users;
83+
SpriteMeshKey last_sprite_mesh_key;
84+
SpriteBase3D *using_sprite = nullptr;
85+
int using_sprite_user_index = -1; // Used to invalidate this sprite's entry in another sprite's users vector.
86+
bool sharing_own_mesh = false;
87+
6288
bool color_dirty = true;
6389
Color color_accum;
6490

@@ -97,6 +123,7 @@ class SpriteBase3D : public GeometryInstance3D {
97123
void _im_update();
98124

99125
void _propagate_color_changed();
126+
void _stop_sharing_sprite();
100127

101128
protected:
102129
Color _get_color_accum();
@@ -105,7 +132,7 @@ class SpriteBase3D : public GeometryInstance3D {
105132
virtual void _draw() = 0;
106133
void draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect);
107134
_FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; }
108-
_FORCE_INLINE_ RID &get_mesh() { return mesh; }
135+
_FORCE_INLINE_ RID &get_mesh() { return using_sprite ? using_sprite->mesh : mesh; }
109136
_FORCE_INLINE_ RID &get_material() { return material; }
110137

111138
uint32_t mesh_surface_offsets[RS::ARRAY_MAX];

0 commit comments

Comments
 (0)