Add Meshes to the Video RAM Profiler

Apply suggestions from code review

Co-Authored-By: Hugo Locurcio <hugo.locurcio@hugo.pro>
This commit is contained in:
Ryan 2025-02-23 23:25:31 -05:00
parent 1696ab0cb6
commit 4497e2a0d3
13 changed files with 177 additions and 9 deletions

View File

@ -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<RS::MeshInfo> *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);

View File

@ -1481,6 +1481,7 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) {
tinfo.width = t->alloc_width;
tinfo.height = t->alloc_height;
tinfo.bytes = t->total_data_size;
tinfo.type = static_cast<RenderingServer::TextureType>(t->type);
switch (t->type) {
case Texture::TYPE_3D:

View File

@ -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);

View File

@ -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;

View File

@ -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<RS::MeshInfo> 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> 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;

View File

@ -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();

View File

@ -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<RS::MeshInfo> *r_info) override {}
/* MESH INSTANCE */

View File

@ -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<RS::MeshInfo> *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);

View File

@ -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<RS::MeshInfo> *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) {

View File

@ -1651,6 +1651,7 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *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<RenderingServer::TextureType>(t->type);
switch (t->type) {
case TextureType::TYPE_3D:

View File

@ -377,6 +377,8 @@ public:
FUNC2(mesh_surface_remove, RID, int)
FUNC1(mesh_clear, RID)
FUNC1(mesh_debug_usage, List<MeshInfo> *)
/* MULTIMESH API */
FUNCRIDSPLIT(multimesh)

View File

@ -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<RS::MeshInfo> *r_info) = 0;
virtual bool mesh_needs_instance(RID p_mesh, bool p_has_skeleton) = 0;
/* MESH INSTANCE */

View File

@ -183,6 +183,7 @@ public:
Image::Format format;
int64_t bytes;
String path;
TextureType type;
};
virtual void texture_debug_usage(List<TextureInfo> *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<MeshInfo> *r_info) = 0;
/* MULTIMESH API */
virtual RID multimesh_create() = 0;