Skip to content

Commit f70f54b

Browse files
committed
Reuse Sprite3D meshes across nodes when possible.
1 parent 52ecb5a commit f70f54b

File tree

4 files changed

+666
-36
lines changed

4 files changed

+666
-36
lines changed

scene/3d/sprite_3d.cpp

Lines changed: 169 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

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

35+
static HashMap<SpriteMeshKey, SpriteBase3D *, SpriteMeshHasher> shared_sprites;
36+
3537
Color SpriteBase3D::_get_color_accum() {
3638
if (!color_dirty) {
3739
return color_accum;
@@ -64,6 +66,74 @@ void SpriteBase3D::_propagate_color_changed() {
6466
}
6567
}
6668

69+
void SpriteBase3D::_start_sharing_sprite() {
70+
shared_sprites.insert(last_sprite_mesh_key, this);
71+
sharing_own_mesh = true;
72+
}
73+
74+
void SpriteBase3D::_stop_sharing_sprite() {
75+
shared_sprites.erase(last_sprite_mesh_key);
76+
sharing_own_mesh = false;
77+
if (!users.is_empty()) {
78+
// Select a successor and make every other user use that successor's mesh instead.
79+
SpriteBase3D *successor = nullptr;
80+
int i = 0;
81+
for (; i < users.size(); i++) {
82+
// There may be sprites that have changed the sprite they're using earlier this frame, so we have to filter them out.
83+
if (users[i] && users[i]->using_sprite == this) {
84+
successor = users[i];
85+
successor->_start_sharing_sprite();
86+
// 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.
87+
// memcpy is used directly because buffer sizes are guaranteed to be identical across all SpriteBase3Ds.
88+
memcpy(successor->vertex_buffer.ptrw(), vertex_buffer.ptr(), vertex_buffer.size());
89+
memcpy(successor->attribute_buffer.ptrw(), attribute_buffer.ptr(), attribute_buffer.size());
90+
RS::get_singleton()->mesh_surface_update_vertex_region(successor->mesh, 0, 0, successor->vertex_buffer);
91+
RS::get_singleton()->mesh_surface_update_attribute_region(successor->mesh, 0, 0, successor->attribute_buffer);
92+
RS::get_singleton()->mesh_set_custom_aabb(successor->mesh, aabb);
93+
successor->using_sprite = nullptr;
94+
successor->set_base(successor->mesh);
95+
successor->set_aabb(aabb);
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]->_start_using_sprite(successor);
105+
}
106+
}
107+
// Now every user has moved on, we can clear the users list.
108+
users.clear();
109+
}
110+
}
111+
112+
void SpriteBase3D::_start_using_sprite(SpriteBase3D *p_using_sprite) {
113+
using_sprite = p_using_sprite;
114+
using_sprite_user_index = using_sprite->users.size();
115+
set_base(using_sprite->mesh);
116+
set_aabb(using_sprite->aabb);
117+
using_sprite->users.push_back(this);
118+
// We don't need to remove this sprite from the previous shared sprite's users list,
119+
// as setting using_sprite means they will be detected and filtered out later.
120+
}
121+
122+
void SpriteBase3D::_stop_using_sprite() {
123+
using_sprite->users.ptrw()[using_sprite_user_index] = nullptr;
124+
using_sprite = nullptr;
125+
}
126+
127+
#ifdef TESTS_ENABLED
128+
void SpriteBase3D::set_sprite_sharing_enabled(bool p_sprite_sharing_enabled) {
129+
sprite_sharing_enabled = p_sprite_sharing_enabled;
130+
}
131+
132+
bool SpriteBase3D::is_sprite_sharing_enabled() {
133+
return sprite_sharing_enabled;
134+
}
135+
#endif
136+
67137
void SpriteBase3D::_notification(int p_what) {
68138
switch (p_what) {
69139
case NOTIFICATION_ENTER_TREE: {
@@ -221,6 +291,87 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
221291
uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0))
222292
};
223293

