sample app - memory & resource profiler

This commit is contained in:
Mikulas Florek 2015-09-20 23:24:38 +02:00
parent 30e8180e28
commit 1e41088840
18 changed files with 412 additions and 91 deletions

View file

@ -1,8 +1,9 @@
BasedOnStyle: LLVM
AlignAfterOpenBracket : false
AlignEscapedNewlinesLeft : true
AlignConsecutiveAssignments : false
AllowAllParametersOfDeclarationOnNextLine : true
AllowAllParametersOfDeclarationOnNextLine : false
AccessModifierOffset : -4
AllowShortCaseLabelsOnASingleLine : true
AllowShortFunctionsOnASingleLine : Inline

View file

@ -35,6 +35,12 @@ public:
}
}
virtual void* reallocate(void* ptr, size_t size) override
{
return m_source.reallocate(ptr, size);
}
IAllocator& getSourceAllocator() { return m_source; }
private:

View file

@ -17,4 +17,10 @@ namespace Lumix
}
void* DefaultAllocator::reallocate(void* ptr, size_t size)
{
return realloc(ptr, size);
}
} // ~namespace Lumix

View file

@ -16,6 +16,7 @@ public:
virtual void* allocate(size_t n) override;
virtual void deallocate(void* p) override;
virtual void* reallocate(void* ptr, size_t size) override;
};

View file

@ -37,6 +37,12 @@ namespace Lumix
m_pool[m_pool_index++] = reinterpret_cast<T*>(ptr);
}
void* reallocate(void*, size_t) override
{
ASSERT(false);
return nullptr;
}
private:
IAllocator& m_allocator;
int32_t m_pool_index;

View file

@ -16,6 +16,7 @@ namespace Lumix
virtual void* allocate(size_t size) = 0;
virtual void deallocate(void* ptr) = 0;
virtual void* reallocate(void* ptr, size_t size) = 0;
template <class T, typename... Args>
T* newObject(Args&&... params)
@ -25,6 +26,7 @@ namespace Lumix
}
template <class T>
void deleteObject(T* ptr)
{

View file

@ -47,6 +47,12 @@ namespace Lumix
}
virtual void* reallocate(void*, size_t) override
{
ASSERT(false);
return nullptr;
}
private:
IAllocator& m_source;
size_t m_bucket_size;

View file

@ -32,7 +32,7 @@ namespace Lumix
bool isReady() const { return State::READY == m_state; }
bool isUnloading() const { return State::UNLOADING == m_state; }
bool isFailure() const { return State::FAILURE == m_state; }
uint32_t getRefCount() const { return m_ref_count; }
template <typename C, void (C::*Function)(State, State)>
void onLoaded(C* instance)

View file

@ -22,6 +22,7 @@ class ResourceManager;
class LUMIX_ENGINE_API ResourceManagerBase
{
friend class Resource;
public:
typedef PODHashMap<uint32_t, Resource*> ResourceTable;
public:
@ -42,6 +43,7 @@ public:
void reload(const Path& path);
void reload(Resource& resource);
ResourceTable& getResourceTable() { return m_resources; }
ResourceManagerBase(IAllocator& allocator);
virtual ~ResourceManagerBase(void);

View file

@ -34,6 +34,12 @@ namespace Lumix
}
}
virtual void* reallocate(void*, size_t) override
{
ASSERT(false);
return nullptr;
}
private:
size_t m_end;
uint8_t m_data[SIZE];

View file

