|
32 | 32 |
|
33 | 33 | #include "scene/resources/atlas_texture.h" |
34 | 34 |
|
| 35 | +static bool instancing_enabled = false; |
| 36 | +static HashMap<SpriteMeshKey, SpriteBase3D *, SpriteMeshHasher> shared_sprites; |
| 37 | + |
35 | 38 | Color SpriteBase3D::_get_color_accum() { |
36 | 39 | if (!color_dirty) { |
37 | 40 | return color_accum; |
@@ -64,6 +67,51 @@ void SpriteBase3D::_propagate_color_changed() { |
64 | 67 | } |
65 | 68 | } |
66 | 69 |
|
| 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 | + |
67 | 115 | void SpriteBase3D::_notification(int p_what) { |
68 | 116 | switch (p_what) { |
69 | 117 | case NOTIFICATION_ENTER_TREE: { |
@@ -221,6 +269,44 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, |
221 | 269 | uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0)) |
222 | 270 | }; |
223 | 271 |
|
| 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 | + |
224 | 310 | for (int i = 0; i < 4; i++) { |
225 | 311 | Vector3 vtx; |
226 | 312 | vtx[x_axis] = vertices[i][0]; |
@@ -264,7 +350,8 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, |
264 | 350 | break; |
265 | 351 | } |
266 | 352 |
|
267 | | - RID mesh_new = get_mesh(); |
| 353 | + RID mesh_new = mesh; |
| 354 | + set_base(mesh_new); |
268 | 355 | RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer); |
269 | 356 | RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer); |
270 | 357 |
|
@@ -697,6 +784,13 @@ void SpriteBase3D::_bind_methods() { |
697 | 784 | } |
698 | 785 |
|
699 | 786 | SpriteBase3D::SpriteBase3D() { |
| 787 | + static bool static_initialized = false; |
| 788 | + if (!static_initialized) { |
| 789 | + static_initialized = true; |
| 790 | + // Auto-instancing isn't supported in the compatibility renderer. |
| 791 | + instancing_enabled = RS::get_singleton()->get_current_rendering_method() != "gl_compatibility"; |
| 792 | + } |
| 793 | + |
700 | 794 | for (int i = 0; i < FLAG_MAX; i++) { |
701 | 795 | flags[i] = i == FLAG_TRANSPARENT || i == FLAG_DOUBLE_SIDED; |
702 | 796 | } |
@@ -774,6 +868,14 @@ SpriteBase3D::~SpriteBase3D() { |
774 | 868 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
775 | 869 | RenderingServer::get_singleton()->free(mesh); |
776 | 870 | RenderingServer::get_singleton()->free(material); |
| 871 | + |
| 872 | + if (instancing_enabled && sharing_own_mesh) { |
| 873 | + _stop_sharing_sprite(); |
| 874 | + } |
| 875 | + |
| 876 | + if (using_sprite) { |
| 877 | + using_sprite->users.ptrw()[using_sprite_user_index] = nullptr; |
| 878 | + } |
777 | 879 | } |
778 | 880 |
|
779 | 881 | /////////////////////////////////////////// |
|
0 commit comments