294+
BaseMaterial3D::Transparency mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_DISABLED;
295+
if (get_draw_flag(FLAG_TRANSPARENT)) {
296+
if (get_alpha_cut_mode() == ALPHA_CUT_DISCARD) {
297+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_SCISSOR;
298+
} else if (get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS) {
299+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS;
300+
} else if (get_alpha_cut_mode() == ALPHA_CUT_HASH) {
301+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_HASH;
302+
} else {
303+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA;
304+
}
305+
}
306+
307+
RID shader_rid;
308+
StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), mat_transparency, get_draw_flag(FLAG_DOUBLE_SIDED), get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), alpha_antialiasing_mode, &shader_rid);
309+
310+
if (last_shader != shader_rid) {
311+
RS::get_singleton()->material_set_shader(get_material(), shader_rid);
312+
last_shader = shader_rid;
313+
}
314+
if (last_texture != p_texture->get_rid()) {
315+
RS::get_singleton()->material_set_param(get_material(), "texture_albedo", p_texture->get_rid());
316+
RS::get_singleton()->material_set_param(get_material(), "albedo_texture_size", Vector2i(p_texture->get_width(), p_texture->get_height()));
317+
last_texture = p_texture->get_rid();
318+
}
319+
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
320+
RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
321+
RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material());
322+
}
323+
324+
RS::get_singleton()->material_set_param(get_material(), "alpha_scissor_threshold", alpha_scissor_threshold);
325+
RS::get_singleton()->material_set_param(get_material(), "alpha_hash_scale", alpha_hash_scale);
326+
RS::get_singleton()->material_set_param(get_material(), "alpha_antialiasing_edge", alpha_antialiasing_edge);
327+
328+
if (sprite_sharing_enabled) {
329+
SpriteMeshKey sprite_mesh_key;
330+
sprite_mesh_key.final_rect = final_rect;
331+
sprite_mesh_key.final_src_rect = final_src_rect;
332+
sprite_mesh_key.v_color = *(uint32_t *)v_color;
333+
sprite_mesh_key.v_normal = v_normal;
334+
sprite_mesh_key.shader = last_shader;
335+
sprite_mesh_key.texture = last_texture;
336+
sprite_mesh_key.px_size = px_size;
337+
sprite_mesh_key.render_priority = render_priority;
338+
sprite_mesh_key.axis = axis;
339+
sprite_mesh_key.hflip = hflip;
340+
sprite_mesh_key.vflip = vflip;
341+
sprite_mesh_key.alpha_cut_disabled = get_alpha_cut_mode() == ALPHA_CUT_DISABLED;
342+
sprite_mesh_key.alpha_antialiasing_edge = alpha_antialiasing_edge;
343+
sprite_mesh_key.alpha_hash_scale = alpha_hash_scale;
344+
sprite_mesh_key.alpha_scissor_threshold = alpha_scissor_threshold;
345+
if (sharing_own_mesh) {
346+
if (last_sprite_mesh_key != sprite_mesh_key) {
347+
// Sprite mesh data changed.
348+
_stop_sharing_sprite();
349+
} else {
350+
// Sprite mesh data unchanged.
351+
return;
352+
}
353+
}
354+
// Try to see if there's any sprite whose mesh can be used instead.
355+
SpriteBase3D **sprite_ptr = shared_sprites.getptr(sprite_mesh_key);
356+
last_sprite_mesh_key = sprite_mesh_key;
357+
if (sprite_ptr) {
358+
if (*sprite_ptr != using_sprite) {
359+
// Found new sprite that can be reused.
360+
_start_using_sprite(*sprite_ptr);
361+
return;
362+
} else {
363+
// Keep using same sprite.
364+
return;
365+
}
366+
}
367+
// Otherwise, setup mesh data and register this sprite's mesh for sharing.
368+
_start_sharing_sprite();
369+
if (using_sprite) {
370+
// This sprite may have been sharing another sprite's mesh before, so we need to stop that here.
371+
_stop_using_sprite();
372+
}
373+
}
374+
224375
for (int i = 0; i < 4; i++) {
225376
Vector3 vtx;
226377
vtx[x_axis] = vertices[i][0];
@@ -264,46 +415,13 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
264415
break;
265416
}
266417