@ -20,7 +20,7 @@ namespace Debug
class LUMIX_ENGINE_API Allocator : public IAllocator
{
private:
public:
class AllocationInfo
{
public:
@ -36,9 +36,11 @@ namespace Debug
virtual void* allocate(size_t size) override;
virtual void deallocate(void* ptr) override;
virtual void* reallocate(void* ptr, size_t size) override;
size_t getTotalSize() const { return m_total_size; }
IAllocator& getSourceAllocator() { return m_source; }
AllocationInfo* getFirstAllocationInfo() const { return m_root; }
private:
inline size_t getAllocationOffset();

View file

@ -99,6 +99,27 @@ namespace Debug
}
void* Allocator::reallocate(void* user_ptr, size_t size)
{
#ifndef _DEBUG
return m_source.reallocate(ptr, size);
#else
if (user_ptr == nullptr) return allocate(size);
if (size == 0) return nullptr;
void* new_data = allocate(size);
if (!new_data) return nullptr;
AllocationInfo* info = getAllocationInfoFromUser(user_ptr);
memcpy(new_data, user_ptr, info->m_size < size ? info->m_size : size);
deallocate(user_ptr);
return new_data;
#endif
}
void* Allocator::allocate(size_t size)
{
#ifndef _DEBUG

View file

@ -59,6 +59,27 @@ StackTree::~StackTree()
}
StackNode* StackTree::getParent(StackNode* node)
{
return node ? node->m_parent : nullptr;
}
bool StackTree::getFunction(StackNode* node, char* out, int max_size)
{
HANDLE process = GetCurrentProcess();
uint8_t symbol_mem[sizeof(SYMBOL_INFO) + 256 * sizeof(char)];
SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(symbol_mem);
memset(symbol_mem, 0, sizeof(symbol_mem));
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
BOOL success = SymFromAddr(process, (DWORD64)(node->m_instruction), 0, symbol);
if (success) Lumix::copyString(out, max_size, symbol->Name);
return success == TRUE;
}
void StackTree::printCallstack(StackNode* node)
{
while (node)

View file

@ -14,7 +14,7 @@ namespace Debug
class StackNode;
class StackTree
class LUMIX_ENGINE_API StackTree
{
public:
StackTree();
@ -22,6 +22,8 @@ namespace Debug
StackNode* record();
void printCallstack(StackNode* node);
static bool getFunction(StackNode* node, char* out, int max_size);
static StackNode* getParent(StackNode* node);
private:
StackNode* insertChildren(StackNode* node, void** instruction, void** stack);

View file

@ -33,9 +33,31 @@
#include <cstdio>
namespace bgfx
namespace bx
{
struct AllocatorI
{
virtual ~AllocatorI() = 0;
virtual void* alloc(size_t _size, size_t _align, const char* _file, uint32_t _line) = 0;
virtual void free(void* _ptr, size_t _align, const char* _file, uint32_t _line) = 0;
};
inline AllocatorI::~AllocatorI()
{
}
struct ReallocatorI : public AllocatorI
{
virtual void* realloc(void* _ptr, size_t _size, size_t _align, const char* _file, uint32_t _line) = 0;
};
} // namespace bx
namespace bgfx
{
struct PlatformData
{
@ -48,7 +70,8 @@ struct PlatformData
void setPlatformData(const PlatformData& _pd);
}
} // namespace bgfx
namespace Lumix
@ -61,6 +84,47 @@ static const uint32_t RENDERABLE_HASH = crc32("renderable");
static const uint32_t CAMERA_HASH = crc32("camera");
struct BGFXAllocator : public bx::ReallocatorI
{
BGFXAllocator(Lumix::IAllocator& source)
: m_source(source)
{
}
virtual ~BGFXAllocator()
{
}
virtual void* alloc(size_t _size, size_t _align, const char* _file, uint32_t _line) override
{
return m_source.allocate(_size);
}
virtual void free(void* _ptr, size_t _align, const char* _file, uint32_t _line) override
{
m_source.deallocate(_ptr);
}
virtual void* realloc(void* _ptr,
size_t _size,
size_t _align,
const char* _file,
uint32_t _line) override
{
return m_source.reallocate(_ptr, _size);
}
Lumix::IAllocator& m_source;
};
struct RendererImpl : public Renderer
{
struct CallbackStub : public bgfx::CallbackI
@ -79,7 +143,10 @@ struct RendererImpl : public Renderer
}
virtual void traceVargs(const char* _filePath, uint16_t _line, const char* _format, va_list _argList) override
virtual void traceVargs(const char* _filePath,
uint16_t _line,
const char* _format,
va_list _argList) override
{
char tmp[2048];
vsnprintf(tmp, sizeof(tmp), _format, _argList);
@ -88,38 +155,32 @@ struct RendererImpl : public Renderer
virtual void screenShot(const char*,
uint32_t,
uint32_t,
uint32_t,
const void*,
uint32_t,
bool) override
uint32_t,
uint32_t,
uint32_t,
const void*,
uint32_t,
bool) override
{
ASSERT(false);
}
virtual void captureBegin(uint32_t,
uint32_t,
uint32_t,
bgfx::TextureFormat::Enum,
bool) override
uint32_t,
uint32_t,
bgfx::TextureFormat::Enum,
bool) override
{
ASSERT(false);
}
virtual uint32_t cacheReadSize(uint64_t) override { return 0; }
virtual bool cacheRead(uint64_t, void*, uint32_t) override
{
return false;
}
virtual bool cacheRead(uint64_t, void*, uint32_t) override { return false; }
virtual void cacheWrite(uint64_t, const void*, uint32_t) override {}
virtual void captureEnd() override { ASSERT(false); }
virtual void captureFrame(const void*, uint32_t) override
{
ASSERT(false);
}
virtual void captureFrame(const void*, uint32_t) override { ASSERT(false); }
};
@ -133,6 +194,7 @@ struct RendererImpl : public Renderer
, m_pipeline_manager(*this, m_allocator)
, m_passes(m_allocator)
, m_shader_defines(m_allocator)
, m_bgfx_allocator(m_allocator)
{
bgfx::PlatformData d;
if (s_hwnd)
@ -141,7 +203,7 @@ struct RendererImpl : public Renderer
d.nwh = s_hwnd;
bgfx::setPlatformData(d);
}
bgfx::init(bgfx::RendererType::Count, 0, 0, &m_callback_stub);
bgfx::init(bgfx::RendererType::Count, 0, 0, &m_callback_stub, &m_bgfx_allocator);
bgfx::reset(800, 600);
bgfx::setDebug(BGFX_DEBUG_TEXT);
@ -195,17 +257,11 @@ struct RendererImpl : public Renderer
m_engine.registerComponentType("point_light", "Point light");
m_engine.registerComponentType("terrain", "Terrain");
m_engine.registerProperty(
"camera",
m_engine.registerProperty("camera",
allocator.newObject<StringPropertyDescriptor<RenderScene>>(
"slot",
&RenderScene::getCameraSlot,
&RenderScene::setCameraSlot,
allocator));
m_engine.registerProperty(
"camera",
allocator.newObject<DecimalPropertyDescriptor<RenderScene>>(
"FOV",
"slot", &RenderScene::getCameraSlot, &RenderScene::setCameraSlot, allocator));
m_engine.registerProperty("camera",
allocator.newObject<DecimalPropertyDescriptor<RenderScene>>("FOV",
&RenderScene::getCameraFOV,
&RenderScene::setCameraFOV,
1.0f,
@ -526,6 +582,7 @@ struct RendererImpl : public Renderer
PipelineManager m_pipeline_manager;
uint32_t m_current_pass_hash;
int m_view_counter;
BGFXAllocator m_bgfx_allocator;
static HWND s_hwnd;
};

View file

@ -57,6 +57,8 @@ public:
, m_is_entity_template_list_opened(false)
, m_selected_template_name(m_allocator)
, m_is_gameview_opened(true)
, m_profiler_ui(nullptr)
, m_asset_browser(nullptr)
{
}
@ -96,7 +98,7 @@ public:
showMainMenu();
m_profiler_ui.onGui();
m_profiler_ui->onGui();
m_asset_browser->onGui();
m_log_ui->onGui();
m_import_asset_dialog->onGui();
@ -276,7 +278,7 @@ public:
ImGui::MenuItem("Entity list", nullptr, &m_is_entity_list_shown);
ImGui::MenuItem("Entity templates", nullptr, &m_is_entity_template_list_opened);
ImGui::MenuItem("Log", nullptr, &m_log_ui->m_is_opened);
ImGui::MenuItem("Profiler", nullptr, &m_profiler_ui.m_is_opened);
ImGui::MenuItem("Profiler", nullptr, &m_profiler_ui->m_is_opened);
ImGui::MenuItem("Properties", nullptr, &m_is_property_grid_shown);
ImGui::MenuItem("Style editor", nullptr, &m_is_style_editor_shown);
ImGui::EndMenu();
@ -690,6 +692,7 @@ public:
shutdownImGui();
delete m_terrain_editor;
delete m_profiler_ui;
delete m_asset_browser;
delete m_log_ui;
delete m_import_asset_dialog;
@ -805,6 +808,7 @@ public:
GetCurrentDirectory(sizeof(current_dir), current_dir);
m_editor = Lumix::WorldEditor::create(current_dir, *m_engine);
m_asset_browser = new AssetBrowser(*m_editor);
m_profiler_ui = new ProfilerUI(&m_allocator, &m_editor->getEngine().getResourceManager());
m_terrain_editor = new TerrainEditor(*m_editor);
m_log_ui = new LogUI(m_editor->getAllocator());
m_import_asset_dialog = new ImportAssetDialog(*m_editor);
@ -918,7 +922,7 @@ public:
AssetBrowser* m_asset_browser;
TerrainEditor* m_terrain_editor;
LogUI* m_log_ui;
ProfilerUI m_profiler_ui;
ProfilerUI* m_profiler_ui;
ImportAssetDialog* m_import_asset_dialog;
ShaderCompiler* m_shader_compiler;
Lumix::string m_selected_template_name;

View file

@ -1,5 +1,11 @@
#include "profiler_ui.h"
#include "core/math_utils.h"
#include "core/resource.h"
#include "core/resource_manager.h"
#include "core/resource_manager_base.h"
#include "debug/allocator.h"
#include "debug/stack_tree.h"
#include "engine/engine.h"
#include "ocornut-imgui/imgui.h"
#include "string_builder.h"
@ -14,8 +20,11 @@ enum Column
};
ProfilerUI::ProfilerUI()
ProfilerUI::ProfilerUI(Lumix::Debug::Allocator* allocator, Lumix::ResourceManager* resource_manager)
: m_main_allocator(allocator)
, m_resource_manager(resource_manager)
{
m_root = nullptr;
m_is_opened = false;
m_current_block = nullptr;
Lumix::g_profiler.getFrameListeners().bind<ProfilerUI, &ProfilerUI::onFrame>(this);
@ -166,61 +175,215 @@ void ProfilerUI::showProfileBlock(Block* block, int column)
}
static void showCallstack(Lumix::Debug::Allocator::AllocationInfo* info)
{
char fn_name[256];
auto* node = info->m_stack_leaf;
while (node)
{
if (Lumix::Debug::StackTree::getFunction(node, fn_name, sizeof(fn_name)))
{
ImGui::BulletText(fn_name);
}
else
{
ImGui::BulletText("N/A");
}
node = Lumix::Debug::StackTree::getParent(node);
}
}
static const char* getResourceStateString(Lumix::Resource::State state)
{
switch (state)
{
case Lumix::Resource::State::EMPTY: return "Empty"; break;
case Lumix::Resource::State::FAILURE: return "Failure"; break;
case Lumix::Resource::State::LOADING: return "Loading"; break;
case Lumix::Resource::State::READY: return "Ready"; break;
case Lumix::Resource::State::UNLOADING: return "Unloading"; break;
}
return "Unknown";
}
void ProfilerUI::onGuiResources()
{
if (!m_resource_manager) return;
if (!ImGui::CollapsingHeader("Resources")) return;
uint32_t manager_types[] = {Lumix::ResourceManager::ANIMATION,
Lumix::ResourceManager::MATERIAL,
Lumix::ResourceManager::MODEL,
Lumix::ResourceManager::PHYSICS,
Lumix::ResourceManager::PIPELINE,
Lumix::ResourceManager::SHADER,
Lumix::ResourceManager::TEXTURE};
const char* manager_names[] = {
"Animations",
"Materials",
"Models",
"Physics",
"Pipelines",
"Shaders",
"Textures"
};
ASSERT(Lumix::lengthOf(manager_types) == Lumix::lengthOf(manager_names));
ImGui::Indent();
for (int i = 0; i < Lumix::lengthOf(manager_types); ++i)
{
if (!ImGui::CollapsingHeader(manager_names[i])) continue;
auto* material_manager = m_resource_manager->get(manager_types[i]);
auto& resources = material_manager->getResourceTable();
ImGui::Columns(4);
size_t sum = 0;
for (auto iter = resources.begin(), end = resources.end(); iter != end; ++iter)
{
ImGui::Text(iter.value()->getPath().c_str());
ImGui::NextColumn();
ImGui::Text("%.3fKB", iter.value()->size() / 1024.0f);
sum += iter.value()->size();
ImGui::NextColumn();
ImGui::Text(getResourceStateString(iter.value()->getState()));
ImGui::NextColumn();
ImGui::Text("%u", iter.value()->getRefCount());
ImGui::NextColumn();
}
ImGui::Text("All");
ImGui::NextColumn();
ImGui::Text("%.3fKB", sum / 1024.0f);
ImGui::NextColumn();
ImGui::NextColumn();
ImGui::Columns(1);
ImGui::Separator();
}
ImGui::Unindent();
}
void ProfilerUI::onGuiMemoryProfiler()
{
if (!m_main_allocator) return;
if (!ImGui::CollapsingHeader("Memory")) return;
ImGui::Text("Total size: %.3fMB", (m_main_allocator->getTotalSize() / 1024) / 1024.0f);
static int from = 0;
static int to = 0x7fffFFFF;
ImGui::SameLine();
ImGui::DragIntRange2("Interval", &from, &to);
auto* current_info = m_main_allocator->getFirstAllocationInfo();
int allocation_count = 0;
while (current_info)
{
auto info = current_info;
current_info = current_info->m_next;
if (info->m_size < from || info->m_size > to) continue;
if (info->m_size < 1024)
{
if (ImGui::TreeNode(info, "%dB", int(info->m_size)))
{
showCallstack(info);
ImGui::TreePop();
}
}
else if (info->m_size < 1024 * 1024)
{
if (ImGui::TreeNode(info, "%dKB", int(info->m_size / 1024)))
{
showCallstack(info);
ImGui::TreePop();
}
}
else
{
if (ImGui::TreeNode(info, "%.3fMB", (info->m_size / 1024) / 1024.0f))
{
showCallstack(info);
ImGui::TreePop();
}
}
++allocation_count;
}
ImGui::Text("Total number of allocations: %d", allocation_count);
}
void ProfilerUI::onGuiCPUProfiler()
{
if (!ImGui::CollapsingHeader("CPU")) return;
bool b = Lumix::g_profiler.isRecording();
if (ImGui::Checkbox("Recording", &b))
{
Lumix::g_profiler.toggleRecording();
}
if (m_root)
{
ImGui::Columns(2);
showProfileBlock(m_root, NAME);
ImGui::NextColumn();
showProfileBlock(m_root, TIME);
ImGui::NextColumn();
ImGui::Columns(1);
}
if (m_root)
{
float times[MAX_FRAMES];
for (int i = 0; i < m_root->m_frames.size(); ++i)
{
times[i] = m_root->m_frames[i];
}
auto* block = m_current_block ? m_current_block : m_root;
float width = ImGui::GetWindowContentRegionWidth();
int count = Lumix::Math::minValue(int(width / 5), block->m_hit_counts.size());
int offset = block->m_hit_counts.size() - count;
struct PlotData
{
Block* block;
int offset;
};
auto getter = [](void* data, int idx) -> float
{
auto* plot_data = (PlotData*)data;
return plot_data->block->m_frames[plot_data->offset + idx];
};
PlotData plot_data;
plot_data.block = block;
plot_data.offset = offset;
ImGui::PlotHistogram("",
getter,
&plot_data,
count,
0,
block->m_name,
0,
FLT_MAX,
ImVec2(width, 100));
}
}
void ProfilerUI::onGui()
{
if (!m_is_opened) return;
if (ImGui::Begin("Profiler", &m_is_opened))
{
bool b = Lumix::g_profiler.isRecording();
if (ImGui::Checkbox("Recording", &b))
{
Lumix::g_profiler.toggleRecording();
}
if (m_root)
{
ImGui::Columns(2);
showProfileBlock(m_root, NAME);
ImGui::NextColumn();
showProfileBlock(m_root, TIME);
ImGui::NextColumn();
ImGui::Columns(1);
}
if (m_root)
{
float times[MAX_FRAMES];
for (int i = 0; i < m_root->m_frames.size(); ++i)
{
times[i] = m_root->m_frames[i];
}
auto* block = m_current_block ? m_current_block : m_root;
float width = ImGui::GetWindowContentRegionWidth();
int count = Lumix::Math::minValue(int(width / 5), block->m_hit_counts.size());
int offset = block->m_hit_counts.size() - count;
struct PlotData
{
Block* block;
int offset;
};
auto getter = [](void* data, int idx) -> float {
auto* plot_data = (PlotData*)data;
return plot_data->block->m_frames[plot_data->offset + idx];
};
PlotData plot_data;
plot_data.block = block;
plot_data.offset = offset;
ImGui::PlotHistogram("",
getter,
&plot_data,
count,
0,
block->m_name,
0,
FLT_MAX,
ImVec2(width, 100));
}
onGuiCPUProfiler();
onGuiMemoryProfiler();
onGuiResources();
}
ImGui::End();
}

View file

@ -8,14 +8,24 @@
namespace Lumix
{
class IAllocator;
class IAllocator;
class ResourceManager;
namespace Debug
{
class Allocator;
}
}
class ProfilerUI
{
public:
ProfilerUI();
ProfilerUI(Lumix::Debug::Allocator* allocator, Lumix::ResourceManager* resource_manager);
~ProfilerUI();
void onGui();
@ -48,6 +58,9 @@ private:
};
private:
void onGuiCPUProfiler();
void onGuiMemoryProfiler();
void onGuiResources();
void onFrame();
void showProfileBlock(ProfilerUI::Block* block, int column);
void cloneBlock(Block* my_block, Lumix::Profiler::Block* remote_block);
@ -56,4 +69,6 @@ private:
Lumix::DefaultAllocator m_allocator;
Block* m_root;
Block* m_current_block;
Lumix::Debug::Allocator* m_main_allocator;
Lumix::ResourceManager* m_resource_manager;
};