Skip to content

Commit 97c4e63

Browse files
committed
Added LoadBalancing class that monitors frame performance and memory metrics which will enable adjustment of scene graph loads to maintain performance/memory utilization within targets.
1 parent cf4d160 commit 97c4e63

7 files changed

Lines changed: 208 additions & 2 deletions

File tree

include/vsg/app/LoadBalancing.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#pragma once
2+
3+
/* <editor-fold desc="MIT License">
4+
5+
Copyright(c) 2026 Robert Osfield
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
9+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12+
13+
</editor-fold> */
14+
15+
#include <vsg/core/Inherit.h>
16+
#include <vsg/ui/FrameStamp.h>
17+
18+
namespace vsg
19+
{
20+
21+
// forward declare
22+
class Viewer;
23+
24+
/// LoadBalancing is a base class for specifying the Camera view matrix and its inverse.
25+
class VSG_DECLSPEC LoadBalancing : public Inherit<Object, LoadBalancing>
26+
{
27+
public:
28+
LoadBalancing();
29+
explicit LoadBalancing(const LoadBalancing& rhs, const CopyOp& copyop = {});
30+
31+
virtual void update(Viewer& viewer);
32+
33+
time_point previousFrameTime = std::numeric_limits<time_point>::max();
34+
35+
double targetFrameTime = 0.016; // seconds
36+
double targetCPUTime = 0.008; // seconds
37+
double targetGPUTime = 0.008; // seconds
38+
double targetGPUMemoryUtilization = 0.9; // ratio
39+
40+
protected:
41+
~LoadBalancing();
42+
};
43+
VSG_type_name(vsg::LoadBalancing);
44+
45+
46+
} // namespace vsg

include/vsg/app/Viewer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1818
#include <vsg/app/RecordAndSubmitTask.h>
1919
#include <vsg/app/UpdateOperations.h>
2020
#include <vsg/app/Window.h>
21+
#include <vsg/app/LoadBalancing.h>
2122
#include <vsg/threading/Barrier.h>
2223
#include <vsg/threading/FrameBlock.h>
2324
#include <vsg/utils/Instrumentation.h>
@@ -129,6 +130,8 @@ namespace vsg
129130
ref_ptr<ActivityStatus> status;
130131
std::list<std::thread> threads;
131132

133+
ref_ptr<LoadBalancing> loadBalancing;
134+
132135
void setupThreading();
133136
void stopThreading();
134137

include/vsg/utils/Profiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace vsg
4242
const SourceLocation* sourceLocation = nullptr;
4343
const Object* object = nullptr;
4444
uint64_t reference = 0;
45+
uint64_t frameCount = 0;
4546
std::thread::id thread_id = {};
4647
};
4748

@@ -134,6 +135,7 @@ namespace vsg
134135
};
135136

136137
mutable size_t frameIndex = 0;
138+
mutable std::atomic_uint64_t frameCount = 0;
137139
mutable std::vector<FrameStatsCollection> perFrameGPUStats;
138140

139141
VkResult getGpuResults(FrameStatsCollection& frameStats) const;

