diff --git a/CMakeLists.txt b/CMakeLists.txt index 73c0566c..eaf086e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ set(SOURCES "model/Texture.cpp" "utilities/Timer.cpp" "utilities/workerpool.cpp" +"utilities/memorystats.cpp" "world/Track.cpp" "world/TrackSleepers.cpp" "world/TrackBlend.cpp" diff --git a/audio/audio.cpp b/audio/audio.cpp index 3be2810d..7d932140 100644 --- a/audio/audio.cpp +++ b/audio/audio.cpp @@ -299,6 +299,21 @@ buffer_manager::buffer( audio::buffer_handle const Buffer ) const { } // creates AL buffers for files decoded in the background so far, or for all of them if requested +// the AL buffers made and the bytes of their sound data, for the debug panel +memory_count +buffer_manager::memory() const { + + memory_count result; + for( auto const &buffer : m_buffers ) { + if( buffer.id == null_resource ) { continue; } + ALint size { 0 }; + ::alGetBufferi( buffer.id, AL_SIZE, &size ); + ++result.count; + result.bytes += static_cast( std::max( size, 0 ) ); + } + return result; +} + void buffer_manager::update( bool const Wait ) { diff --git a/audio/audio.h b/audio/audio.h index 391bf1eb..d67d020f 100644 --- a/audio/audio.h +++ b/audio/audio.h @@ -9,6 +9,8 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include "utilities/memorystats.h" + #if defined(__APPLE__) && !__has_include() #include #include @@ -60,6 +62,9 @@ public: // creates AL buffers for files decoded in the background so far, or for all of them if requested void update( bool const Wait = false ); + // the AL buffers made and the bytes of their sound data, which the AL keeps in the process, for the debug panel + memory_count + memory() const; private: // types diff --git a/audio/audiorenderer.h b/audio/audiorenderer.h index e48d582d..62d7b2a8 100644 --- a/audio/audiorenderer.h +++ b/audio/audiorenderer.h @@ -108,6 +108,8 @@ public: audio::openal_buffer const &buffer( audio::buffer_handle const Buffer ) const; // makes buffers of all sound files still decoded in the background (sound.asyncload), waiting for them void complete_buffers(); + // the sound buffers and the bytes of their data, for the debug panel + memory_count buffers_memory() const { return m_buffers.memory(); } bool init(); @@ -164,4 +166,4 @@ private: extern openal_renderer renderer; extern bool event_volume_change; -} // namespace audio \ No newline at end of file +} // namespace audio \ No newline at end of file diff --git a/model/MdlMngr.cpp b/model/MdlMngr.cpp index 680f67c4..9ff03a02 100644 --- a/model/MdlMngr.cpp +++ b/model/MdlMngr.cpp @@ -40,6 +40,29 @@ TMdlContainer::LoadModel(std::string const &Name, bool const Dynamic) { TModelsManager::modelcontainer_sequence TModelsManager::m_models { 1, TMdlContainer{} }; TModelsManager::stringmodelcontainerindex_map TModelsManager::m_modelsmap; +// what the loaded models keep in memory, for the debug panel +TModelsManager::memory_data +TModelsManager::memory() { + + memory_data result; + std::vector pending; + for( auto const &container : m_models ) { + if( container.Model == nullptr ) { continue; } + ++result.models; + pending.assign( 1, container.Model->Root ); + while( false == pending.empty() ) { + auto const *submodel { pending.back() }; + pending.pop_back(); + if( submodel == nullptr ) { continue; } + ++result.submodels.count; + result.submodels.bytes += memory_of( submodel->Vertices ) + memory_of( submodel->Userdata ) + memory_of( submodel->Indices ); + pending.push_back( submodel->Child ); + pending.push_back( submodel->Next ); + } + } + return result; +} + // wczytanie modelu do tablicy TModel3d * TModelsManager::LoadModel(std::string const &Name, std::string const &virtualName, bool dynamic) { diff --git a/model/MdlMngr.h b/model/MdlMngr.h index fb255f2e..7de1c10f 100644 --- a/model/MdlMngr.h +++ b/model/MdlMngr.h @@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "utilities/Classes.h" +#include "utilities/memorystats.h" class TMdlContainer { friend class TModelsManager; @@ -22,6 +23,13 @@ class TModelsManager { public: // McZapkie: dodalem sciezke, notabene Path!=Patch :) static TModel3d *GetModel(std::string const &Name, bool const dynamic = false, bool const Logerrors = true , int uid = 0); + // what the loaded models keep in memory, for the debug panel: models, submodels with the bytes of their own copies of the + // geometry (vertices, user data, indices; the gpu gets theirs from the geometry banks) + struct memory_data { + std::uint64_t models { 0 }; + memory_count submodels; + }; + static memory_data memory(); private: // types: diff --git a/model/Texture.cpp b/model/Texture.cpp index 875fae26..d4fc449b 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -1484,6 +1484,25 @@ texture_manager::update() { } } +// what the textures keep in memory, for the debug panel +texture_manager::memory_data +texture_manager::memory() const { + + memory_data result; + for( auto const &texture : m_textures ) { + if( texture.first == nullptr ) { continue; } + if( false == texture.first->data.empty() ) { + ++result.cpu.count; + result.cpu.bytes += memory_of( texture.first->data ); + } + if( texture.first->id != opengl_texture::invalid_id ) { + ++result.gpu.count; + result.gpu.bytes += static_cast( texture.first->size ) * 1024; + } + } + return result; +} + // debug performance string std::string texture_manager::info() const { diff --git a/model/Texture.h b/model/Texture.h index 3ae979d1..d4f15071 100644 --- a/model/Texture.h +++ b/model/Texture.h @@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include "winheaders.h" +#include "utilities/memorystats.h" #include #include "model/ResourceManager.h" #include "gl/ubo.h" @@ -180,6 +181,14 @@ public: // debug performance string std::string info() const; + // what the textures keep in memory, for the debug panel: copies of the data kept in the process (count, bytes), and the + // textures made on the gpu with the size of their data (count, bytes) + struct memory_data { + memory_count cpu; + memory_count gpu; + }; + memory_data + memory() const; private: // types: diff --git a/rendering/geometrybank.cpp b/rendering/geometrybank.cpp index 5b4abf12..2197047d 100644 --- a/rendering/geometrybank.cpp +++ b/rendering/geometrybank.cpp @@ -420,6 +420,32 @@ geometry_bank::userdata( gfx::geometry_handle const &Geometry ) const { // geometry bank manager, holds collection of geometry banks // performs a resource sweep +// what the chunks keep in the process, added to the counts +void +geometry_bank::count_memory( memory_count &Vertices, memory_count &Indices, memory_count &Userdata ) const { + + for( auto const &chunk : m_chunks ) { + ++Vertices.count; + Vertices.bytes += memory_of( chunk.vertices ); + Indices.bytes += memory_of( chunk.indices ); + Userdata.bytes += memory_of( chunk.userdata ); + } + Indices.count = Userdata.count = Vertices.count; +} + +// what the banks keep in the process, for the debug panel +auto +geometrybank_manager::memory() const -> memory_data { + + memory_data result; + for( auto const &bank : m_geometrybanks ) { + if( bank.first == nullptr ) { continue; } + ++result.banks; + bank.first->count_memory( result.vertices, result.indices, result.userdata ); + } + return result; +} + void geometrybank_manager::update() { diff --git a/rendering/geometrybank.h b/rendering/geometrybank.h index eae11241..ae260798 100644 --- a/rendering/geometrybank.h +++ b/rendering/geometrybank.h @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "model/ResourceManager.h" +#include "utilities/memorystats.h" struct world_vertex; @@ -161,6 +162,9 @@ public: auto userdata( gfx::geometry_handle const &Geometry ) const -> gfx::userdata_array const &; // primitive type of specified chunk, 0 if there's no such chunk auto type( gfx::geometry_handle const &Geometry ) const -> unsigned int; + // what the chunks keep in the process (a copy of what went to the gpu), added to the counts: chunks and vertex bytes, index bytes, + // user data bytes + void count_memory( memory_count &Vertices, memory_count &Indices, memory_count &Userdata ) const; protected: // types: @@ -272,6 +276,14 @@ public: auto indices( gfx::geometry_handle const &Geometry ) const -> gfx::index_array const &; // provides direct access to vertex data of specfied chunk auto vertices( gfx::geometry_handle const &Geometry ) const -> gfx::vertex_array const &; + // what the banks keep in the process, for the debug panel: banks, chunks with their vertex bytes, index bytes and user data bytes + struct memory_data { + std::uint64_t banks { 0 }; + memory_count vertices; + memory_count indices; + memory_count userdata; + }; + auto memory() const -> memory_data; // provides direct access to vertex data of specfied chunk auto userdata( gfx::geometry_handle const &Geometry ) const -> gfx::userdata_array const &; // primitive type of specified chunk, 0 if there's no such chunk diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index bd1ad340..f72783fa 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -15,6 +15,10 @@ http://mozilla.org/MPL/2.0/. #include "vehicle/Train.h" #include "vehicle/Camera.h" #include "simulation/simulation.h" +#include "model/MdlMngr.h" +#include "audio/audiorenderer.h" +#include "world/Track.h" +#include "utilities/memorystats.h" #include "utilities/Logs.h" #include "simulation/simulationtime.h" #include "application/application.h" @@ -805,6 +809,7 @@ void opengl46_renderer::SwapBuffers() // the debug data, in sections for the panel. info_times() and info_stats() hold the same, joined m_debugsections.clear(); + Update_Memory_Info(); // a line of a table of times: the name indented and padded to a column, then the time(s) auto const timeline = []( int const Indent, std::string const &Name, std::string const &Values ) { auto line { std::string( static_cast( Indent ), ' ' ) + Name }; @@ -1133,6 +1138,7 @@ void opengl46_renderer::SwapBuffers() culltext += Light_Shadow_Cull_Info( false ); m_debugsections.push_back( { "Scene statistics", std::move( statstext ), false } ); m_debugsections.push_back( { "GPU culling", std::move( culltext ), false } ); + m_debugsections.push_back( { "Memory", m_memorytext, true } ); // the parts of the sections start with a line break here and there, and end with one or not for( auto §ion : m_debugsections ) { @@ -1149,6 +1155,105 @@ void opengl46_renderer::SwapBuffers() } } +// what the process and its parts keep in memory, for the debug panel (m_memorytext). gathered every 2 s: it goes through every +// texture, geometry chunk, model and scenery shape +void opengl46_renderer::Update_Memory_Info() +{ + auto const now { std::chrono::steady_clock::now() }; + if( ( false == m_memorytext.empty() ) && ( now - m_memorytime < std::chrono::seconds( 2 ) ) ) { return; } + m_memorytime = now; + + auto const mb = []( std::uint64_t const Bytes ) { + return to_string( static_cast( Bytes ) / ( 1024.0 * 1024.0 ), 1 ) + " MB"; }; + auto const counted = []( memory_count const &Count, char const *Name ) { + return std::to_string( Count.count ) + " " + Name; }; + std::string text; + + auto const process { query_process_memory() }; + if( process.valid ) { + text += "process: " + mb( process.resident ) + " resident (peak " + mb( process.peak ) + ")" + + ( process.committed > 0 ? ", " + mb( process.committed ) + " private" : std::string{} ) + "\n"; + } + // the memory of the card, all processes: GL_NVX_gpu_memory_info (in kB) + if( m_nvxmemory < 0 ) { + m_nvxmemory = 0; + GLint extensions { 0 }; + ::glGetIntegerv( GL_NUM_EXTENSIONS, &extensions ); + for( GLint index = 0; index < extensions; ++index ) { + auto const *name { reinterpret_cast( ::glGetStringi( GL_EXTENSIONS, static_cast( index ) ) ) }; + if( ( name != nullptr ) && ( std::strcmp( name, "GL_NVX_gpu_memory_info" ) == 0 ) ) { + m_nvxmemory = 1; + break; + } + } + } + if( m_nvxmemory > 0 ) { + GLint dedicated { 0 }, available { 0 }, evicted { 0 }; + ::glGetIntegerv( 0x9047, &dedicated ); // GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX + ::glGetIntegerv( 0x9049, &available ); // GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX + ::glGetIntegerv( 0x904B, &evicted ); // GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX + text += "card memory, all programs: " + mb( static_cast( std::max( dedicated - available, 0 ) ) * 1024 ) + " used of " + + mb( static_cast( dedicated ) * 1024 ) + ( evicted > 0 ? ", " + mb( static_cast( evicted ) * 1024 ) + " evicted" : std::string{} ) + "\n"; + } + + std::uint64_t accounted { 0 }; + // textures: the copies of the data kept in the process after the upload + auto const textures { m_textures.memory() }; + accounted += textures.cpu.bytes; + text += "textures: " + std::to_string( textures.cpu.count ) + " keep their data in ram, " + mb( textures.cpu.bytes ) + "; " + + std::to_string( textures.gpu.count ) + " on the card, ~" + mb( textures.gpu.bytes ) + "\n"; + // geometry: the copies of the chunks sent to the card + auto const geometry { m_geometry.memory() }; + accounted += geometry.vertices.bytes + geometry.indices.bytes + geometry.userdata.bytes; + text += "geometry banks: " + std::to_string( geometry.banks ) + ", " + counted( geometry.vertices, "chunks" ) + ": vertices " + mb( geometry.vertices.bytes ) + + ", indices " + mb( geometry.indices.bytes ) + ", user data " + mb( geometry.userdata.bytes ) + "\n"; + // models: the geometry their submodels keep of their own + auto const models { TModelsManager::memory() }; + accounted += models.submodels.bytes; + text += "models: " + std::to_string( models.models ) + ", " + counted( models.submodels, "submodels" ) + ", their own geometry " + mb( models.submodels.bytes ) + "\n"; + // scenery: the world space vertices the shapes and lines keep, the transforms of the sleepers, the model instances + memory_count shapes, lines, sleepers; + std::uint64_t instances { 0 }; + if( simulation::Region != nullptr ) { + auto const shape = [ &shapes ]( scene::shape_node const &Shape ) { + ++shapes.count; + shapes.bytes += memory_of( Shape.data().vertices ) + memory_of( Shape.data().userdata ); }; + for( auto const *section : simulation::Region->m_sections ) { + if( section == nullptr ) { continue; } + for( auto const &item : section->m_shapes ) { shape( item ); } + for( auto const &cell : section->m_cells ) { + for( auto const &item : cell.m_shapesopaque ) { shape( item ); } + for( auto const &item : cell.m_shapestranslucent ) { shape( item ); } + for( auto const &item : cell.m_lines ) { + ++lines.count; + lines.bytes += memory_of( item.data().vertices ); + } + for( auto const *track : cell.m_paths ) { + if( track->m_sleeper_local_transforms.empty() ) { continue; } + ++sleepers.count; + sleepers.bytes += memory_of( track->m_sleeper_local_transforms ); + } + instances += cell.m_instancesopaque.size() + cell.m_instancetranslucent.size(); + } + } + } + accounted += shapes.bytes + lines.bytes + sleepers.bytes; + text += "scenery: " + counted( shapes, "shapes" ) + " " + mb( shapes.bytes ) + ", " + counted( lines, "lines" ) + " " + mb( lines.bytes ) + ", sleepers of " + + counted( sleepers, "tracks" ) + " " + mb( sleepers.bytes ) + ", " + std::to_string( instances ) + " model instances\n"; + // sounds: the data of the buffers, which the AL keeps in the process + auto const sounds { audio::renderer.buffers_memory() }; + accounted += sounds.bytes; + text += "sounds: " + counted( sounds, "buffers" ) + " " + mb( sounds.bytes ) + "\n"; + text += "vehicles: " + std::to_string( simulation::Vehicles.sequence().size() ) + "\n"; + if( process.valid ) { + auto const total { process.committed > 0 ? process.committed : process.resident }; + text += "counted above: " + mb( accounted ) + " of " + mb( total ) + ( process.committed > 0 ? " private" : " resident" ) + ", the rest " + + mb( total > accounted ? total - accounted : 0 ) + " (other data, the heaps, the driver)\n"; + } + text += "gathered in " + to_string( std::chrono::duration( std::chrono::steady_clock::now() - now ).count(), 2 ) + " ms, every 2 s"; + m_memorytext = std::move( text ); +} + void opengl46_renderer::cpu_timeline::begin( char const *Name ) { if( active ) { end(); } diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 20f90777..a6ed0318 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -617,6 +617,12 @@ class opengl46_renderer : public gfx_renderer { //debug_stats m_debugstats; std::string m_debugstatstext; std::vector m_debugsections; // the same data in sections, for the debug panel + // what the process and its parts keep in memory, for the debug panel, gathered every 2 s; whether the card tells its memory + // (GL_NVX_gpu_memory_info), -1 not asked yet + void Update_Memory_Info(); + std::string m_memorytext; + std::chrono::steady_clock::time_point m_memorytime {}; + int m_nvxmemory { -1 }; struct simulation_state { std::string weather; std::string season; diff --git a/utilities/memorystats.cpp b/utilities/memorystats.cpp new file mode 100644 index 00000000..d42c0bb9 --- /dev/null +++ b/utilities/memorystats.cpp @@ -0,0 +1,70 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "utilities/memorystats.h" + +#if defined(_WIN32) +#include +#pragma comment(lib, "psapi.lib") +#elif defined(__APPLE__) +#include +#else +#include +#include +#endif + +process_memory query_process_memory() +{ + process_memory result; +#if defined(_WIN32) + PROCESS_MEMORY_COUNTERS_EX counters{}; + counters.cb = sizeof(counters); + if (::GetProcessMemoryInfo(::GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters))) + { + result.valid = true; + result.resident = counters.WorkingSetSize; + result.peak = counters.PeakWorkingSetSize; + result.committed = counters.PrivateUsage; + } +#elif defined(__APPLE__) + mach_task_basic_info info{}; + mach_msg_type_number_t count{MACH_TASK_BASIC_INFO_COUNT}; + if (::task_info(::mach_task_self(), MACH_TASK_BASIC_INFO, reinterpret_cast(&info), &count) == KERN_SUCCESS) + { + result.valid = true; + result.resident = info.resident_size; + result.peak = info.resident_size_max; + } +#else + // the lines of /proc/self/status give kB + std::ifstream status("/proc/self/status"); + std::string line; + auto const value = [&line](char const *Key, std::uint64_t &Target) { + auto const length{std::char_traits::length(Key)}; + if (line.compare(0, length, Key) != 0) + { + return false; + } + Target = std::stoull(line.substr(length)) * 1024; + return true; + }; + while (std::getline(status, line)) + { + if (value("VmRSS:", result.resident)) + { + result.valid = true; + } + value("VmHWM:", result.peak); + // resident private memory: the closest this has to private bytes + value("RssAnon:", result.committed); + } +#endif + return result; +} diff --git a/utilities/memorystats.h b/utilities/memorystats.h new file mode 100644 index 00000000..3a909add --- /dev/null +++ b/utilities/memorystats.h @@ -0,0 +1,44 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include + +// memory of the process, as the system counts it, for the debug panel +struct process_memory +{ + bool valid{false}; + std::uint64_t resident{0}; // in physical memory now (working set, resident set) + std::uint64_t peak{0}; // the most it was in physical memory + std::uint64_t committed{0}; // private memory the process asked for, in physical memory or not (private bytes; 0 where not known) +}; + +process_memory query_process_memory(); + +// what a part of the program keeps in memory: things and bytes +struct memory_count +{ + std::uint64_t count{0}; + std::uint64_t bytes{0}; + + memory_count &operator+=(memory_count const &Other) + { + count += Other.count; + bytes += Other.bytes; + return *this; + } +}; + +// bytes a vector keeps for its elements +template std::uint64_t memory_of(std::vector const &Vector) +{ + return static_cast(Vector.capacity()) * sizeof(Type_); +}