Skip to content

Commit 7b973bb

Browse files
committed
Reuse Sprite3D meshes across nodes when possible.
1 parent b89c47b commit 7b973bb

File tree

2 files changed

+177
-36
lines changed

2 files changed

+177
-36
lines changed

scene/3d/sprite_3d.cpp

Lines changed: 143 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 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(successor->get_material(), get_render_priority());
91+
RS::get_singleton()->mesh_surface_set_material(successor->mesh, 0, successor->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,84 @@ 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+
BaseMaterial3D::Transparency mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_DISABLED;
273+
if (get_draw_flag(FLAG_TRANSPARENT)) {
274+
if (get_alpha_cut_mode() == ALPHA_CUT_DISCARD) {
275+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_SCISSOR;
276+
} else if (get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS) {
277+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS;
278+
} else if (get_alpha_cut_mode() == ALPHA_CUT_HASH) {
279+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_HASH;
280+
} else {
281+
mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA;
282+
}
283+
}
284+
285+
RID shader_rid;
286+
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);
287+
288+
if (last_shader != shader_rid) {
289+
RS::get_singleton()->material_set_shader(get_material(), shader_rid);
290+
last_shader = shader_rid;
291+
}
292+
if (last_texture != p_texture->get_rid()) {
293+
RS::get_singleton()->material_set_param(get_material(), "texture_albedo", p_texture->get_rid());
294+
RS::get_singleton()->material_set_param(get_material(), "albedo_texture_size", Vector2i(p_texture->get_width(), p_texture->get_height()));
295+
last_texture = p_texture->get_rid();
296+
}
297+
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
298+
RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
299+
RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material());
300+
}
301+
302+
RS::get_singleton()->material_set_param(get_material(), "alpha_scissor_threshold", alpha_scissor_threshold);
303+
RS::get_singleton()->material_set_param(get_material(), "alpha_hash_scale", alpha_hash_scale);
304+
RS::get_singleton()->material_set_param(get_material(), "alpha_antialiasing_edge", alpha_antialiasing_edge);
305+
306+
if (instancing_enabled) {
307+
SpriteMeshKey sprite_mesh_key;
308+
memcpy(&sprite_mesh_key.vertices, vertices, sizeof(Vector2) * 4);
309+
memcpy(&sprite_mesh_key.uvs, uvs, sizeof(Vector2) * 4);
310+
sprite_mesh_key.v_color = *(uint32_t *)v_color;
311+
sprite_mesh_key.v_normal = v_normal;
312+
sprite_mesh_key.shader = last_shader;
313+
sprite_mesh_key.texture = last_texture;
314+
sprite_mesh_key.render_priority = render_priority;
315+
sprite_mesh_key.alpha_cut_disabled = get_alpha_cut_mode() == ALPHA_CUT_DISABLED;
316+
sprite_mesh_key.alpha_antialiasing_edge = alpha_antialiasing_edge;
317+
sprite_mesh_key.alpha_hash_scale = alpha_hash_scale;
318+
sprite_mesh_key.alpha_scissor_threshold = alpha_scissor_threshold;
319+
if (sharing_own_mesh) {
320+
if (last_sprite_mesh_key != sprite_mesh_key) {
321+
// Sprite mesh data changed.
322+
_stop_sharing_sprite();
323+
} else {
324+
// Sprite mesh data unchanged.
325+
return;
326+
}
327+
}
328+
// Try to see if there's any sprite whose mesh can be used instead.
329+
SpriteBase3D **sprite_ptr = shared_sprites.getptr(sprite_mesh_key);
330+
last_sprite_mesh_key = sprite_mesh_key;
331+
if (sprite_ptr) {
332+
if (*sprite_ptr != using_sprite) {
333+
// Found new sprite that can be reused.
334+
using_sprite = *sprite_ptr;
335+
set_base(using_sprite->mesh);
336+
using_sprite_user_index = using_sprite->users.size();
337+
using_sprite->users.push_back(this);
338+
// 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.
339+
return;
340+
} else {
341+
// Keep using same sprite.
342+
return;
343+
}
344+
}
345+
// Otherwise, setup mesh data and register this sprite's mesh for sharing.
346+
shared_sprites.insert(sprite_mesh_key, this);
347+
sharing_own_mesh = true;
348+
}
349+
224350
for (int i = 0; i < 4; i++) {
225351
Vector3 vtx;
226352
vtx[x_axis] = vertices[i][0];
@@ -264,46 +390,13 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
264390
break;
265391
}
266392

