Trying to figure out how this works.
Stanlyhalo opened this issue · 15 comments
So far I have successfully imported the two files I needed into my project and I can include them without errors. My next step is figuring out how to profile everything. So for an example, I want to get the profile stats (I think you have it in ms) of the imgui system. How do I go about doing so? Would I get the amount of frames it took at the start to end of rendering imgui, then get the ms in timing, and with that pass it over to the profiler? If that's correct, how do I do so?
You need to fill your array of legit::ProfilerTask structures with tasks that took place in your frame. It can be both GPU tasks and CPU tasks, because this structure just contains time, name and color and it does not care how exactly you obtained it. Then you feed them into legit::ProfilerGraph::LoadFrameData() function to tell the profiler what to render. Then you just call legit::ProfilerGraph::RenderTimings() and it renders everything using ImGui.
So in order for this to work, you obviously need to write the gathering code that tracks how long each part of your code actually executes and gathers its time stamps. This submodule only handles rendering stuff because different engines gather their profiling times in really different ways, and there's no easy way to gather for profiling GPU for example, since it'd really depend on your GAPI.
However, if you want to see an example of how it could be used, you can check Raikiri/LegitEngine for an example of how it can be implemented for CPU using std::clock() and for GPU using Vulkan GAPI. Basically you can just grab CpuProfiler from LegitVulkan/CpuProfiler.h and you can see how it's used in PresentQueue.h for example.
Oh neet, I'll check that out in a bit, I think my main questions right now is, when do I call these functions? Do I create the task before the main loop, same with the window, and then do I load the data and render the data in the loop? I noticed the code in the Engine is somewhat different from how it is in this repo from what I can tell.
Edit*: What is the start and end time in the ProfilerTask?
You re-create the array of tasks each frame from scratch. Just clear the array and fill with tasks of the current frame, then render it at the end of the frame. Task is basically a part of your code that was executed during the current frame. startTime is when this code started execution, endTime is when it finished. It's up to you to make sure tasks go one after another and don't overlap.
So what do I enter for the startTime and endTime? Is it the time in which the render started and the time in which it started? If so, is it in seconds? And the current way I get the current frame is just add 1 to the frame size_t variable everytime the loop starts, is this the correct way?
gdi_start = std::chrono::high_resolution_clock::now();
gdi_core.NewFrame();
gdi_core.RunUI();
ImGuiUtils::ProfilersWindow win = ImGuiUtils::ProfilersWindow();
CosmicEngine::ProfilerTask task{
,
,
"GDI",
CosmicEngine::Colors::peterRiver
};
win.gpuGraph.LoadFrameData(&task, framesCount);
win.Render();
gdi_core.Render(commandBuffer);
gdi_end = std::chrono::high_resolution_clock::now();
^ Here's some code for what I'm currently stuck on. Currently trying to figure out what to input for the start and end times for the task but im not sure how i would go about doing it.
This is my updated code:
double gdi_start = 0;
double gdi_end = 0;
size_t framesCount = 0;
ImGuiUtils::ProfilersWindow win = ImGuiUtils::ProfilersWindow();
CosmicEngine::ProfilerTask task{
gdi_start,
gdi_end,
"GDI",
CosmicEngine::Colors::peterRiver
};
while (!window.shouldClose()) {
glfwPollEvents();
window.attemptDragWindow();
framesCount++;
if (auto commandBuffer = renderer.beginFrame()) {
renderer.beginSwapChainRenderPass(commandBuffer);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[0]->gameEntities);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[1]->gameEntities);
gdi_start++;
gdi_core.NewFrame();
gdi_core.RunUI();
win.gpuGraph.LoadFrameData(&task, 1);
win.Render();
gdi_core.Render(commandBuffer);
gdi_end++;
task = CosmicEngine::ProfilerTask{
gdi_start,
gdi_end,
"GDI",
CosmicEngine::Colors::peterRiver
};
gdi_start = 0;
gdi_end = 0;
renderer.endSwapChainRenderPass(commandBuffer);
renderer.endFrame();
}
}
I think I'm getting closer, but I'm getting a "Bad Allocation" error in the console and it crashes.
Edit*: Well now it runs, and it shows the fps on the window title, but the profiler for the GDI task doesn't do anything rlly atm still.
The units that the profiler uses you basically define yourself. It's determined by this line:
float maxFrameTime = 1.0f / 30.0f;
So basically it means that the height of the profiler graph is 1/30th (of a second in this case, but you can treat is whatever else you want). So for example if you measure your time in milliseconds and want profiler's height to be 20ms, then set this value to 20.0f and feed the profiler time in milliseconds. I definitely should have exposed that value as an argument of that function, but I kinda forgot.
You should fill startTime and endTime of your task and once you gather all times for all your tasks, you submit all of them at the end of the frame using LoadFrameData(). Its first argument is array of tasks for this frame, second argument is how many of them there are. By the time this method executes, the array of tasks should be already complete with all timestamps already in, your code currently tries to gather some timestamps after submitting them.
double gdi_start = 0;
double gdi_end = 0;
size_t framesCount = 0;
ImGuiUtils::ProfilersWindow win = ImGuiUtils::ProfilersWindow();
CosmicEngine::ProfilerTask task{
0,
0,
"GDI",
CosmicEngine::Colors::peterRiver
};
while (!window.shouldClose()) {
glfwPollEvents();
window.attemptDragWindow();
framesCount++;
if (auto commandBuffer = renderer.beginFrame()) {
renderer.beginSwapChainRenderPass(commandBuffer);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[0]->gameEntities);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[1]->gameEntities);
gdi_start++;
gdi_core.NewFrame();
gdi_core.RunUI();
float maxFrameTime = 1.0f / 20.0f;
//std::chrono::duration_cast<std::chrono::milliseconds>(gdi_start - gdi_end).count()
win.Render();
gdi_core.Render(commandBuffer);
gdi_end++;
task = CosmicEngine::ProfilerTask{
gdi_start,
gdi_end,
"GDI",
CosmicEngine::Colors::peterRiver
};
win.gpuGraph.LoadFrameData(&task, maxFrameTime);
renderer.endSwapChainRenderPass(commandBuffer);
renderer.endFrame();
}
}
So I tried what you said, and if the maxFrameTime does not equal 1.0f after the division, it doesn't show up on the legacy key (right side where it shows all the tasks). And if I do set it to 20.0f/20.0f, it looks like this:
The second parameter of LoadFrameData is an int, it's number of tasks. You can't pass maxFrameTime there. The only way to change maxFrameTime is in ProfilerGraph::RenderLegend().
And again, look at CpuProfiler.h for how time stamps are gathered using high resolution timer. Specifically look for calls to hrc::now().
ooooh okay, so I fixed that, and I changed the gdi_start and end to the high_resolution_clock in std::chrono, but how do I use it for the input for startTime and endTime for the task (if that's what I need to do)? And also, why doesn't the profiler record data, like the data doesn't get saved along the graph but instead just renders a line in the legacy key.
This is now what I have, but I can't still have it "record" the data, and also it keeps going between 100 & 200 ms for the task:
auto start = std::chrono::high_resolution_clock::now();
auto gdi_start = std::chrono::high_resolution_clock::now();
auto gdi_end = std::chrono::high_resolution_clock::now();
size_t framesCount = 0;
ImGuiUtils::ProfilersWindow win = ImGuiUtils::ProfilersWindow();
CosmicEngine::ProfilerTask task{
0,
0,
"GDI",
CosmicEngine::Colors::peterRiver
};
while (!window.shouldClose()) {
glfwPollEvents();
window.attemptDragWindow();
framesCount++;
if (auto commandBuffer = renderer.beginFrame()) {
renderer.beginSwapChainRenderPass(commandBuffer);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[0]->gameEntities);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[1]->gameEntities);
gdi_start = std::chrono::high_resolution_clock::now();
gdi_core.NewFrame();
gdi_core.RunUI();
float maxFrameTime = 20.0f / 20.0f;
//std::chrono::duration_cast<std::chrono::milliseconds>(gdi_start - gdi_end).count()
win.Render();
gdi_core.Render(commandBuffer);
gdi_end = std::chrono::high_resolution_clock::now();
task = CosmicEngine::ProfilerTask{
(double)std::chrono::duration_cast<std::chrono::milliseconds>(gdi_start - start).count(),
(double)std::chrono::duration_cast<std::chrono::milliseconds>(gdi_end - start).count(),
"GDI",
CosmicEngine::Colors::peterRiver
};
win.gpuGraph.LoadFrameData(&task, 1);
renderer.endSwapChainRenderPass(commandBuffer);
renderer.endFrame();
}
}
Well I'm getting closer, it's now recording, but it's like WAY above the max.
task = CosmicEngine::ProfilerTask{
(double)std::chrono::duration_cast<std::chrono::milliseconds>(gdi_start - start).count(),
(double)std::chrono::duration_cast<std::chrono::milliseconds>(gdi_end - gdi_start).count(),
"GDI",
CosmicEngine::Colors::peterRiver
};
Well, I'm super close, just the bars goes super high.
auto start = std::chrono::high_resolution_clock::now();
auto gdi_start = std::chrono::high_resolution_clock::now();
auto gdi_end = std::chrono::high_resolution_clock::now();
ImGuiUtils::ProfilersWindow win = ImGuiUtils::ProfilersWindow();
CosmicEngine::ProfilerTask task{
0,
0,
"GDI",
CosmicEngine::Colors::peterRiver
};
while (!window.shouldClose()) {
glfwPollEvents();
window.attemptDragWindow();
if (auto commandBuffer = renderer.beginFrame()) {
renderer.beginSwapChainRenderPass(commandBuffer);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[0]->gameEntities);
simpleRenderSystem.renderGameEntities(commandBuffer, gameMaps[1]->gameEntities);
gdi_start = std::chrono::high_resolution_clock::now();
gdi_core.NewFrame();
gdi_core.RunUI();
float maxFrameTime = 1.0f / 20.0f;
//std::chrono::duration_cast<std::chrono::milliseconds>(gdi_start - gdi_end).count()
win.Render();
gdi_core.Render(commandBuffer);
gdi_end = std::chrono::high_resolution_clock::now();
task = CosmicEngine::ProfilerTask{
0,
(double)std::chrono::duration_cast<std::chrono::microseconds>(gdi_end - gdi_start).count(),
"GDI",
CosmicEngine::Colors::peterRiver
};
win.gpuGraph.LoadFrameData(&task, 1);
renderer.endSwapChainRenderPass(commandBuffer);
renderer.endFrame();
}
}
Hey, if you're still here, I'm only running into one issue which should be easy to solve since you made it, but when I tried to add multiple tasks, it appears that the bars overlap each other instead of stacking:
Edit*: I think I finally understand the startTime and endTime parameters for a task lol.
The startTime of task n+1 should always be greater than endTime of task n. Otherwise they'll all overlap on the graph (and this is what happens on your screenshot). This can be useful for profiling tasks that partially overlap in time, but generally should be avoided.
By the way, your renderer is srgb-incorrect, that's why your colors show in a different way than they're intended to. Look up srgb color space and srgb blending.