267-
RID mesh_new = get_mesh();
418+
RID mesh_new = mesh;
419+
set_base(mesh_new);
268420
RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer);
269421
RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer);
270422

271423
RS::get_singleton()->mesh_set_custom_aabb(mesh_new, aabb_new);
272424
set_aabb(aabb_new);
273-
274-
RS::get_singleton()->material_set_param(get_material(), "alpha_scissor_threshold", alpha_scissor_threshold);
275-
RS::get_singleton()->material_set_param(get_material(), "alpha_hash_scale", alpha_hash_scale);
276-
RS::get_singleton()->material_set_param(get_material(), "alpha_antialiasing_edge", alpha_antialiasing_edge);
277-
278-
BaseMaterial3D::Transparency mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_DISABLED;
279-
if (get_draw_flag(FLAG_TRANSPARENT)) {
280-
if (get_alpha_cut_mode() == ALPHA_CUT_DISCARD) {
281-
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_SCISSOR;
282-
} else if (get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS) {
283-
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS;
284-
} else if (get_alpha_cut_mode() == ALPHA_CUT_HASH) {
285-
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_HASH;
286-
} else {
287-
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA;
288-
}
289-
}
290-
291-
RID shader_rid;
292-
StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), mat_transparency, get_draw_flag(FLAG_DOUBLE_SIDED), get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), alpha_antialiasing_mode, &shader_rid);
293-
294-
if (last_shader != shader_rid) {
295-
RS::get_singleton()->material_set_shader(get_material(), shader_rid);
296-
last_shader = shader_rid;
297-
}
298-
if (last_texture != p_texture->get_rid()) {
299-
RS::get_singleton()->material_set_param(get_material(), "texture_albedo", p_texture->get_rid());
300-
RS::get_singleton()->material_set_param(get_material(), "albedo_texture_size", Vector2i(p_texture->get_width(), p_texture->get_height()));
301-
last_texture = p_texture->get_rid();
302-
}
303-
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
304-
RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
305-
RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material());
306-
}
307425
}
308426

