Cogs.Core
QualityService.cpp
1#include "Rendering/IContext.h"
2#include "Rendering/IGraphicsDevice.h"
3#include "Rendering/Statistics.h"
4
5#include "Foundation/Logging/Logger.h"
6
7#include "Context.h"
8#include "Services/Variables.h"
9
10#include "QualityService.h"
11
12namespace {
13 using namespace Cogs::Core;
14 Cogs::Logging::Log logger = Cogs::Logging::getLogger("QualityService");
15
16 struct Setting {
18 float defaultValue;
19 };
20
21 const Cogs::StringView qualityVariableName = "quality.setting";
22 const Cogs::StringView qualityFrameTimeAlphaName = "quality.frameTime.alpha"; // smoothing in (0,1], higher value discards values faster.
23 const Cogs::StringView qualityFrameTimeTargetName = "quality.frameTimeTarget";
24 const Cogs::StringView GPUMemTargetMBName = "quality.GPUMemTargetMB";
25
26 const Setting qualityScalingSpeed = { "quality.scaling.speed", 5e-2f }; // How fast to adjust scaling
27
28 const Setting qualityAssetSystemToleranceScaleCoarse = { "quality.assetSystem.toleranceScale.coarse", 10.f }; // 1 <= value
29 const Setting qualityAssetSystemToleranceScaleDetailed = { "quality.assetSystem.toleranceScale.detailed", 0.25f }; // value <= 1
30
31 const Setting qualityPotreeSystemToleranceScaleCoarse = { "quality.potreeSystem.toleranceScale.coarse", 3.f }; // 1 <= value
32 const Setting qualityPotreeSystemToleranceScaleDetailed = { "quality.potreeSystem.toleranceScale.detailed", 0.25f }; // value <= 1
33 const Setting qualityPotreeSystemChunkCountScaleCoarse = { "quality.potreeSystem.chunkCountScale.coarse", 1e-3f }; // value <= 1
34 const Setting qualityPotreeSystemChunkCountScaleDetailed = { "quality.potreeSystem.chunkCountScale.detailed", 2.f }; // 1 <= value
35
36 const Setting qualityOGC3DTilesSystemToleranceScaleCoarse = { "quality.ogc3DTilesSystem.toleranceScale.coarse", 3.f }; // 1 <= value
37 const Setting qualityOGC3DTilesSystemToleranceScaleDetailed = { "quality.ogc3DTilesSystem.toleranceScale.detailed", 0.25f }; // value <= 1
38 const Setting qualityOGC3DTilesSystemCacheControlCoarse = { "quality.ogc3DTilesSystem.cacheControl.coarse", 1e-3f }; // value <= 1
39 const Setting qualityOGC3DTilesSystemCacheControlDetailed = { "quality.ogc3DTilesSystem.cacheControl.detailed", 2.f }; // 1 <= value
40
41 float mapDecreasing(Context* context, const Setting& coarseSetting, const Setting& detailedSetting, float quality)
42 {
43 Variable* coarseVar = context->variables->get(coarseSetting.name);
44 if (coarseVar->isEmpty()) {
45 coarseVar->setFloat(coarseSetting.defaultValue);
46 }
47
48 Variable* detailedVar = context->variables->get(detailedSetting.name);
49 if (detailedVar->isEmpty()) {
50 detailedVar->setFloat(detailedSetting.defaultValue);
51 }
52
53 float coarse = std::max(1.f, coarseVar->getFloat());
54 float detailed = std::min(1.f, std::max(detailedVar->getFloat(), std::numeric_limits<float>::min()));
55
56 // Use average slope at x=1, value=1.
57 // Clamp slope to ensure a monotonically decreasing function over [0,2]
58 float a = coarse - 1.f;
59 float b = 1.f - detailed;
60 float c = glm::min(0.5f * (a + b), glm::min(a, b));
61
62 // One quadratic Bezier below 1 and one above.
63 float rv;
64 if (quality <= 1.f) {
65 const float x = quality;
66 rv = coarse * (1.f - x) * (1.f - x) + 2.f * (1.f + c) * (1.f - x) * x + x * x;
67 }
68 else {
69 const float x = quality - 1.f;
70 rv = (1.f - x) * (1.f - x) + 2.f * (1.f - c) * (1.f - x) * x + detailed * x * x;
71 }
72 return std::isfinite(rv) ? rv : 1.f;
73 }
74
75 float mapIncreasing(Context* context, const Setting& coarseSetting, const Setting& detailedSetting, float quality)
76 {
77 Variable* coarseVar = context->variables->get(coarseSetting.name);
78 if (coarseVar->isEmpty()) {
79 coarseVar->setFloat(coarseSetting.defaultValue);
80 }
81
82 Variable* detailedVar = context->variables->get(detailedSetting.name);
83 if (detailedVar->isEmpty()) {
84 detailedVar->setFloat(detailedSetting.defaultValue);
85 }
86
87 float coarse = std::min(1.f, std::max(coarseVar->getFloat(), std::numeric_limits<float>::min()));
88 float detailed = std::max(1.f, detailedVar->getFloat());
89
90 // Use average slope at x=1, value=1.
91 // Clamp slope to ensure a monotonically decreasing function over [0,2]
92 float a = coarse - 1.f;
93 float b = 1.f - detailed;
94 float c = glm::max(0.5f * (a + b), glm::min(a, b));
95
96 // One quadratic Bezier below 1 and one above.
97 float rv;
98 if (quality <= 1.f) {
99 const float x = quality;
100 rv = coarse * (1.f - x) * (1.f - x) + 2.f * (1.f + c) * (1.f - x) * x + x * x;
101 }
102 else {
103 const float x = quality - 1.f;
104 rv = (1.f - x) * (1.f - x) + 2.f * (1.f - c) * (1.f - x) * x + detailed * x * x;
105 }
106 return std::isfinite(rv) ? rv : 1.f;
107 }
108}
109
110Cogs::Core::QualityService::QualityService(class Context* context)
111 : context(context)
112{
113
114 for(size_t i=0; i<(size_t)MetricType::MetricTypeCount; i++){
115 for(size_t j=0; j<TIMES_COUNT; j++){
116 times[j][i] = 0.0f;
117 }
118 }
119}
120
121void Cogs::Core::QualityService::beginFrame()
122{
123 if(hasOneFrame){
124 end(MetricType::Idle);
125 times_idx = (times_idx+1)%TIMES_COUNT;
126 }
127 else{
128 hasOneFrame = true;
129 }
130 for(size_t i=0; i<(size_t)MetricType::MetricTypeCount; i++){
131 time[i] = {};
132 }
133 for(size_t i=0; i<(size_t)MetricType::MetricTypeCount; i++){
134 times[times_idx][i] = 0.0f;
135 }
136
137 {
138 Cogs::UploadStatistics upload_stats = context->device->getImmediateContext()->getLastUploadStatistics();
139 bufferUploadSize[times_idx] = upload_stats.bufferUploadSize;
140 textureUploadSize[times_idx] = upload_stats.textureUploadSize;
141 }
142
143 assert(stack_idx == 0);
144 begin(MetricType::Frame);
145
146 if (Variable* var = context->variables->get(qualityVariableName); !var->isEmpty()) {
147 qualitySetting = var->getFloat(qualitySetting);
148 if (qualitySetting < 0.0f) { qualitySetting = 0.f; }
149 if (200.f < qualitySetting || !std::isfinite(qualitySetting)) { qualitySetting = 200.f; }
150 var->setFloat(qualitySetting);
151 }
152 else {
153 context->variables->set(qualityVariableName, qualitySetting);
154 }
155
156 if (Variable* var = context->variables->get(qualityFrameTimeTargetName); !var->isEmpty()) {
157 frameTimeTarget = std::max(0.f, var->getFloat());
158 }
159 else {
160 context->variables->set(qualityFrameTimeTargetName, frameTimeTarget);
161 }
162
163 if (Variable* var = context->variables->get(GPUMemTargetMBName); !var->isEmpty()) {
164 gpuMemTargetMB = static_cast<uint32_t>(std::max(0, var->getInt()));
165 }
166 else {
167 context->variables->set(GPUMemTargetMBName, static_cast<int>(gpuMemTargetMB));
168 }
169
170 float maxQuality = 1e-2f * qualitySetting;
171 if ((0.f < frameTimeTarget) || (0 < gpuMemTargetMB)) {
172 float speed = qualityScalingSpeed.defaultValue;
173 if (Variable* var = context->variables->get(qualityScalingSpeed.name); !var->isEmpty()) {
174 speed = std::max(std::numeric_limits<float>::min(), var->getFloat());
175 }
176 else {
177 context->variables->set(qualityScalingSpeed.name, speed);
178 }
179
180 speed = speed * 1e-3f * avgFrameTime;
181 if (rapidBackoff) {
182 currentQuality = glm::clamp(currentQuality - 10.f * speed, 0.f, maxQuality);
183 resetFrameTime = true;
184 }
185 else if (throttleDown) {
186 currentQuality = glm::clamp(currentQuality - speed, 0.f, maxQuality);
187 }
188 else if (hasSlack) {
189 currentQuality = std::min(currentQuality + speed, maxQuality);
190 }
191 else {
192 currentQuality = std::min(currentQuality, maxQuality);
193 }
194 }
195 else {
196 currentQuality = maxQuality;
197 }
198
199 {
200 Variable* coarse = context->variables->get(qualityAssetSystemToleranceScaleCoarse.name);
201 if (coarse->isEmpty()) {
202 coarse->setFloat(qualityAssetSystemToleranceScaleCoarse.defaultValue);
203 }
204
205 Variable* detailed = context->variables->get(qualityAssetSystemToleranceScaleDetailed.name);
206 if (detailed->isEmpty()) {
207 detailed->setFloat(qualityAssetSystemToleranceScaleDetailed.defaultValue);
208 }
209 }
210
211 assetSystemToleranceScale = mapDecreasing(context,
212 qualityAssetSystemToleranceScaleCoarse,
213 qualityAssetSystemToleranceScaleDetailed,
214 currentQuality);
215
216 potreeSystemToleranceScale = mapDecreasing(context,
217 qualityPotreeSystemToleranceScaleCoarse,
218 qualityPotreeSystemToleranceScaleDetailed,
219 currentQuality);
220
221 potreeSystemChunkCountScale = mapIncreasing(context,
222 qualityPotreeSystemChunkCountScaleCoarse,
223 qualityPotreeSystemChunkCountScaleDetailed,
224 currentQuality);
225
226 ogc3DTilesSystemToleranceScale = mapDecreasing(context,
227 qualityOGC3DTilesSystemToleranceScaleCoarse,
228 qualityOGC3DTilesSystemToleranceScaleDetailed,
229 currentQuality);
230
231 ogc3DTilesSystemCacheControl = mapIncreasing(context,
232 qualityOGC3DTilesSystemCacheControlCoarse,
233 qualityOGC3DTilesSystemCacheControlDetailed,
234 currentQuality);
235 rapidBackoff = false;
236 throttleDown = false;
237 hasSlack = true;
238}
239
240void Cogs::Core::QualityService::endFrame()
241{
242 end(MetricType::Frame);
243
244 float frameTime = times[times_idx][(size_t)MetricType::Frame];
245 if (avgFrameTime == 0.f || resetFrameTime) {
246 resetFrameTime = false;
247 avgFrameTime = frameTime;
248 }
249 else {
250 float alpha = 1e-2f;
251 if (Variable* var = context->variables->get(qualityFrameTimeAlphaName); !var->isEmpty()) {
252 alpha = glm::clamp(var->getFloat(), std::numeric_limits<float>::min(), 1.f);
253 }
254 else {
255 context->variables->set(qualityFrameTimeAlphaName, alpha);
256 }
257 avgFrameTime = (1.f - alpha) * avgFrameTime + alpha * frameTime;
258 }
259
260 bool hasSlack = true; // TODO This is suspicus, hides class member
261 bool hasLimits = false;
262 if (0 < gpuMemTargetMB) {
263 const Cogs::ResourceStatistics resourceStats = context->device->getResourceStatistics();
264
265 float gpuMemTarget = float(gpuMemTargetMB);
266 float gpuMemValue = float(resourceStats.memoryConsumption()) / float(1024 * 1024);
267
268 if (1.5f * gpuMemTarget < gpuMemValue) {
269 requestQualityChange(QualityRequest::RapidBackoff);
270 return;
271 }
272
273 if (gpuMemTarget < gpuMemValue) {
274 requestQualityChange(QualityRequest::ThrottleDown);
275 return;
276 }
277
278 if (0.8f * gpuMemTarget < gpuMemValue) {
279 hasSlack = false;
280 }
281 hasLimits = true;
282 }
283
284 if (0.f < frameTimeTarget) {
285 if (1.5f * frameTimeTarget < avgFrameTime) {
286 requestQualityChange(QualityRequest::RapidBackoff);
287 return;
288 }
289
290 if (frameTimeTarget < avgFrameTime) {
291 requestQualityChange(QualityRequest::ThrottleDown);
292 return;
293 }
294
295 if (0.8f * frameTimeTarget < avgFrameTime) {
296 hasSlack = false;
297 }
298 hasLimits = true;
299 }
300
301 if (hasLimits && !hasSlack) {
302 requestQualityChange(QualityRequest::AtTarget);
303 return;
304 }
305
306 requestQualityChange(QualityRequest::HasSlack);
307
308 begin(MetricType::Idle);
309}
310
311void Cogs::Core::QualityService::requestQualityChange(QualityRequest request)
312{
313 switch (request) {
314 case QualityRequest::AtTarget:
315 hasSlack = false;
316 break;
317 case QualityRequest::HasSlack:
318 break;
319 case QualityRequest::ThrottleDown:
320 throttleDown = true;
321 break;
322 case QualityRequest::RapidBackoff:
323 rapidBackoff = true;
324 break;
325 default:
326 assert(false && "Illegal enum value");
327 break;
328 }
329}
330
331void QualityService::begin(MetricType metric)
332{
333 time[(size_t)metric] = Cogs::perfTime();
334 stack_idx++;
335}
336
337void QualityService::end(MetricType metric)
338{
339 times[times_idx][(size_t)metric] += (float)(perfTimeToSeconds(Cogs::perfTime()-time[(size_t)metric]));
340 stack_idx--;
341}
342
344{
345 size_t idx = (times_idx+TIMES_COUNT-1)%TIMES_COUNT;
346 float frameTime = times[idx][(size_t)MetricType::Frame];
347 float idleTime = times[idx][(size_t)MetricType::Idle];
348 float presentTime = times[idx][(size_t)MetricType::Present];
349 return {
350 .frameTime = frameTime + idleTime,
351 .cogsTime = frameTime,
352 .cpuTime = frameTime - presentTime,
353 .presentTime = presentTime,
354 .idleTime = idleTime
355 };
356}
357
358float QualityService::getMetric(MetricType metric)
359{
360 size_t idx = (times_idx+TIMES_COUNT-1)%TIMES_COUNT;
361 return times[idx][(size_t)metric];
362}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
FrameTimings getFrameTimings() const
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
COGSFOUNDATION_API TimePerf perfTime()
High resolution performance timer. Returns an implementation defined absolute timestamp,...
Runtime control variable.
Definition: Variables.h:27