Skip to content

Commit 0d1ca8c

Browse files
committed
Reuse Sprite3D meshes across nodes when possible.
1 parent 42c7f14 commit 0d1ca8c

File tree

4 files changed

+656
-36
lines changed

4 files changed

+656
-36
lines changed

scene/3d/sprite_3d.cpp

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

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

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

70+
void SpriteBase3D::_start_sharing_sprite() {
71+
shared_sprites.insert(last_sprite_mesh_key, this);
72+
sharing_own_mesh = true;
73+
}
74+
75+
void SpriteBase3D::_stop_sharing_sprite() {
76+
shared_sprites.erase(last_sprite_mesh_key);
77+
sharing_own_mesh = false;
78+
if (!users.is_empty()) {
79+
// Select a successor and make every other user use that successor's mesh instead.
80+
SpriteBase3D *successor = nullptr;
81+
int i = 0;
82+
for (; i < users.size(); i++) {
83+
// There may be sprites that have changed the sprite they're using earlier this frame, so we have to filter them out.
84+
if (users[i] && users[i]->using_sprite == this) {
85+
successor = users[i];
86+
successor->_start_sharing_sprite();
87+
// 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.
88+
// memcpy is used directly because buffer sizes are guaranteed to be identical across all SpriteBase3Ds.
89+
memcpy(successor->vertex_buffer.ptrw(), vertex_buffer.ptr(), vertex_buffer.size());
90+
memcpy(successor->attribute_buffer.ptrw(), attribute_buffer.ptr(), attribute_buffer.size());
91+
RS::get_singleton()->mesh_surface_update_vertex_region(successor->mesh, 0, 0, successor->vertex_buffer);
92+
RS::get_singleton()->mesh_surface_update_attribute_region(successor->mesh, 0, 0, successor->attribute_buffer);
93+
RS::get_singleton()->mesh_set_custom_aabb(successor->mesh, aabb);
94+
successor->using_sprite = nullptr;
95+
successor->set_base(successor->mesh);
96+
successor->set_aabb(aabb);
97+
i++; // Skip the successor for the next for-loop.
98+
break;
99+
}
100+
}
101+
// Propagate the change to remaining users that haven't changed their using_sprite.
102+
// Note: This works even if the same user is registered twice. Very rare but can still happen.
103+
for (; i < users.size(); i++) {
104+
if (users[i] && users[i]->using_sprite == this) {
105+
users[i]->_start_using_sprite(successor);
106+
}
107+
}
108+
// Now every user has moved on, we can clear the users list.
109+
users.clear();
110+
}
111+
}
112+
113+
void SpriteBase3D::_start_using_sprite(SpriteBase3D *p_using_sprite) {
114+
using_sprite = p_using_sprite;
115+
using_sprite_user_index = using_sprite->users.size();
116+
set_base(using_sprite->mesh);
117+
set_aabb(using_sprite->aabb);
118+
using_sprite->users.push_back(this);
119+
// We don't need to remove this sprite from the previous shared sprite's users list,
120+
// as setting using_sprite means they will be detected and filtered out later.
121+
}
122+
123+
void SpriteBase3D::_stop_using_sprite() {
124+
using_sprite->users.write[using_sprite_user_index] = nullptr;
125+
using_sprite = nullptr;
126+
}
127+
67128
void SpriteBase3D::_notification(int p_what) {
68129
switch (p_what) {
69130
case NOTIFICATION_ENTER_TREE: {
@@ -221,6 +282,92 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
221282
uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0))
222283
};
223284

