diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h index 05de4a58d57..a364636e378 100644 --- a/drivers/gles3/storage/mesh_storage.h +++ b/drivers/gles3/storage/mesh_storage.h @@ -319,6 +319,7 @@ public: virtual void mesh_clear(RID p_mesh) override; virtual void mesh_surface_remove(RID p_mesh, int p_surface) override; + virtual void mesh_debug_usage(List *r_info) override {} _FORCE_INLINE_ const RID *mesh_get_surface_count_and_materials(RID p_mesh, uint32_t &r_surface_count) { Mesh *mesh = mesh_owner.get_or_null(p_mesh); diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 7035a79d5e5..7c52c14ea5d 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1481,6 +1481,7 @@ void TextureStorage::texture_debug_usage(List *r_info) { tinfo.width = t->alloc_width; tinfo.height = t->alloc_height; tinfo.bytes = t->total_data_size; + tinfo.type = static_cast(t->type); switch (t->type) { case Texture::TYPE_3D: diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 6efe1be8d37..87176df07ff 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -451,9 +451,19 @@ void ScriptEditorDebugger::_msg_servers_memory_usage(uint64_t p_thread_id, const it->set_text(3, String::humanize_size(bytes)); total += bytes; - if (has_theme_icon(type, EditorStringName(EditorIcons))) { - it->set_icon(0, get_editor_theme_icon(type)); + // If it does not have a theme icon, just go up the inheritance tree until we find one. + if (!has_theme_icon(type, EditorStringName(EditorIcons))) { + StringName base_type = type; + while (base_type != "Resource" || base_type != "") { + base_type = ClassDB::get_parent_class(base_type); + if (has_theme_icon(base_type, EditorStringName(EditorIcons))) { + type = base_type; + break; + } + } } + + it->set_icon(0, get_editor_theme_icon(type)); } vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total)); @@ -1004,6 +1014,7 @@ void ScriptEditorDebugger::_notification(int p_what) { next->set_button_icon(get_editor_theme_icon(SNAME("DebugNext"))); dobreak->set_button_icon(get_editor_theme_icon(SNAME("Pause"))); docontinue->set_button_icon(get_editor_theme_icon(SNAME("DebugContinue"))); + vmem_notice_icon->set_texture(get_editor_theme_icon(SNAME("NodeInfo"))); vmem_refresh->set_button_icon(get_editor_theme_icon(SNAME("Reload"))); vmem_export->set_button_icon(get_editor_theme_icon(SNAME("Save"))); search->set_right_icon(get_editor_theme_icon(SNAME("Search"))); @@ -2184,11 +2195,32 @@ ScriptEditorDebugger::ScriptEditorDebugger() { { //vmem inspect VBoxContainer *vmem_vb = memnew(VBoxContainer); HBoxContainer *vmem_hb = memnew(HBoxContainer); - Label *vmlb = memnew(Label(TTR("List of Video Memory Usage by Resource:") + " ")); - vmlb->set_theme_type_variation("HeaderSmall"); - vmlb->set_h_size_flags(SIZE_EXPAND_FILL); + Label *vmlb = memnew(Label(TTRC("List of Video Memory Usage by Resource:"))); + vmlb->set_theme_type_variation("HeaderSmall"); vmem_hb->add_child(vmlb); + + { // Add notice icon. + vmem_notice_icon = memnew(TextureRect); + vmem_notice_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + vmem_notice_icon->set_h_size_flags(SIZE_SHRINK_CENTER); + vmem_notice_icon->set_visible(true); + vmem_notice_icon->set_tooltip_text(TTR(R"(Notice: +This tool only reports memory allocations tracked by the engine. +Therefore, total VRAM usage is inaccurate compared to what the Monitors tab or external tools can report. +Instead, use the monitors tab to obtain more precise VRAM usage. + +- Buffer Memory (e.g. GPUParticles) is not tracked. +- Meshes are not tracked in the Compatibility renderer.)")); + vmem_hb->add_child(vmem_notice_icon); + } + + { // Add some space to move the rest of the controls to the right. + Control *space = memnew(Control); + space->set_h_size_flags(SIZE_EXPAND_FILL); + vmem_hb->add_child(space); + } + vmem_hb->add_child(memnew(Label(TTR("Total:") + " "))); vmem_total = memnew(LineEdit); vmem_total->set_editable(false); diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index ff736cd3ba2..ac8e3fc7cd8 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -137,6 +137,7 @@ private: Button *vmem_refresh = nullptr; Button *vmem_export = nullptr; LineEdit *vmem_total = nullptr; + TextureRect *vmem_notice_icon = nullptr; Tree *stack_dump = nullptr; LineEdit *search = nullptr; diff --git a/servers/debugger/servers_debugger.cpp b/servers/debugger/servers_debugger.cpp index af784629c0c..eba247cbe1f 100644 --- a/servers/debugger/servers_debugger.cpp +++ b/servers/debugger/servers_debugger.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/debugger/engine_debugger.h" #include "core/debugger/engine_profiler.h" +#include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "servers/display_server.h" @@ -435,7 +436,24 @@ void ServersDebugger::_send_resource_usage() { info.path = E.path; info.vram = E.bytes; info.id = E.texture; - info.type = "Texture"; + + switch (E.type) { + case RS::TextureType::TEXTURE_TYPE_2D: + info.type = "Texture2D"; + break; + case RS::TextureType::TEXTURE_TYPE_3D: + info.type = "Texture3D"; + break; + case RS::TextureType::TEXTURE_TYPE_LAYERED: + info.type = "TextureLayered"; + break; + } + + String possible_type = _get_resource_type_from_path(E.path); + if (!possible_type.is_empty()) { + info.type = possible_type; + } + if (E.depth == 0) { info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format); } else { @@ -444,9 +462,61 @@ void ServersDebugger::_send_resource_usage() { usage.infos.push_back(info); } + List mesh_info; + RS::get_singleton()->mesh_debug_usage(&mesh_info); + + for (const RS::MeshInfo &E : mesh_info) { + ServersDebugger::ResourceInfo info; + info.path = E.path; + // We use 64-bit integers to avoid overflow, if for whatever reason, the sum is bigger than 4GB. + uint64_t vram = E.vertex_buffer_size + E.attribute_buffer_size + E.skin_buffer_size + E.index_buffer_size + E.blend_shape_buffer_size + E.lod_index_buffers_size; + // But can info.vram even hold that, and why is it an int instead of an uint? + info.vram = vram; + + // Even though these empty meshes can be indicative of issues somewhere else + // for UX reasons, we don't want to show them. + if (vram == 0 && E.path.is_empty()) { + continue; + } + + info.id = E.mesh; + info.type = "Mesh"; + String possible_type = _get_resource_type_from_path(E.path); + if (!possible_type.is_empty()) { + info.type = possible_type; + } + + info.format = itos(E.vertex_count) + " Vertices"; + usage.infos.push_back(info); + } + EngineDebugger::get_singleton()->send_message("servers:memory_usage", usage.serialize()); } +// Done on a best-effort basis. +String ServersDebugger::_get_resource_type_from_path(const String &p_path) { + if (p_path.is_empty()) { + return ""; + } + + if (!ResourceLoader::exists(p_path)) { + return ""; + } + + if (ResourceCache::has(p_path)) { + Ref resource = ResourceCache::get_ref(p_path); + return resource->get_class(); + } else { + // This doesn't work all the time for embedded resources. + String resource_type = ResourceLoader::get_resource_type(p_path); + if (resource_type != "") { + return resource_type; + } + } + + return ""; +} + ServersDebugger::ServersDebugger() { singleton = this; diff --git a/servers/debugger/servers_debugger.h b/servers/debugger/servers_debugger.h index 4e2aa48e871..0eb4a279582 100644 --- a/servers/debugger/servers_debugger.h +++ b/servers/debugger/servers_debugger.h @@ -117,6 +117,7 @@ private: static Error _capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured); void _send_resource_usage(); + String _get_resource_type_from_path(const String &p_path); ServersDebugger(); diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h index 461e3dd807a..09758e5e40b 100644 --- a/servers/rendering/dummy/storage/mesh_storage.h +++ b/servers/rendering/dummy/storage/mesh_storage.h @@ -132,6 +132,7 @@ public: virtual void mesh_surface_remove(RID p_mesh, int p_surface) override; virtual void mesh_clear(RID p_mesh) override; + virtual void mesh_debug_usage(List *r_info) override {} /* MESH INSTANCE */ diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index abc525512a2..16d05772ebd 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -395,6 +395,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) if (new_surface.attribute_data.size()) { s->attribute_buffer = RD::get_singleton()->vertex_buffer_create(new_surface.attribute_data.size(), new_surface.attribute_data); + s->attribute_buffer_size = new_surface.attribute_data.size(); } if (new_surface.skin_data.size()) { s->skin_buffer = RD::get_singleton()->vertex_buffer_create(new_surface.skin_data.size(), new_surface.skin_data, as_storage_flag); @@ -411,6 +412,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) bool is_index_16 = new_surface.vertex_count <= 65536 && new_surface.vertex_count > 0; s->index_buffer = RD::get_singleton()->index_buffer_create(new_surface.index_count, is_index_16 ? RD::INDEX_BUFFER_FORMAT_UINT16 : RD::INDEX_BUFFER_FORMAT_UINT32, new_surface.index_data, false); + s->index_buffer_size = new_surface.index_data.size(); s->index_count = new_surface.index_count; s->index_array = RD::get_singleton()->index_array_create(s->index_buffer, 0, s->index_count); if (new_surface.lods.size()) { @@ -420,6 +422,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) for (int i = 0; i < new_surface.lods.size(); i++) { uint32_t indices = new_surface.lods[i].index_data.size() / (is_index_16 ? 2 : 4); s->lods[i].index_buffer = RD::get_singleton()->index_buffer_create(indices, is_index_16 ? RD::INDEX_BUFFER_FORMAT_UINT16 : RD::INDEX_BUFFER_FORMAT_UINT32, new_surface.lods[i].index_data); + s->lods[i].index_buffer_size = new_surface.lods[i].index_data.size(); s->lods[i].index_array = RD::get_singleton()->index_array_create(s->lods[i].index_buffer, 0, indices); s->lods[i].edge_length = new_surface.lods[i].edge_length; s->lods[i].index_count = indices; @@ -437,6 +440,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) if (mesh->blend_shape_count > 0) { s->blend_shape_buffer = RD::get_singleton()->storage_buffer_create(new_surface.blend_shape_data.size(), new_surface.blend_shape_data); + s->blend_shape_buffer_size = new_surface.blend_shape_data.size(); } if (use_as_storage) { @@ -917,6 +921,35 @@ void MeshStorage::mesh_surface_remove(RID p_mesh, int p_surface) { } } +void MeshStorage::mesh_debug_usage(List *r_info) { + for (const RID &mesh_rid : mesh_owner.get_owned_list()) { + Mesh *mesh = mesh_owner.get_or_null(mesh_rid); + if (!mesh) { + continue; + } + RS::MeshInfo mesh_info; + mesh_info.mesh = mesh_rid; + mesh_info.path = mesh->path; + + for (uint32_t surface_index = 0; surface_index < mesh->surface_count; surface_index++) { + MeshStorage::Mesh::Surface *surface = mesh->surfaces[surface_index]; + + mesh_info.vertex_buffer_size += surface->vertex_buffer_size; + mesh_info.attribute_buffer_size += surface->attribute_buffer_size; + mesh_info.skin_buffer_size += surface->skin_buffer_size; + mesh_info.index_buffer_size += surface->index_buffer_size; + mesh_info.blend_shape_buffer_size += surface->blend_shape_buffer_size; + mesh_info.vertex_count += surface->vertex_count; + + for (uint32_t lod_index = 0; lod_index < surface->lod_count; lod_index++) { + mesh_info.lod_index_buffers_size += surface->lods[lod_index].index_buffer_size; + } + } + + r_info->push_back(mesh_info); + } +} + bool MeshStorage::mesh_needs_instance(RID p_mesh, bool p_has_skeleton) { Mesh *mesh = mesh_owner.get_or_null(p_mesh); ERR_FAIL_NULL_V(mesh, false); diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index b2af74d55f8..08f00366726 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -78,11 +78,14 @@ private: RS::PrimitiveType primitive = RS::PRIMITIVE_POINTS; uint64_t format = 0; - RID vertex_buffer; - RID attribute_buffer; - RID skin_buffer; uint32_t vertex_count = 0; + RID vertex_buffer; uint32_t vertex_buffer_size = 0; + + RID attribute_buffer; + uint32_t attribute_buffer_size = 0; + + RID skin_buffer; uint32_t skin_buffer_size = 0; // A different pipeline needs to be allocated @@ -106,6 +109,7 @@ private: uint32_t version_count = 0; RID index_buffer; + uint32_t index_buffer_size = 0; RID index_array; uint32_t index_count = 0; @@ -113,6 +117,7 @@ private: float edge_length = 0.0; uint32_t index_count = 0; RID index_buffer; + uint32_t index_buffer_size = 0; RID index_array; }; @@ -130,6 +135,7 @@ private: Vector4 uv_scale; RID blend_shape_buffer; + uint32_t blend_shape_buffer_size = 0; RID material; @@ -397,6 +403,8 @@ public: virtual void mesh_clear(RID p_mesh) override; virtual void mesh_surface_remove(RID p_mesh, int p_surface) override; + virtual void mesh_debug_usage(List *r_info) override; + virtual bool mesh_needs_instance(RID p_mesh, bool p_has_skeleton) override; _FORCE_INLINE_ const RID *mesh_get_surface_count_and_materials(RID p_mesh, uint32_t &r_surface_count) { diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index 259de53a37b..81706bfd9a5 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -1651,6 +1651,7 @@ void TextureStorage::texture_debug_usage(List *r_info) { tinfo.width = t->width; tinfo.height = t->height; tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps > 1); + tinfo.type = static_cast(t->type); switch (t->type) { case TextureType::TYPE_3D: diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 748cc3706f8..14231ce4fd1 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -377,6 +377,8 @@ public: FUNC2(mesh_surface_remove, RID, int) FUNC1(mesh_clear, RID) + FUNC1(mesh_debug_usage, List *) + /* MULTIMESH API */ FUNCRIDSPLIT(multimesh) diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h index 512b613abc0..b83e6df712c 100644 --- a/servers/rendering/storage/mesh_storage.h +++ b/servers/rendering/storage/mesh_storage.h @@ -76,6 +76,8 @@ public: virtual void mesh_surface_remove(RID p_mesh, int p_surface) = 0; virtual void mesh_clear(RID p_mesh) = 0; + virtual void mesh_debug_usage(List *r_info) = 0; + virtual bool mesh_needs_instance(RID p_mesh, bool p_has_skeleton) = 0; /* MESH INSTANCE */ diff --git a/servers/rendering_server.h b/servers/rendering_server.h index af8be9db32c..24c61574f14 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -183,6 +183,7 @@ public: Image::Format format; int64_t bytes; String path; + TextureType type; }; virtual void texture_debug_usage(List *r_info) = 0; @@ -442,6 +443,20 @@ public: virtual void mesh_surface_remove(RID p_mesh, int p_surface) = 0; virtual void mesh_clear(RID p_mesh) = 0; + struct MeshInfo { + RID mesh; + String path; + uint32_t vertex_buffer_size = 0; + uint32_t attribute_buffer_size = 0; + uint32_t skin_buffer_size = 0; + uint32_t index_buffer_size = 0; + uint32_t blend_shape_buffer_size = 0; + uint32_t lod_index_buffers_size = 0; + uint64_t vertex_count = 0; + }; + + virtual void mesh_debug_usage(List *r_info) = 0; + /* MULTIMESH API */ virtual RID multimesh_create() = 0;