src/vsg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ set(SOURCES
187187
app/UpdateOperations.cpp
188188
app/RecordTraversal.cpp
189189
app/CompileTraversal.cpp
190+
app/LoadBalancing.cpp
190191

191192
raytracing/AccelerationGeometry.cpp
192193
raytracing/AccelerationStructure.cpp

src/vsg/app/LoadBalancing.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/* <editor-fold desc="MIT License">
2+
3+
Copyright(c) 2026 Robert Osfield
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10+
11+
</editor-fold> */
12+
13+
#include <vsg/app/LoadBalancing.h>
14+
#include <vsg/app/Viewer.h>
15+
#include <vsg/io/Logger.h>
16+
#include <vsg/utils/Profiler.h>
17+
18+
using namespace vsg;
19+
20+
LoadBalancing::LoadBalancing()
21+
{
22+
}
23+
24+
LoadBalancing::LoadBalancing(const LoadBalancing& rhs, const CopyOp& copyop) :
25+
Inherit(rhs, copyop)
26+
{
27+
}
28+
29+
LoadBalancing::~LoadBalancing()
30+
{
31+
}
32+
33+
void LoadBalancing::update(Viewer& viewer)
34+
{
35+
if (previousFrameTime == std::numeric_limits<time_point>::max())
36+
{
37+
previousFrameTime = viewer.getFrameStamp()->time;
38+
return;
39+
}
40+
41+
double frameDelta = (previousFrameTime == std::numeric_limits<time_point>::max()) ? 0.0 : std::chrono::duration<double, std::chrono::seconds::period>(viewer.getFrameStamp()->time - previousFrameTime).count();
42+
uint64_t previousFrameCount = viewer.getFrameStamp()->frameCount - 1;
43+
44+
// vsg::info("LoadBalancing::update( ", &viewer, " ) ", viewer.getFrameStamp()->frameCount, ", ", frameDelta);
45+
46+
double frameTargetRatio = frameDelta/targetFrameTime;
47+
double cpuTargetRatio = 0.0;
48+
double gpuTargetRatio = 0.0;
49+
50+
if (auto profiler = viewer.instrumentation.cast<Profiler>())
51+
{
52+
auto& log = profiler->log;
53+
54+
size_t targetFrameCount = 16;
55+
56+
double max_cpu_durection = 0.0;
57+
double max_gpu_durection = 0.0;
58+
59+
auto& frameIndices = log->frameIndices;
60+
auto last_frame = frameIndices.end();
61+
auto first_frame = (frameIndices.size() > targetFrameCount) ? (frameIndices.end() - targetFrameCount) : frameIndices.begin();
62+
for(auto itr = first_frame; itr != last_frame; ++itr)
63+
{
64+
auto first_index = *itr;
65+
const auto& first = log->entry(first_index);
66+
auto last_index = first.reference;
67+
68+
auto cpu_duration = 0.0;
69+
auto gpu_duration = 0.0;
70+
71+
for(auto i = first_index; i <= last_index;)
72+
{
73+
if (log->entry(i).type == ProfileLog::COMMAND_BUFFER)
74+
{
75+
const auto& start = log->entry(i);
76+
const auto& end = log->entry(start.reference);
77+
if (start.gpuTime != 0 && end.gpuTime != 0)
78+
{
79+
gpu_duration = static_cast<double>(end.gpuTime - start.gpuTime) * log->timestampScaleToMilliseconds * 0.001;
80+
if (gpu_duration > max_gpu_durection)
81+
{
82+
max_gpu_durection = gpu_duration;
83+
}
84+
}
85+
86+
cpu_duration = std::abs(std::chrono::duration<double, std::chrono::seconds::period>(end.cpuTime - start.cpuTime).count());
87+
88+
if (cpu_duration > max_cpu_durection)
89+
{
90+
max_cpu_durection = cpu_duration;
91+
}
92+
93+
i = start.reference+1;
94+
}
95+
else
96+
{
97+
++i;
98+
}
99+
}
100+
101+
102+
103+
#if 0
104+
if (first.sourceLocation) vsg::info(" entry(", first_index,") { ", int(first.type), ", ", first.reference, ", func=", first.sourceLocation->function, ", cpu_duration = ", cpu_duration, ", gpu_duration = ", gpu_duration, "} ");
105+
else vsg::info(" entry(", first_index,") { ", int(first.type), ", ", first.reference, " }");
106+
107+
if (max_gpu_durection > targetGPUTime)
108+
{
109+
vsg::info(" GPU time too high ", max_gpu_durection, "ms is greated than target ", targetGPUTime, "ms");
110+
}
111+
#endif
112+
}
113+
114+
cpuTargetRatio = max_cpu_durection/targetCPUTime;
115+
gpuTargetRatio = max_gpu_durection/targetGPUTime;
116+
}
117+
118+
double maxTargetRatio = std::max({frameTargetRatio, cpuTargetRatio, gpuTargetRatio});
119+
120+
if (maxTargetRatio > 2.0)
121+
{
122+
vsg::info("Frame break on frameCount = ", previousFrameCount, " : maxTargetRatio = ", maxTargetRatio, " { frame = ", frameTargetRatio, ", cpu = ", cpuTargetRatio, ", gpu = ", gpuTargetRatio, " }");
123+
}
124+
else if (maxTargetRatio > 1.2)
125+
{
126+
vsg::info("Frame targets exceeded on frameCount = ", previousFrameCount, " : maxTargetRatio = ", maxTargetRatio, " { frame = ", frameTargetRatio, ", cpu = ", cpuTargetRatio, ", gpu = ", gpuTargetRatio, " }");
127+
}
128+
129+
previousFrameTime = viewer.getFrameStamp()->time;
130+
}
131+
132+

src/vsg/app/Viewer.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Viewer::Viewer() :
3131
updateOperations(UpdateOperations::create()),
3232
animationManager(AnimationManager::create()),
3333
status(vsg::ActivityStatus::create()),
34+
loadBalancing(vsg::LoadBalancing::create()),
3435
_firstFrame(true),
3536
_start_point(clock::now()),
3637
_frameStamp(FrameStamp::create(_start_point, 0, 0.0))
@@ -793,6 +794,8 @@ void Viewer::update()
793794
{
794795
CPU_INSTRUMENTATION_L1_NC(instrumentation, "Viewer update", COLOR_UPDATE);
795796

797+
if (loadBalancing) loadBalancing->update(*this);
798+
796799
// merge any updates from the DatabasePager
797800
for (const auto& task : recordAndSubmitTasks)
798801
{

src/vsg/utils/Profiler.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ uint64_t ProfileLog::report(std::ostream& out, uint64_t reference)
8787
{
8888
++i;
8989

90-
out << indent << "{ " << typeNames[first.type] << ", cpu_duration = " << cpu_duration << "ms, ";
90+
out << indent << "{ " << typeNames[first.type];
91+
if (first.type==ProfileLog::FRAME) out << ", frameCount = "<<first.frameCount;
92+
out << ", cpu_duration = " << cpu_duration << "ms, ";
9193
if (gpu_duration != 0.0) out << ", gpu_duration = " << gpu_duration << "ms, ";
9294

9395
auto itr = threadNames.find(first.thread_id);
@@ -107,7 +109,9 @@ uint64_t ProfileLog::report(std::ostream& out, uint64_t reference)
107109
out << indent << "} ";
108110
}
109111

110-
out << typeNames[first.type] << ", cpu_duration = " << cpu_duration << "ms, ";
112+
out << typeNames[first.type];
113+
if (first.type==ProfileLog::FRAME) out << ", frameCount = "<<first.frameCount;
114+
out << ", cpu_duration = " << cpu_duration << "ms, ";
111115
if (gpu_duration != 0.0) out << ", gpu_duration = " << gpu_duration << "ms, ";
112116

113117
auto itr = threadNames.find(first.thread_id);
@@ -173,6 +177,10 @@ VkResult Profiler::getGpuResults(FrameStatsCollection& frameStats) const
173177
}
174178
gpuStats->queryIndex = 0;
175179
}
180+
else if (result == VK_NOT_READY)
181+
{
182+
debug("Profiler::getGpuResults() ", gpuStats, ", query failed with result = ", result);
183+
}
176184
else
177185
{
178186
info("Profiler::getGpuResults() ", gpuStats, ", query failed with result = ", result);
@@ -190,9 +198,12 @@ void Profiler::setThreadName(const std::string& name) const
190198

191199
void Profiler::enterFrame(const SourceLocation* sl, uint64_t& reference, FrameStamp& frameStamp) const
192200
{
201+
frameCount = frameStamp.frameCount;
202+
193203
auto& entry = log->enter(reference, ProfileLog::FRAME);
194204
entry.sourceLocation = sl;
195205
entry.object = &frameStamp;
206+
entry.frameCount = frameCount;
196207

197208
getGpuResults(perFrameGPUStats[frameIndex]);
198209
}
@@ -203,6 +214,8 @@ void Profiler::leaveFrame(const SourceLocation* sl, uint64_t& reference, FrameSt
203214
auto& entry = log->leave(reference, ProfileLog::FRAME);
204215
entry.sourceLocation = sl;
205216
entry.object = &frameStamp;
217+
entry.frameCount = frameCount;
218+
206219
uint64_t endReference = reference;
207220

208221
if (endReference >= static_cast<uint64_t>(log->entries.size()))
@@ -236,6 +249,7 @@ void Profiler::enter(const SourceLocation* sl, uint64_t& reference, const Object
236249
auto& entry = log->enter(reference, ProfileLog::CPU);
237250
entry.sourceLocation = sl;
238251
entry.object = object;
252+
entry.frameCount = frameCount;
239253
}
240254
}
241255

@@ -246,6 +260,7 @@ void Profiler::leave(const SourceLocation* sl, uint64_t& reference, const Object
246260
auto& entry = log->leave(reference, ProfileLog::CPU);
247261
entry.sourceLocation = sl;
248262
entry.object = object;
263+
entry.frameCount = frameCount;
249264
}
250265
}
251266

@@ -285,6 +300,7 @@ void Profiler::enterCommandBuffer(const SourceLocation* sl, uint64_t& reference,
285300
auto& entry = log->enter(reference, ProfileLog::COMMAND_BUFFER);
286301
entry.sourceLocation = sl;
287302
entry.object = &commandBuffer;
303+
entry.frameCount = frameCount;
288304

289305
if (gpuStats)
290306
{
@@ -303,6 +319,7 @@ void Profiler::leaveCommandBuffer(const SourceLocation* sl, uint64_t& reference,
303319
auto& entry = log->leave(reference, ProfileLog::COMMAND_BUFFER);
304320
entry.sourceLocation = sl;
305321
entry.object = &commandBuffer;
322+
entry.frameCount = frameCount;
306323

307324
if (commandBuffer.gpuStats) commandBuffer.gpuStats->writeGpuTimestamp(commandBuffer, reference, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
308325
}
@@ -315,6 +332,7 @@ void Profiler::enter(const SourceLocation* sl, uint64_t& reference, CommandBuffe
315332
auto& entry = log->enter(reference, ProfileLog::GPU);
316333
entry.sourceLocation = sl;
317334
entry.object = object;
335+
entry.frameCount = frameCount;
318336

319337
if (commandBuffer.gpuStats) commandBuffer.gpuStats->writeGpuTimestamp(commandBuffer, reference, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
320338
}
@@ -327,6 +345,7 @@ void Profiler::leave(const SourceLocation* sl, uint64_t& reference, CommandBuffe
327345
auto& entry = log->leave(reference, ProfileLog::GPU);
328346
entry.sourceLocation = sl;
329347
entry.object = object;
348+
entry.frameCount = frameCount;
330349

331350
if (commandBuffer.gpuStats) commandBuffer.gpuStats->writeGpuTimestamp(commandBuffer, reference, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
332351
}

0 commit comments

Comments
 (0)