285+
BaseMaterial3D::Transparency mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_DISABLED;
286+
if (get_draw_flag(FLAG_TRANSPARENT)) {
287+
if (get_alpha_cut_mode() == ALPHA_CUT_DISCARD) {
288+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_SCISSOR;
289+
} else if (get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS) {
290+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS;
291+
} else if (get_alpha_cut_mode() == ALPHA_CUT_HASH) {
292+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_HASH;
293+
} else {
294+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA;
295+
}
296+
}
297+
298+
RID shader_rid;
299+
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);
300+
301+
if (last_shader != shader_rid) {
302+
RS::get_singleton()->material_set_shader(get_material(), shader_rid);
303+
last_shader = shader_rid;
304+
}
305+
if (last_texture != p_texture->get_rid()) {
306+
RS::get_singleton()->material_set_param(get_material(), "texture_albedo", p_texture->get_rid());
307+
RS::get_singleton()->material_set_param(get_material(), "albedo_texture_size", Vector2i(p_texture->get_width(), p_texture->get_height()));
308+
last_texture = p_texture->get_rid();
309+
}
310+
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
311+
RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
312+
RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material());
313+
}
314+
315+
RS::get_singleton()->material_set_param(get_material(), "alpha_scissor_threshold", alpha_scissor_threshold);
316+
RS::get_singleton()->material_set_param(get_material(), "alpha_hash_scale", alpha_hash_scale);
317+
RS::get_singleton()->material_set_param(get_material(), "alpha_antialiasing_edge", alpha_antialiasing_edge);
318+
319+
if (sprite_sharing_enabled) {
320+
SpriteMeshKey sprite_mesh_key;
321+
sprite_mesh_key.final_rect = final_rect;
322+
sprite_mesh_key.final_src_rect = final_src_rect;
323+
sprite_mesh_key.v_color = *(uint32_t *)v_color;
324+
sprite_mesh_key.v_normal = v_normal;
325+
sprite_mesh_key.shader = last_shader;
326+
sprite_mesh_key.texture = last_texture;
327+
sprite_mesh_key.px_size = px_size;
328+
sprite_mesh_key.render_priority = render_priority;
329+
sprite_mesh_key.axis = axis;
330+
sprite_mesh_key.hflip = hflip;
331+
sprite_mesh_key.vflip = vflip;
332+
sprite_mesh_key.alpha_cut_disabled = get_alpha_cut_mode() == ALPHA_CUT_DISABLED;
333+
sprite_mesh_key.alpha_antialiasing_edge = alpha_antialiasing_edge;
334+
sprite_mesh_key.alpha_hash_scale = alpha_hash_scale;
335+
sprite_mesh_key.alpha_scissor_threshold = alpha_scissor_threshold;
336+
if (sharing_own_mesh) {
337+
if (last_sprite_mesh_key != sprite_mesh_key) {
338+
// Sprite mesh data changed.
339+
_stop_sharing_sprite();
340+
} else {
341+
// Sprite mesh data unchanged.
342+
return;
343+
}
344+
}
345+
// Try to see if there's any sprite whose mesh can be used instead.
346+
const SpriteMeshKey *begin_key = shared_sprites.is_empty() ? nullptr : &shared_sprites.begin()->key;
347+
uint32_t begin_hash = begin_key ? SpriteMeshKey::hash(*begin_key) : 0;
348+
uint32_t current_hash = SpriteMeshKey::hash(sprite_mesh_key);
349+
auto begin_key_bytes = reinterpret_cast<const uint8_t *>(begin_key);
350+
auto sprite_mesh_key_bytes = reinterpret_cast<const uint8_t *>(&sprite_mesh_key);
351+
SpriteBase3D **sprite_ptr = shared_sprites.getptr(sprite_mesh_key);
352+
last_sprite_mesh_key = sprite_mesh_key;
353+
if (sprite_ptr) {
354+
if (*sprite_ptr != using_sprite) {
355+
// Found new sprite that can be reused.
356+
_start_using_sprite(*sprite_ptr);
357+
return;
358+
} else {
359+
// Keep using same sprite.
360+
return;
361+
}
362+
}
363+
// Otherwise, setup mesh data and register this sprite's mesh for sharing.
364+
_start_sharing_sprite();
365+
if (using_sprite) {
366+
// This sprite may have been sharing another sprite's mesh before, so we need to stop that here.
367+
_stop_using_sprite();
368+
}
369+
}
370+
224371
for (int i = 0; i < 4; i++) {
225372
Vector3 vtx;
226373
vtx[x_axis] = vertices[i][0];
@@ -264,46 +411,13 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
264411
break;
265412
}
266413

267-
RID mesh_new = get_mesh();
414+
RID mesh_new = mesh;
415+
set_base(mesh_new);
268416
RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer);
269417
RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer);
270418

271419
RS::get_singleton()->mesh_set_custom_aabb(mesh_new, aabb_new);
272420
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-
}
307421
}
308422

309423
void SpriteBase3D::set_centered(bool p_center) {
@@ -697,6 +811,13 @@ void SpriteBase3D::_bind_methods() {
697811
}
698812

699813
SpriteBase3D::SpriteBase3D() {
814+
static bool static_initialized = false;
815+
if (!static_initialized) {
816+
static_initialized = true;
817+
// Auto-instancing isn't supported in the compatibility renderer.
818+
sprite_sharing_enabled = RS::get_singleton()->get_current_rendering_method() != "gl_compatibility";
819+
}
820+
700821
for (int i = 0; i < FLAG_MAX; i++) {
701822
flags[i] = i == FLAG_TRANSPARENT || i == FLAG_DOUBLE_SIDED;
702823
}
@@ -774,6 +895,14 @@ SpriteBase3D::~SpriteBase3D() {
774895
ERR_FAIL_NULL(RenderingServer::get_singleton());
775896
RenderingServer::get_singleton()->free(mesh);
776897
RenderingServer::get_singleton()->free(material);
898+
899+
if (sprite_sharing_enabled && sharing_own_mesh) {
900+
_stop_sharing_sprite();
901+
}
902+
903+
if (using_sprite) {
904+
_stop_using_sprite();
905+
}
777906
}
778907

779908
///////////////////////////////////////////

scene/3d/sprite_3d.h

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

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

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

@@ -96,6 +138,10 @@ class SpriteBase3D : public GeometryInstance3D {
96138
void _im_update();
97139

98140
void _propagate_color_changed();
141+
void _start_sharing_sprite();
142+
void _stop_sharing_sprite();
143+
void _start_using_sprite(SpriteBase3D *p_using_sprite);
144+
void _stop_using_sprite();
99145

100146
protected:
101147
Color _get_color_accum();
@@ -104,7 +150,7 @@ class SpriteBase3D : public GeometryInstance3D {
104150
virtual void _draw() = 0;
105151
void draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect);
106152
_FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; }
107-
_FORCE_INLINE_ RID &get_mesh() { return mesh; }
153+
_FORCE_INLINE_ RID &get_mesh() { return using_sprite ? using_sprite->mesh : mesh; }
108154
_FORCE_INLINE_ RID &get_material() { return material; }
109155

110156
uint32_t mesh_surface_offsets[RS::ARRAY_MAX];

0 commit comments

Comments
 (0)