309427
void SpriteBase3D::set_centered(bool p_center) {
@@ -697,6 +815,14 @@ void SpriteBase3D::_bind_methods() {
697815
}
698816

699817
SpriteBase3D::SpriteBase3D() {
818+
static bool static_initialized = false;
819+
if (!static_initialized) {
820+
static_initialized = true;
821+
// Auto-instancing isn't supported in the compatibility renderer.
822+
// In every other renderer, we enable sprite sharing if it is not enabled yet.
823+
sprite_sharing_enabled = sprite_sharing_enabled || RS::get_singleton()->get_current_rendering_method() != "gl_compatibility";
824+
}
825+
700826
for (int i = 0; i < FLAG_MAX; i++) {
701827
flags[i] = i == FLAG_TRANSPARENT || i == FLAG_DOUBLE_SIDED;
702828
}
@@ -774,6 +900,14 @@ SpriteBase3D::~SpriteBase3D() {
774900
ERR_FAIL_NULL(RenderingServer::get_singleton());
775901
RenderingServer::get_singleton()->free(mesh);
776902
RenderingServer::get_singleton()->free(material);
903+
904+
if (sprite_sharing_enabled && sharing_own_mesh) {
905+
_stop_sharing_sprite();
906+
}
907+
908+
if (using_sprite) {
909+
_stop_using_sprite();
910+
}
777911
}
778912

779913
///////////////////////////////////////////

scene/3d/sprite_3d.h

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

36+
struct SpriteMeshKey {
37+
// Mesh vertices are derived from final_rect.position, final_rect.size, axis, and px_size
38+
// axis and px_size are moved down to improve struct packing.
39+
Rect2 final_rect;
40+
// Mesh uvs are derived from final_src_rect.position, final_src_rect.size, hflip, vflip and texture size
41+
// Texture size depends on the texture, and we're already including the texture for material checks,
42+
// so we won't include texture size here.
43+
// hflip and vflip are moved down to improve struct packing.
44+
Rect2 final_src_rect;
45+
uint32_t v_color;
46+
uint32_t v_normal;
47+
RID shader;
48+
RID texture;
49+
real_t px_size;
50+
int render_priority;
51+
uint8_t axis; // Vector3::Axis will only ever have 3 members. We store it as a uint8_t instead of int to save space.
52+
bool hflip;
53+
bool vflip;
54+
bool alpha_cut_disabled;
55+
float alpha_scissor_threshold;
56+
float alpha_hash_scale;
57+
float alpha_antialiasing_edge;
58+
bool operator==(const SpriteMeshKey &other) const {
59+
return memcmp(this, &other, sizeof(SpriteMeshKey)) == 0;
60+
}
61+
bool operator!=(const SpriteMeshKey &other) const {
62+
return memcmp(this, &other, sizeof(SpriteMeshKey)) != 0;
63+
}
64+
};
65+
66+
struct SpriteMeshHasher {
67+
static _FORCE_INLINE_ uint32_t hash(const SpriteMeshKey &p_mesh_key) {
68+
return hash_murmur3_buffer(&p_mesh_key, sizeof(SpriteMeshKey));
69+
}
70+
};
71+
3672
class SpriteBase3D : public GeometryInstance3D {
3773
GDCLASS(SpriteBase3D, GeometryInstance3D);
3874

@@ -58,6 +94,13 @@ class SpriteBase3D : public GeometryInstance3D {
5894
};
5995

6096
private:
97+
inline static bool sprite_sharing_enabled = false;
98+
Vector<SpriteBase3D *> users;
99+
SpriteMeshKey last_sprite_mesh_key;
100+
SpriteBase3D *using_sprite = nullptr;
101+
int using_sprite_user_index = -1; // Used to invalidate this sprite's entry in another sprite's users vector.
102+
bool sharing_own_mesh = false;
103+
61104
bool color_dirty = true;
62105
Color color_accum;
63106

@@ -96,6 +139,10 @@ class SpriteBase3D : public GeometryInstance3D {
96139
void _im_update();
97140

98141
void _propagate_color_changed();
142+
void _start_sharing_sprite();
143+
void _stop_sharing_sprite();
144+
void _start_using_sprite(SpriteBase3D *p_using_sprite);
145+
void _stop_using_sprite();
99146

100147
protected:
101148
Color _get_color_accum();
@@ -104,7 +151,7 @@ class SpriteBase3D : public GeometryInstance3D {
104151
virtual void _draw() = 0;
105152
void draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect);
106153
_FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; }
107-
_FORCE_INLINE_ RID &get_mesh() { return mesh; }
154+
_FORCE_INLINE_ RID &get_mesh() { return using_sprite ? using_sprite->mesh : mesh; }
108155
_FORCE_INLINE_ RID &get_material() { return material; }
109156

110157
uint32_t mesh_surface_offsets[RS::ARRAY_MAX];
@@ -173,6 +220,13 @@ class SpriteBase3D : public GeometryInstance3D {
173220

174221
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
175222

223+
_FORCE_INLINE_ static void set_sprite_sharing_enabled(bool p_sprite_sharing_enabled) {
224+
sprite_sharing_enabled = p_sprite_sharing_enabled;
225+
}
226+
_FORCE_INLINE_ static bool is_sprite_sharing_enabled() {
227+
return sprite_sharing_enabled;
228+
}
229+
176230
SpriteBase3D();
177231
~SpriteBase3D();
178232
};

0 commit comments

Comments
 (0)