267-
RID mesh_new = get_mesh();
393+
RID mesh_new = mesh;
394+
set_base(mesh_new);
268395
RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer);
269396
RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer);
270397

271398
RS::get_singleton()->mesh_set_custom_aabb(mesh_new, aabb_new);
272399
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-
}
307400
}
308401

309402
void SpriteBase3D::set_centered(bool p_center) {
@@ -697,6 +790,13 @@ void SpriteBase3D::_bind_methods() {
697790
}
698791

699792
SpriteBase3D::SpriteBase3D() {
793+
static bool static_initialized = false;
794+
if (!static_initialized) {
795+
static_initialized = true;
796+
// Auto-instancing isn't supported in the compatibility renderer.
797+
instancing_enabled = RS::get_singleton()->get_current_rendering_method() != "gl_compatibility";
798+
}
799+
700800
for (int i = 0; i < FLAG_MAX; i++) {
701801
flags[i] = i == FLAG_TRANSPARENT || i == FLAG_DOUBLE_SIDED;
702802
}
@@ -774,6 +874,14 @@ SpriteBase3D::~SpriteBase3D() {
774874
ERR_FAIL_NULL(RenderingServer::get_singleton());
775875
RenderingServer::get_singleton()->free(mesh);
776876
RenderingServer::get_singleton()->free(material);
877+
878+
if (instancing_enabled && sharing_own_mesh) {
879+
_stop_sharing_sprite();
880+
}
881+
882+
if (using_sprite) {
883+
using_sprite->users.ptrw()[using_sprite_user_index] = nullptr;
884+
}
777885
}
778886

779887
///////////////////////////////////////////

scene/3d/sprite_3d.h

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

36+
struct SpriteMeshKey {
37+
Vector2 vertices[4];
38+
Vector2 uvs[4];
39+
uint32_t v_color;
40+
uint32_t v_normal;
41+
RID shader;
42+
RID texture;
43+
int render_priority;
44+
bool alpha_cut_disabled;
45+
float alpha_scissor_threshold;
46+
float alpha_hash_scale;
47+
float alpha_antialiasing_edge;
48+
bool operator==(const SpriteMeshKey &other) const {
49+
return memcmp(this, &other, sizeof(SpriteMeshKey)) == 0;
50+
}
51+
bool operator!=(const SpriteMeshKey &other) const {
52+
return memcmp(this, &other, sizeof(SpriteMeshKey)) != 0;
53+
}
54+
};
55+
56+
struct SpriteMeshHasher {
57+
static _FORCE_INLINE_ uint32_t hash(const SpriteMeshKey &p_mesh_key) {
58+
return hash_murmur3_buffer(&p_mesh_key, sizeof(SpriteMeshKey));
59+
}
60+
};
61+
3662
class SpriteBase3D : public GeometryInstance3D {
3763
GDCLASS(SpriteBase3D, GeometryInstance3D);
3864

@@ -58,6 +84,12 @@ class SpriteBase3D : public GeometryInstance3D {
5884
};
5985

6086
private:
87+
Vector<SpriteBase3D *> users;
88+
SpriteMeshKey last_sprite_mesh_key;
89+
SpriteBase3D *using_sprite = nullptr;
90+
int using_sprite_user_index = -1; // Used to invalidate this sprite's entry in another sprite's users vector.
91+
bool sharing_own_mesh = false;
92+
6193
bool color_dirty = true;
6294
Color color_accum;
6395

@@ -96,6 +128,7 @@ class SpriteBase3D : public GeometryInstance3D {
96128
void _im_update();
97129

98130
void _propagate_color_changed();
131+
void _stop_sharing_sprite();
99132

100133
protected:
101134
Color _get_color_accum();
@@ -104,7 +137,7 @@ class SpriteBase3D : public GeometryInstance3D {
104137
virtual void _draw() = 0;
105138
void draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect);
106139
_FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; }
107-
_FORCE_INLINE_ RID &get_mesh() { return mesh; }
140+
_FORCE_INLINE_ RID &get_mesh() { return using_sprite ? using_sprite->mesh : mesh; }
108141
_FORCE_INLINE_ RID &get_material() { return material; }
109142

110143
uint32_t mesh_surface_offsets[RS::ARRAY_MAX];

0 commit comments

Comments
 (0)