Cogs.Core
AssetStatsCommand.cpp
1#define _USE_MATH_DEFINES
2#include <cmath>
3#include <queue>
4#include "rapidjson/document.h"
5#include "rapidjson/filewritestream.h"
6#include "rapidjson/prettywriter.h"
7#include "rapidjson/error/en.h"
8
9#include "AssetStatsCommand.h"
10
11#include "Foundation/Logging/Logger.h"
12#include "Foundation/Platform/IO.h"
13#include "Foundation/Platform/Timer.h"
14
15#include "Services/TaskManager.h"
16#include "Resources/AssetManager.h"
17#include "Resources/ModelManager.h"
18#include "Resources/MeshHelper.h"
19#include "Utilities/Math.h"
20#include "Components/Core/AssetComponent.h" // AssetFlags
21#include "Batch.h"
22
23
24
25namespace {
26 using namespace Cogs::Core;
27
28 Cogs::Logging::Log logger = Cogs::Logging::getLogger("AssetStats");
29
30 constexpr uint32_t NoValue = static_cast<uint32_t>(-1);
31 const Cogs::Core::StringRef bboxString = Cogs::Core::Strings::add("bbox");
32 const Cogs::Core::StringRef errorsString = Cogs::Core::Strings::add("errors");
33
34
35 struct RunningStatistics
36 {
37 void add(double value)
38 {
39 assert(std::isfinite(value));
40 count++;
41
42 double delta = value - mean;
43 mean += delta / count;
44
45 minValue = std::min(minValue, value);
46 maxValue = std::max(maxValue, value);
47 }
48
49 void add(RunningStatistics& other)
50 {
51 size_t newCount = count + other.count;
52 if (newCount) {
53 double w0 = double(count) / double(newCount);
54 double w1 = double(other.count) / double(newCount);
55
56 mean = w0 * mean + w1 * other.mean;
57 }
58 count = newCount;
59 minValue = std::min(minValue, other.minValue);
60 maxValue = std::max(maxValue, other.maxValue);
61 }
62
63 size_t count = 0;
64 double mean = 0.f;
65 double minValue = std::numeric_limits<double>::max();
66 double maxValue = -std::numeric_limits<double>::max();
67 };
68
69 struct ProjectedStatistics
70 {
71 float mean = 0.f;
72 float minValue = std::numeric_limits<float>::max();
73 float maxValue = -std::numeric_limits<float>::max();
74 };
75
76 struct ErrorRange;
77
78 struct ErrorRanges {
79 Cogs::Mutex lock;
80 std::vector<std::unique_ptr<ErrorRange>> items;
81 };
82
83 const char* issueId[] = {
84 "None",
85 "TriangleMeanMaxEdgeTooSmall",
86 "NearScreenCoverOutOfRange",
87 "FarScreenCoverOutOfRange",
88 "NearScreenCoverAtCoarsestLodTooBig",
89 "TooLittleTriangleIncrease",
90 };
91
92 enum struct IssueId : uint32_t
93 {
94 None,
95 TriangleMeanMaxEdgeTooSmall,
96 NearScreenCoverOutOfRange,
97 FarScreenCoverOutOfRange,
98 NearScreenCoverAtCoarsestLodTooBig,
99 TooLittleTriangleIncrease,
100 Count
101 };
102 static_assert(sizeof(issueId) == sizeof(issueId[0]) * size_t(IssueId::Count));
103
104 struct Issue
105 {
106 std::string message;
107 IssueId id = IssueId::None;
108 uint32_t severity = 0;
109 };
110
111 struct ErrorRange
112 {
113 ErrorRange(uint32_t level, float minError, float maxError) : level(level), minError(minError), maxError(maxError) {}
114
115 const uint32_t level;
116 const float minError;
117 const float maxError;
118
119 ErrorRanges childRanges;
120 std::vector<Issue> issues;
121
122 struct {
123 Cogs::Mutex lock;
124 RunningStatistics lodNodeBounds;
125 RunningStatistics modelBounds;
126 RunningStatistics triangleArea;
127 RunningStatistics triangleMinEdge;
128 RunningStatistics triangleMaxEdge;
129 uint32_t modelCount = 0;
130 };
131
132 struct {
133 ProjectedStatistics triangleArea;
134 ProjectedStatistics triangleMinEdge;
135 ProjectedStatistics triangleMaxEdge;
136 float distance = 0.f; // Distance used for projection
137 } pixels;
138
139 float nearSplitCover = -1.f;
140 float farSplitCover = -1.f;
141 };
142
143 ErrorRange* getErrorRange(ErrorRanges& ranges, const uint32_t level, const float minError, const float maxError)
144 {
145 Cogs::LockGuard guard(ranges.lock);
146 std::vector<std::unique_ptr<ErrorRange>>& items = ranges.items;
147
148 const size_t n = items.size();
149 for (size_t i = 0; i < n; i++) {
150 if (items[i]->minError == minError && items[i]->maxError == maxError && items[i]->level == level) {
151 return items[i].get();
152 }
153 }
154
155 items.emplace_back();
156 items[n] = std::make_unique<ErrorRange>(level, minError, maxError);
157 return items[n].get();
158 }
159
160 struct ResourceItem
161 {
162 enum struct Kind {
163 None,
164 AssetItem,
165 ModelItem
166 } kind = ResourceItem::Kind::None;
167
168 enum struct State {
169 None,
170 Issued,
171 Loading,
172 Processing,
173 Done,
174 Failed
175 } state = State::None;
176
177 TaskId task = NoTask;
178 std::string path;
179 ResourceHandleBase handle;
180 ErrorRange* errorRange = nullptr;
181 struct {
182 bool useRelativePaths = true;
183 } asset;
184
185 struct {
186 uint32_t part = ::NoValue;
187 } model;
188 };
189
190
191 struct ProcessModelScratch
192 {
198 };
199
200
201 struct LevelPaths
202 {
203 std::unordered_set<std::string> assetPaths;
204 std::unordered_set<std::string> modelPaths;
205 };
206
207 struct Ctx
208 {
209 Ctx() {}
210 // Just to make sure we do not get any unintended copies while capturing lambdas etc.
211 Ctx(const Ctx&) = delete;
212 Ctx& operator=(const Ctx&) = delete;
213
214 Context* context = nullptr;
215 TaskId group = NoTask;
216 Cogs::Atomic<bool> failure = false;
217 MemoryPoolAllocator<CrtAllocator> alloc = MemoryPoolAllocator<CrtAllocator>();
218
219 struct {
220 Cogs::Mutex lock;
221 std::list<std::unique_ptr<ProcessModelScratch>> processModelScratch;
222 } threadTemporaries;
223
224 struct {
225 Cogs::Mutex lock;
226 std::queue<std::unique_ptr<ResourceItem>> queue;
227 size_t count = 0;
228 } resourceQueue;
229
230 struct {
231 Cogs::Mutex lock;
232 std::vector<LevelPaths> levels;
233 } uniquePaths;
234
235 std::string source;
236 float referenceFieldOfView = float(M_PI / 4.0);
237 float referenceScreenSize = 1000.f;
238 struct {
239 float splitPointScreenCoverMin = 1.f / 2.f;
240 float splitPointScreenCoverMax = 2.f;
241 float splitPointScreenCoverFurthesSplitMax = 0.2f; // screen cover of furthest split
242 float splitPointMinTriangleEdgeLength = 10.f;
243 float perLevelMinTriangleIncrease = 1.5f;
244 } rules;
245
246 ErrorRanges rootRanges;
247
250 };
251
252 float infinityNormDistance(const glm::vec3& lo, const glm::vec3& hi)
253 {
254 glm::vec3 d = glm::abs(hi - lo);
255 return std::max(std::max(d.x, d.y), d.z);
256 }
257
258
259 void enqueueAsset(Ctx* ctx, ErrorRange* errorRange, const std::string& path, bool useRelativePaths)
260 {
261 if (ctx->failure) {
262 return;
263 }
264
265 { // scope for resourcequeue locing, and for assetItem lifetime
266 std::unique_ptr<ResourceItem> assetItem = std::make_unique<ResourceItem>(ResourceItem{
267 .kind = ResourceItem::Kind::AssetItem,
268 .state = ResourceItem::State::Issued,
269 .path = path,
270 .errorRange = errorRange,
271 .asset = {
272 .useRelativePaths = useRelativePaths
273 }
274 });
275
276 Cogs::LockGuard guard(ctx->resourceQueue.lock);
277 ctx->resourceQueue.queue.push(std::move(assetItem));
278 ctx->resourceQueue.count++;
279 }
280
281 { // scope for uniquepaths locking
282 Cogs::LockGuard guard(ctx->uniquePaths.lock);
283 if (ctx->uniquePaths.levels.size() <= errorRange->level) {
284 ctx->uniquePaths.levels.resize(errorRange->level + 1);
285 }
286 ctx->uniquePaths.levels[errorRange->level].assetPaths.insert(path);
287 }
288 }
289
290 void enqueueModel(Ctx* ctx, ErrorRange* errorRange, const std::string& path, uint32_t part)
291 {
292 if (ctx->failure) {
293 return;
294 }
295
296 { // scope for resourcequeue locking, and for modelItem lifetime
297 std::unique_ptr<ResourceItem> modelItem = std::make_unique<ResourceItem>(ResourceItem{
298 .kind = ResourceItem::Kind::ModelItem,
299 .state = ResourceItem::State::Issued,
300 .path = path,
301 .errorRange = errorRange,
302 .model = {
303 .part = part
304 }
305 });
306
307 Cogs::LockGuard guard(ctx->resourceQueue.lock);
308 ctx->resourceQueue.queue.push(std::move(modelItem));
309 ctx->resourceQueue.count++;
310 }
311
312 { // scope for uniquepaths locking
313 Cogs::LockGuard guard(ctx->uniquePaths.lock);
314 if (ctx->uniquePaths.levels.size() <= errorRange->level) {
315 ctx->uniquePaths.levels.resize(errorRange->level + 1);
316 }
317 ctx->uniquePaths.levels[errorRange->level].modelPaths.insert(path);
318 }
319 }
320
321 void processAssetEntityItem(Ctx* ctx, const std::string& parentPath, const Asset* asset, ErrorRange* errorRange, const size_t entityIx, bool useRelativePaths);
322
323 void processAssetEntityLodGroup(Ctx* ctx, const std::string& parentPath, const Asset* asset, ErrorRange* errorRange, const SceneEntityDefinition& entityDef, bool useRelativePaths)
324 {
325 const AssetDefinition& definition = asset->definition;
326 const PropertyRange propertyRange = asset->definition.scene.getProperties(entityDef);
327 std::span<const float> bboxValues = propertyRange.getProperty(bboxString, std::span<const float>());
328 if (bboxValues.size() != 6) {
329 LOG_ERROR(logger, "Lod level item bounding box has wrong number of elements (%zu != 6)", bboxValues.size());
330 ctx->failure = true;
331 return;
332 }
333
334 float bboxSize = infinityNormDistance(glm::vec3(bboxValues[0], bboxValues[1], bboxValues[2]),
335 glm::vec3(bboxValues[3], bboxValues[4], bboxValues[5]));
336 {
337 Cogs::LockGuard guard(errorRange->lock);
338 errorRange->lodNodeBounds.add(bboxSize);
339 }
340
341 if (entityDef.lod.numLods) {
342 std::span<const float> errorValues = propertyRange.getProperty(errorsString, std::span<const float>());
343 if (errorValues.size() != entityDef.lod.numLods) {
344 LOG_ERROR(logger, "Lod level item count (=%u) does not match number of error values (=%zu)", entityDef.lod.numLods, errorValues.size());
345 ctx->failure = true;
346 return;
347 }
348
349 uint32_t childIx = entityDef.firstChild;
350 for (uint32_t k = 0; k < entityDef.lod.numLods; k++) {
351 if (childIx == NoValue) {
352 LOG_ERROR(logger, "Empty Lod-level encountered.");
353 ctx->failure = true;
354 return;
355 }
356
357 uint32_t level = errorRange->level + entityDef.lod.numLods - k - 1;
358 float minErrorLevel = k == 0 ? errorRange->minError : glm::clamp(errorValues[k], errorRange->minError, errorRange->maxError);
359 float maxErrorLevel = glm::clamp(k + 1 < entityDef.lod.numLods ? errorValues[k + 1] : errorRange->maxError, errorRange->minError, errorRange->maxError);
360
361 ErrorRange* childErrorRange = getErrorRange(errorRange->childRanges, level, minErrorLevel, maxErrorLevel);
362 assert(childErrorRange->minError == minErrorLevel);
363 assert(childErrorRange->maxError == maxErrorLevel);
364
365 while (true) {
366 const SceneEntityDefinition& child = definition.scene[childIx];
367 processAssetEntityItem(ctx, parentPath, asset, childErrorRange, childIx, useRelativePaths);
368 childIx = child.nextSibling;
369 if (child.nextLodSibling == NoValue) break;
370 assert(child.nextLodSibling == childIx);
371 }
372 }
373 }
374 }
375
376 bool getSourcePath(Ctx* ctx, std::string& sourcePath, const std::string& parentPath, const Asset* asset,
377 const SceneEntityDefinition& entityDef, AssetResourceType assetResourceType, bool useRelativePaths)
378 {
379 const AssetDefinition& definition = asset->definition;
380 if (entityDef.asset.index == ::NoValue) {
381 LOG_ERROR(logger, "Asset without source");
382 ctx->failure = true;
383 return false;
384 }
385 const PropertyInfo& sourceInfo = definition.scene.properties.getPropertyByIndex(entityDef.asset.index);
386 switch (sourceInfo.type) {
387 case PropertyType::UnsignedInteger:
388 sourcePath = createAssetResourcePathFromIndex(sourceInfo.uintValue, assetResourceType);
389 break;
390 case PropertyType::String: {
391 sourcePath = definition.scene.properties.getString(sourceInfo).to_string();
392 break;
393 }
394 default:
395 LOG_ERROR(logger, "Source property is neither uint nor string");
396 ctx->failure = true;
397 return false;
398 }
399 if (useRelativePaths && Cogs::IO::isRelative(sourcePath)) {
400 sourcePath = Cogs::IO::combine(parentPath, sourcePath);
401 }
402 return true;
403 }
404
405 void processAssetEntityItem(Ctx* ctx,
406 const std::string& parentPath,
407 const Asset* asset,
408 ErrorRange* errorRange,
409 const size_t entityIx,
410 bool useRelativePaths)
411 {
412 const AssetDefinition& definition = asset->definition;
413 const SceneEntityDefinition& entityDef = asset->definition.scene.entities[entityIx];
414
415 if (asset->definition.scene.entities.size() <= entityIx) {
416 LOG_ERROR(logger, "Illegal entity ix %zu", entityIx);
417 ctx->failure = true;
418 return;
419 }
420 if (entityDef.isLodGroup()) {
421 processAssetEntityLodGroup(ctx, parentPath, asset, errorRange, entityDef, useRelativePaths);
422 }
423 else if (entityDef.isAsset()) {
424 std::string sourcePath;
425 if (getSourcePath(ctx, sourcePath, parentPath, asset, entityDef, AssetResourceType::Asset, useRelativePaths))
426 {
427 enqueueAsset(ctx, errorRange, sourcePath, entityDef.asset.flags & uint32_t(AssetFlags::RelativePaths));
428 }
429 }
430 else if (entityDef.isModel()) {
431 std::string sourcePath;
432 if (getSourcePath(ctx, sourcePath, parentPath, asset, entityDef, AssetResourceType::Model, useRelativePaths))
433 {
434 enqueueModel(ctx, errorRange, sourcePath, entityDef.model.part);
435 }
436 }
437 else if (!entityDef.isEmpty()) {
438
439 if (entityDef.numFields) {
440 for (const FieldValue& entry : std::span(definition.scene.fieldValues).subspan(entityDef.firstField, entityDef.numFields)) {
441
442 if (entry.componentId == ctx->assetCompTypeId && entry.fieldId == ctx->assetCompAssetFieldId && entry.type == DefaultValueType::Asset) {
443
444 std::string sourcePath = entry.value;
445 if (useRelativePaths && Cogs::IO::isRelative(sourcePath)) {
446 sourcePath = Cogs::IO::combine(parentPath, sourcePath);
447 }
448
449 if (!Cogs::IO::exists(sourcePath)) {
450 LOG_ERROR(logger, "Path %s does not exist", sourcePath.c_str());
451 ctx->failure = true;
452 return;
453 }
454
455 enqueueAsset(ctx, errorRange, sourcePath, useRelativePaths);
456 }
457 }
458 }
459
460 }
461 }
462
463 void processModel(Ctx* /*ctx*/, ProcessModelScratch* scratch, ErrorRange* errorRange, Model* model, uint32_t part)
464 {
465
466 size_t begin = part != ::NoValue ? part : 0;
467 size_t end = part != ::NoValue ? part + 1 : model->parts.size();
468 assert(end > begin && "Invalid part range");
469
470 size_t count = end - begin;
471 assert(count && "Invalid part range.");
472
473
474 Cogs::Memory::TypedBuffer<glm::mat4>& worldFromModel = scratch->worldFromModel;
475 worldFromModel.resize(count);
476
477 RunningStatistics modelBounds;
478 RunningStatistics triangleArea;
479 RunningStatistics triangleMinEdge;
480 RunningStatistics triangleMaxEdge;
481 for (size_t i = 0; i < count; i++) {
482 const ModelPart& modelPart = model->parts[begin + i];
483
484 const glm::mat4& L = model->getPartTransform(modelPart);
485 if (modelPart.parentIndex != ::NoValue) {
486 assert(begin <= modelPart.parentIndex);
487 assert(modelPart.parentIndex < begin + i);
488 worldFromModel[i] = worldFromModel[modelPart.parentIndex - begin] * L;
489 }
490 else {
491 worldFromModel[i] = L;
492 }
493
494 if (modelPart.meshIndex != ::NoValue) {
495 const MeshHandle mesh = model->meshes[modelPart.meshIndex];
496
497 glm::vec3 minPos = glm::vec3(std::numeric_limits<float>::max());
498 glm::vec3 maxPos = glm::vec3(-std::numeric_limits<float>::max());
499
500 // Transform positions from model space to world space
501 Cogs::Memory::TypedBuffer<glm::vec3>& worldPos = scratch->worldPos;
502 {
503 std::span<const glm::vec3> modelPos = extractSemanticStreamVec3(scratch->posBacking, mesh, Cogs::ElementSemantic::Position, 0);
504 worldPos.resize(modelPos.size());
505
506 const glm::mat4& M = worldFromModel[i];
507 for (size_t k = 0, n = modelPos.size(); k < n; k++) {
508 const glm::vec4 h = M * glm::vec4(modelPos[k], 1.f);
509 worldPos[k] = (1.f / h.w) * glm::vec3(h);
510 }
511 }
512
513 // Get indices or create dummy indices
514 std::span<const uint32_t> idx;
515 if (mesh->isIndexed()) {
516 idx = extractIndexStreamUint32(scratch->idxBacking, mesh);
517 }
518 else {
519 size_t np = worldPos.size();
520 size_t ni = scratch->dummyIndices.size();
521 if (ni < np) {
522 scratch->dummyIndices.resize(np, true);
523 for (size_t k = ni; k < np; k++) {
524 scratch->dummyIndices[k] = uint32_t(k);
525 }
526 }
527 idx = std::span<const uint32_t>(scratch->dummyIndices.data(), np);
528 }
529
530 // Traverse primitives
531 size_t startIndex = std::min(size_t(modelPart.startIndex), idx.size());
532 size_t vertexCount = std::min(size_t(modelPart.vertexCount), idx.size() - startIndex);
533
534 switch (modelPart.primitiveType) {
536 for (size_t k = startIndex; k + 3 <= startIndex + vertexCount; k += 3) {
537 const glm::vec3& a = worldPos[idx[k + 0]];
538 const glm::vec3& b = worldPos[idx[k + 1]];
539 const glm::vec3& c = worldPos[idx[k + 2]];
540
541 const float e_ab = glm::distance(a, b);
542 const float e_bc = glm::distance(b, c);
543 const float e_ca = glm::distance(c, a);
544 triangleMinEdge.add(std::min(std::min(e_ab, e_bc), e_ca));
545 triangleMaxEdge.add(std::max(std::max(e_ab, e_bc), e_ca));
546 triangleArea.add(0.5f * glm::length(glm::cross(b - a, c - a)));
547 minPos = glm::min(minPos, glm::min(a, glm::min(b, c)));
548 maxPos = glm::max(maxPos, glm::max(a, glm::max(b, c)));
549 }
550 break;
551 default:
552 LOG_WARNING(logger, "Unsupported primitive type %u", uint32_t(modelPart.primitiveType));
553 break;
554 }
555
556
557 if (modelPart.boundsIndex != ::NoValue) {
558 const Cogs::Geometry::BoundingBox& bbox = model->bounds[modelPart.boundsIndex];
559 const glm::vec3 c0 = euclidean(worldFromModel[i] * glm::vec4(bbox.min.x, bbox.min.y, bbox.min.z, 1.f));
560 const glm::vec3 c1 = euclidean(worldFromModel[i] * glm::vec4(bbox.min.x, bbox.min.y, bbox.max.z, 1.f));
561 const glm::vec3 c2 = euclidean(worldFromModel[i] * glm::vec4(bbox.min.x, bbox.max.y, bbox.min.z, 1.f));
562 const glm::vec3 c3 = euclidean(worldFromModel[i] * glm::vec4(bbox.min.x, bbox.max.y, bbox.max.z, 1.f));
563 const glm::vec3 c4 = euclidean(worldFromModel[i] * glm::vec4(bbox.max.x, bbox.min.y, bbox.min.z, 1.f));
564 const glm::vec3 c5 = euclidean(worldFromModel[i] * glm::vec4(bbox.max.x, bbox.min.y, bbox.max.z, 1.f));
565 const glm::vec3 c6 = euclidean(worldFromModel[i] * glm::vec4(bbox.max.x, bbox.max.y, bbox.min.z, 1.f));
566 const glm::vec3 c7 = euclidean(worldFromModel[i] * glm::vec4(bbox.max.x, bbox.max.y, bbox.max.z, 1.f));
567 const glm::vec3 worldMin = glm::min(glm::min(glm::min(c0, c1),
568 glm::min(c2, c3)),
569 glm::min(glm::min(c4, c5),
570 glm::min(c6, c7)));
571 const glm::vec3 worldMax = glm::max(glm::max(glm::max(c0, c1),
572 glm::max(c2, c3)),
573 glm::max(glm::max(c4, c5),
574 glm::max(c6, c7)));
575 modelBounds.add(infinityNormDistance(worldMin, worldMax));
576 }
577 else if (minPos.x <= maxPos.x) {
578 modelBounds.add(infinityNormDistance(minPos, maxPos));
579 }
580 }
581 }
582
583 Cogs::LockGuard guard(errorRange->lock);
584
585 errorRange->triangleArea.add(triangleArea);
586 errorRange->triangleMinEdge.add(triangleMinEdge);
587 errorRange->triangleMaxEdge.add(triangleMaxEdge);
588 errorRange->modelBounds.add(modelBounds);
589 errorRange->modelCount++;
590 }
591
592 void processResource(Ctx* ctx, ResourceItem* item)
593 {
594 if (item->kind == ResourceItem::Kind::AssetItem) {
595 assert(item->handle->getType() == ResourceTypes::Asset);
596 Asset* asset = static_cast<Asset*>(item->handle.get());
597 AssetDefinition& definition = asset->definition;
598 std::string sourceDirectoryPath = Cogs::IO::parentPath(item->path);
599 for (size_t i = 0; i < definition.scene.entities.size(); ++i) {
600 SceneEntityDefinition& entityDef = definition.scene.entities[i];
601 if (entityDef.parentIndex == ::NoValue) {
602 processAssetEntityItem(ctx, sourceDirectoryPath, asset, item->errorRange, i, item->asset.useRelativePaths);
603 }
604 }
605 }
606
607 else if (item->kind == ResourceItem::Kind::ModelItem) {
608 std::unique_ptr<ProcessModelScratch> scratch;
609 {
610 Cogs::LockGuard guard(ctx->threadTemporaries.lock);
611 if (!ctx->threadTemporaries.processModelScratch.empty()) {
612 scratch = std::move(ctx->threadTemporaries.processModelScratch.front());
613 ctx->threadTemporaries.processModelScratch.pop_front();
614 assert(scratch);
615 }
616 }
617 if (!scratch) {
618 scratch = std::make_unique<ProcessModelScratch>();
619 }
620
621 assert(item->handle->getType() == ResourceTypes::Model);
622 Model* model = static_cast<Model*>(item->handle.get());
623 processModel(ctx, scratch.get(), item->errorRange, model, item->model.part);
624
625 assert(scratch);
626 {
627 Cogs::LockGuard guard(ctx->threadTemporaries.lock);
628 ctx->threadTemporaries.processModelScratch.push_front(std::move(scratch));
629 }
630 }
631
632 else {
633 assert(false && "Illegal workitem kind");
634 }
635
636 }
637
638 void processAssetQueueUntilDone(Ctx* ctx, size_t maxActive)
639 {
640 LOG_DEBUG(logger, "Processing asset files...");
641 ctx->group = ctx->context->taskManager->createGroup(TaskManager::ResourceQueue);
642
643 size_t processedCount = 0;
644
645 double lastReport = -1000.0;
646 Cogs::Timer timer = Cogs::Timer::startNew();
647 std::vector<std::unique_ptr<ResourceItem>> activeResourceItems, activeResourceItemsNext;
648 do {
649 runFrames(ctx->context, 1, true, false);
650
651 activeResourceItemsNext.clear();
652 for (std::unique_ptr<ResourceItem>& assetItem : activeResourceItems) {
653
654 switch (assetItem->state) {
655 case ResourceItem::State::None: [[fallthrough]];
656 case ResourceItem::State::Issued:
657 assert(false && "Invalid state");
658 break;
659
660 case ResourceItem::State::Loading:
661 // Check if item has finished loading
662
663 if (assetItem->handle->isLoaded()) {
664
665 if (assetItem->kind == ResourceItem::Kind::ModelItem) {
666 Model* model = static_cast<Model*>(assetItem->handle.get());
667 for (const MeshHandle& mesh : model->meshes) {
668 if (!mesh->isLoaded()) {
669 if (mesh->hasFailedLoad()) {
670 assetItem->state = ResourceItem::State::Failed;
671 }
672 goto not_done_loading;
673 }
674 }
675 }
676
677 // Then try to fire off task
678 assetItem->state = ResourceItem::State::Processing;
679 assetItem->task = ctx->context->taskManager->enqueueChild(ctx->group,
680 [ctx, item_ = assetItem.get()]()
681 {
682 processResource(ctx, item_);
683 });
684 if (!assetItem->task.isValid()) {
685 assetItem->state = ResourceItem::State::Failed;
686 }
687 }
688 else if (assetItem->handle->hasFailedLoad()) {
689 assetItem->state = ResourceItem::State::Failed;
690 }
691 not_done_loading:
692 break;
693
694 case ResourceItem::State::Processing:
695 // Check if item task has finished
696 if (!ctx->context->taskManager->isActive(assetItem->task)) {
697 assetItem->state = ResourceItem::State::Done;
698 }
699 break;
700
701 case ResourceItem::State::Done: break; // Handled below
702 case ResourceItem::State::Failed: break; // Handled below
703
704 default:
705 assert(false && "Invalid enum");
706 break;
707 }
708
709 // Keep tasks that are running and filter out finished/failed tasks
710 switch (assetItem->state) {
711 case ResourceItem::State::Loading: [[fallthrough]];
712 case ResourceItem::State::Processing:
713 // Keep item in active set
714 activeResourceItemsNext.emplace_back(std::move(assetItem));
715 break;
716 case ResourceItem::State::Done: break; // Release item
717 case ResourceItem::State::Failed: // Flag failure and release item
718 ctx->failure = true;
719 break;
720 default:
721 assert(false && "Invalid enum");
722 break;
723 }
724 }
725
726 // Make queued items active
727 while (activeResourceItemsNext.size() < maxActive) {
728 std::unique_ptr<ResourceItem> item;
729 {
730 Cogs::LockGuard guard(ctx->resourceQueue.lock);
731 if (ctx->resourceQueue.queue.empty()) {
732 assert(ctx->resourceQueue.count == 0);
733 break;
734 }
735 item = std::move(ctx->resourceQueue.queue.front());
736 ctx->resourceQueue.queue.pop();
737 ctx->resourceQueue.count--;
738 }
739 assert(item);
740 assert(item->state == ResourceItem::State::Issued);
741 item->state = ResourceItem::State::Loading;
742 if (item->kind == ResourceItem::Kind::AssetItem) {
743 item->handle = ctx->context->assetManager->loadAsset(item->path, NoResourceId, AssetLoadFlags::ForceUnique);
744 }
745 else if (item->kind == ResourceItem::Kind::ModelItem) {
746 item->handle = ctx->context->modelManager->loadModel(item->path, NoResourceId, ModelLoadFlags::None);
747 }
748
749 activeResourceItemsNext.emplace_back(std::move(item));
750 processedCount++;
751 }
752
753 activeResourceItems.swap(activeResourceItemsNext);
754
755 double elapsed = timer.elapsedSeconds();
756 if (activeResourceItems.empty() || std::floor(lastReport) != std::floor(elapsed)) {
757 lastReport = elapsed;
758
759 size_t queueCount = 0;
760 {
761 Cogs::LockGuard guard(ctx->resourceQueue.lock);
762 queueCount = ctx->resourceQueue.count;
763 }
764
765 LOG_DEBUG(logger, "%.0fs: assets: done=%zu act=%zu queue=%zu",
766 std::floor(elapsed), processedCount, activeResourceItems.size(), queueCount);
767 }
768 } while (!activeResourceItems.empty());
769
770 Cogs::LockGuard guard(ctx->resourceQueue.lock);
771 assert(ctx->resourceQueue.count == 0);
772
773 ctx->context->taskManager->wait(ctx->group);
774 ctx->group = NoTask;
775
776 LOG_DEBUG(logger, "Finished processing %zu asset/model items in %.1f seconds", processedCount, timer.elapsedSeconds());
777 }
778
779
780
781 Value getStatisticsAsJson(Ctx& ctx, const RunningStatistics& stats, bool includeCount)
782 {
783 Value statsJson(kObjectType);
784 if (includeCount) {
785 statsJson.AddMember("count", static_cast<uint64_t>(stats.count), ctx.alloc);
786 }
787 statsJson.AddMember("mean", stats.mean, ctx.alloc);
788 statsJson.AddMember("minValue", stats.minValue, ctx.alloc);
789 statsJson.AddMember("maxValue", stats.maxValue, ctx.alloc);
790 return statsJson;
791 }
792
793 Value getStatisticsAsJson(Ctx& ctx, const ProjectedStatistics& stats)
794 {
795 Value statsJson(kObjectType);
796 statsJson.AddMember("mean", stats.mean, ctx.alloc);
797 statsJson.AddMember("minValue", stats.minValue, ctx.alloc);
798 statsJson.AddMember("maxValue", stats.maxValue, ctx.alloc);
799 return statsJson;
800 }
801
802 Value getRangesAsJson(Ctx& ctx, ErrorRanges& ranges)
803 {
804 Value rangesJson(kArrayType);
805 for (const std::unique_ptr<ErrorRange>& range : ranges.items) {
806 Value rangeJson(kObjectType);
807
808 rangeJson.AddMember("minDistance", range->minError, ctx.alloc);
809 if (std::isfinite(range->maxError)) { rangeJson.AddMember("maxDistance", range->maxError, ctx.alloc); }
810 else { rangeJson.AddMember("maxDistance", Value("inf", ctx.alloc), ctx.alloc); }
811
812 if (range->lodNodeBounds.count) {
813 rangeJson.AddMember("lodNodeBounds", getStatisticsAsJson(ctx, range->lodNodeBounds, true), ctx.alloc);
814 }
815 if (range->modelBounds.count) {
816 rangeJson.AddMember("modelBounds", getStatisticsAsJson(ctx, range->modelBounds, true), ctx.alloc);
817 }
818 if (range->triangleArea.count) {
819 rangeJson.AddMember("triangleCount", static_cast<uint64_t>(range->triangleArea.count), ctx.alloc);
820 rangeJson.AddMember("triangleArea", getStatisticsAsJson(ctx, range->triangleArea, false), ctx.alloc);
821 rangeJson.AddMember("triangleMinEdge", getStatisticsAsJson(ctx, range->triangleMinEdge, false), ctx.alloc);
822 rangeJson.AddMember("triangleMaxEdge", getStatisticsAsJson(ctx, range->triangleMaxEdge, false), ctx.alloc);
823 if (0.f < range->pixels.distance) {
824 Value projJson(kObjectType);
825 projJson.AddMember("screenDistance", range->pixels.distance, ctx.alloc);
826 projJson.AddMember("triangleArea", getStatisticsAsJson(ctx, range->pixels.triangleArea), ctx.alloc);
827 projJson.AddMember("triangleMinEdge", getStatisticsAsJson(ctx, range->pixels.triangleMinEdge), ctx.alloc);
828 projJson.AddMember("triangleMaxEdge", getStatisticsAsJson(ctx, range->pixels.triangleMaxEdge), ctx.alloc);
829 rangeJson.AddMember("pixels", projJson, ctx.alloc);
830 }
831 }
832 if (!range->issues.empty()) {
833 Value issuesJson(kArrayType);
834 for (const Issue& issue : range->issues) {
835 Value issueJson(kObjectType);
836
837 issueJson.AddMember("id", Value(issueId[size_t(issue.id)], ctx.alloc), ctx.alloc);
838 issueJson.AddMember("severity", issue.severity, ctx.alloc);
839 issueJson.AddMember("what", Value(issue.message.c_str(), ctx.alloc), ctx.alloc);
840 issuesJson.PushBack(issueJson, ctx.alloc);
841 }
842 rangeJson.AddMember("issues", issuesJson, ctx.alloc);
843 }
844 if (!range->childRanges.items.empty()) {
845 rangeJson.AddMember("children", getRangesAsJson(ctx, range->childRanges), ctx.alloc);
846 }
847 rangesJson.PushBack(rangeJson, ctx.alloc);
848 }
849 return rangesJson;
850 }
851
852 void writeStatsAsJson(Ctx& ctx, const std::string& path)
853 {
854 if (ctx.failure) return;
855
856 Document doc(&ctx.alloc);
857 doc.SetObject();
858
859 Value rules(kObjectType);
860 rules.AddMember("splitPointScreenCoverMin", ctx.rules.splitPointScreenCoverMin, ctx.alloc);
861 rules.AddMember("splitPointScreenCoverMax", ctx.rules.splitPointScreenCoverMax, ctx.alloc);
862 rules.AddMember("splitPointScreenCoverFurthesSplitMax", ctx.rules.splitPointScreenCoverFurthesSplitMax, ctx.alloc);
863 rules.AddMember("splitPointMinTriangleEdgeLength", ctx.rules.splitPointMinTriangleEdgeLength, ctx.alloc);
864 rules.AddMember("perLevelMinTriangleIncrease", ctx.rules.perLevelMinTriangleIncrease, ctx.alloc);
865
866 Value input(kObjectType);
867 input.AddMember("source", Value(ctx.source.c_str(), ctx.alloc), ctx.alloc);
868 input.AddMember("referenceFieldOfViewDegrees", glm::degrees(ctx.referenceFieldOfView), ctx.alloc);
869 input.AddMember("refenceScreenPixelSize", ctx.referenceScreenSize, ctx.alloc);
870 input.AddMember("rules", rules, ctx.alloc);
871
872 doc.AddMember("input", input, ctx.alloc);
873 doc.AddMember("ranges", getRangesAsJson(ctx, ctx.rootRanges), ctx.alloc);
874
875 FILE* file = fopen(path.c_str(), "w");
876 if (!file) {
877 LOG_ERROR(logger, "Error opening %s for writing", path.c_str());
878 ctx.failure = true;
879 return;
880 }
881
882 char buffer[4096];
883 FileWriteStream stream(file, buffer, sizeof(buffer));
884 PrettyWriter<FileWriteStream> writer(stream);
885 writer.SetMaxDecimalPlaces(4);
886
887 bool ok = doc.Accept(writer);
888 stream.Flush();
889 fclose(file);
890 if (!ok) {
891 LOG_ERROR(logger, "Error writing json into %s", path.c_str());
892 ctx.failure = true;
893 }
894 else {
895 LOG_DEBUG(logger, "Wrote %s", path.c_str());
896 }
897 }
898
899 void addIssue(Ctx& /*ctx*/, ErrorRange* range, IssueId id, uint32_t severity, const char* format, ...)
900 {
901 thread_local static char buffer[256] = {};
902 va_list args;
903 va_start(args, format);
904 int n = vsnprintf(buffer, sizeof(buffer), format, args);
905 va_end(args);
906 assert(0 <= n);
907 buffer[sizeof(buffer) - 1] = '\0';
908 range->issues.push_back(Issue{ .message = buffer, .id = id, .severity = severity });
909 }
910
911 void analyzeRanges(Ctx& ctx, ErrorRanges& ranges, float screenSizeWorld, float referenceScreenSize, size_t trianglesAtParentLevel)
912 {
913 size_t childBoundsCount = 0;
914 size_t boundsCount = 0;
915 size_t trianglesAtLevel = 0;
916 for (std::unique_ptr<ErrorRange>& range : ranges.items) {
917 trianglesAtLevel += range->triangleArea.count;
918 boundsCount += range->lodNodeBounds.count + range->modelBounds.count;
919 for (std::unique_ptr<ErrorRange>& childRange : range->childRanges.items) {
920 childBoundsCount += childRange->lodNodeBounds.count + childRange->modelBounds.count;
921 }
922 }
923
924 // If we are more than two nodes (lod node + model node) at this level, we have been refined.
925 bool refinedLevel = 2 < boundsCount;
926
927 // If children have more than two nodes, they are refined.
928 bool refinedChildLevel = 2 < childBoundsCount;
929
930 for (std::unique_ptr<ErrorRange>& range : ranges.items) {
931 analyzeRanges(ctx, range->childRanges, screenSizeWorld, referenceScreenSize, trianglesAtLevel);
932
933
934 if (range->lodNodeBounds.count) {
935 if (std::isfinite(range->maxError)) {
936 range->farSplitCover = float(range->lodNodeBounds.mean) / (screenSizeWorld * range->maxError);
937
938 if (refinedChildLevel) {
939 if ((2.f * range->farSplitCover < ctx.rules.splitPointScreenCoverMin) || (ctx.rules.splitPointScreenCoverMax < 2.f * range->farSplitCover))
940 {
941 addIssue(ctx, range.get(), IssueId::FarScreenCoverOutOfRange, 0,
942 "At %.2f: 2 x farScreenCover=%.2f is outside ideal range [%.2f, %.2f]",
943 range->maxError, 2.f * range->farSplitCover, ctx.rules.splitPointScreenCoverMin, ctx.rules.splitPointScreenCoverMax);
944 }
945 }
946 }
947 }
948
949 if (range->triangleArea.count) {
950
951 if (double(range->triangleArea.count) < ctx.rules.perLevelMinTriangleIncrease * double(trianglesAtParentLevel)) {
952 addIssue(ctx, range.get(), IssueId::TooLittleTriangleIncrease, 0,
953 "Current level has %zu triangles that is only %.1f%% of %zu triangles on parent level",
954 range->triangleArea.count, 100.0 * double(range->triangleArea.count) / double(trianglesAtParentLevel), trianglesAtParentLevel);
955 }
956 }
957
958
959 if (range->modelBounds.count) {
960 if (0.f < range->minError) {
961
962 range->nearSplitCover = float(range->modelBounds.mean) / (screenSizeWorld * range->minError);
963
964 if (refinedLevel) {
965 if ((range->nearSplitCover < ctx.rules.splitPointScreenCoverMin) || (ctx.rules.splitPointScreenCoverMax < range->nearSplitCover))
966 {
967 addIssue(ctx, range.get(), IssueId::NearScreenCoverOutOfRange, 0,
968 "At %.2f: nearScreenCover=%.2f is outside ideal range[%.2f, %.2f]",
969 range->minError, range->nearSplitCover, ctx.rules.splitPointScreenCoverMin, ctx.rules.splitPointScreenCoverMax);
970 }
971 }
972
973 if (!std::isfinite(range->maxError) && ctx.rules.splitPointScreenCoverFurthesSplitMax < range->nearSplitCover) {
974 addIssue(ctx, range.get(), IssueId::NearScreenCoverAtCoarsestLodTooBig, 0,
975 "At %.2f: splitPointScreenCover=%.2f of coarsest level is larger than %.2f",
976 range->minError, range->nearSplitCover, ctx.rules.splitPointScreenCoverFurthesSplitMax);
977 }
978
979 }
980
981 if (0.f < range->minError) {
982 range->pixels.distance = range->minError;
983 double pixelProj = referenceScreenSize / (screenSizeWorld * range->pixels.distance);
984 if (range->triangleArea.count) {
985 range->pixels.triangleArea.mean = float(pixelProj * pixelProj * range->triangleArea.mean);
986 range->pixels.triangleArea.minValue = float(pixelProj * pixelProj * range->triangleArea.minValue);
987 range->pixels.triangleArea.maxValue = float(pixelProj * pixelProj * range->triangleArea.maxValue);
988 }
989 if (range->triangleMinEdge.count) {
990 range->pixels.triangleMinEdge.mean = float(pixelProj * range->triangleMinEdge.mean);
991 range->pixels.triangleMinEdge.minValue = float(pixelProj * range->triangleMinEdge.minValue);
992 range->pixels.triangleMinEdge.maxValue = float(pixelProj * range->triangleMinEdge.maxValue);
993 }
994 if (range->triangleMaxEdge.count) {
995 range->pixels.triangleMaxEdge.mean = float(pixelProj * range->triangleMaxEdge.mean);
996 range->pixels.triangleMaxEdge.minValue = float(pixelProj * range->triangleMaxEdge.minValue);
997 range->pixels.triangleMaxEdge.maxValue = float(pixelProj * range->triangleMaxEdge.maxValue);
998
999 if (range->pixels.triangleMaxEdge.mean < ctx.rules.splitPointMinTriangleEdgeLength) {
1000 addIssue(ctx, range.get(), IssueId::TriangleMeanMaxEdgeTooSmall, 0, "Triangle mean max edge length=%.2f is less than %.2f pixels",
1001 range->pixels.triangleMaxEdge.mean, ctx.rules.splitPointMinTriangleEdgeLength);
1002 }
1003 }
1004 }
1005 }
1006 }
1007 }
1008
1009 void dumpRanges(ErrorRanges& ranges, uint32_t indent)
1010 {
1011 const char* indents = " ";
1012
1013 for (size_t i = ranges.items.size(); 0 < i; --i) {
1014 const std::unique_ptr<ErrorRange>& range = ranges.items[i - 1];
1015 float rangeSize = range->maxError - range->minError;
1016 LOG_DEBUG(logger, "");
1017 LOG_DEBUG(logger, "%.*s+- errorRange=[%f, %f), errorRangeSize=%f", 4 * indent, indents, range->minError, range->maxError, rangeSize);
1018 if (range->lodNodeBounds.count) {
1019 LOG_DEBUG(logger, "%.*s lodNodeBounds: count=%zu world={ mean=%.1f min=%.1f max=%.1f }",
1020 4 * indent, indents, range->lodNodeBounds.count,
1021 range->lodNodeBounds.mean, range->lodNodeBounds.minValue, range->lodNodeBounds.maxValue);
1022 }
1023 if (range->modelBounds.count) {
1024 LOG_DEBUG(logger, "%.*s modelBounds: count=%zu world={ mean=%.1f min=%.1f max=%.1f }",
1025 4 * indent, indents, range->modelBounds.count,
1026 range->modelBounds.mean, range->modelBounds.minValue, range->modelBounds.maxValue);
1027
1028 }
1029 if (0.f < range->nearSplitCover) {
1030 LOG_DEBUG(logger, "%.*s nearSplitCover=%f", 4 * indent, indents, range->nearSplitCover);
1031 }
1032 if (0.f < range->farSplitCover) {
1033 LOG_DEBUG(logger, "%.*s farSplitCover=%f", 4 * indent, indents, range->farSplitCover);
1034 }
1035 if (range->triangleArea.count) {
1036 LOG_DEBUG(logger, "%.*s triangleArea: count=%zu mean=%.1f min=%.1f max=%.1f",
1037 4 * indent, indents, range->triangleArea.count,
1038 range->triangleArea.mean, range->triangleArea.minValue, range->triangleArea.maxValue);
1039 if (0.f < range->pixels.distance) {
1040 LOG_DEBUG(logger, "%.*s pixelTriangleArea: distance=%.1f mean=%.1f min=%.1f max=%.1f",
1041 4 * indent, indents, range->pixels.distance,
1042 range->pixels.triangleArea.mean, range->pixels.triangleArea.minValue, range->pixels.triangleArea.maxValue);
1043
1044 }
1045 }
1046 if (range->triangleMinEdge.count) {
1047 LOG_DEBUG(logger, "%.*s triangleMinEdge: count=%zu mean=%.1f min=%.1f max=%.1f",
1048 4 * indent, indents, range->triangleMinEdge.count,
1049 range->triangleMinEdge.mean, range->triangleMinEdge.minValue, range->triangleMinEdge.maxValue);
1050 if (0.f < range->pixels.distance) {
1051 LOG_DEBUG(logger, "%.*s pixelTriangleMinEdge: distance=%.1f mean=%.1f min=%.1f max=%.1f",
1052 4 * indent, indents, range->pixels.distance,
1053 range->pixels.triangleMinEdge.mean, range->pixels.triangleMinEdge.minValue, range->pixels.triangleMinEdge.maxValue);
1054 }
1055 }
1056 if (range->triangleMaxEdge.count) {
1057 LOG_DEBUG(logger, "%.*s triangleMaxEdge: count=%zu mean=%.1f min=%.1f max=%.1f",
1058 4 * indent, indents, range->triangleMaxEdge.count,
1059 range->triangleMaxEdge.mean, range->triangleMaxEdge.minValue, range->triangleMaxEdge.maxValue);
1060 if (0.f < range->pixels.distance) {
1061 LOG_DEBUG(logger, "%.*s pixelsTriangleMaxEdge: distance=%.1f mean=%.1f min=%.1f max=%.1f",
1062 4 * indent, indents, range->pixels.distance,
1063 range->pixels.triangleMaxEdge.mean, range->pixels.triangleMaxEdge.minValue, range->pixels.triangleMaxEdge.maxValue);
1064 }
1065 }
1066 for (const Issue& issue : range->issues) {
1067 LOG_WARNING(logger, "%.*s id=%s severity=%u %s",
1068 4 * indent, indents,
1069 issueId[size_t(issue.id)], issue.severity, issue.message.c_str());
1070 }
1071 dumpRanges(range->childRanges, indent + 1);
1072 }
1073 }
1074
1075 void writeStatsAsLogger(Ctx& ctx, const char* source)
1076 {
1077 LOG_DEBUG(logger, "source: %s", source);
1078 for (size_t i = 0; i < ctx.uniquePaths.levels.size(); i++) {
1079 LOG_DEBUG(logger, "Level %zu: %zu unique asset files, %zu unique models files", i, ctx.uniquePaths.levels[i].assetPaths.size(), ctx.uniquePaths.levels[i].modelPaths.size());
1080 }
1081 LOG_DEBUG(logger, "");
1082 LOG_DEBUG(logger, "Reference FOV: %f radians, screen size: %f pixels", ctx.referenceFieldOfView, ctx.referenceScreenSize);
1083 dumpRanges(ctx.rootRanges, 0);
1084 }
1085
1086}
1087
1088
1090{
1091 LOG_ERROR(logger, "Use in post");
1092}
1093
1094void Cogs::Core::AssetStatsCommand::undo()
1095{
1096 LOG_ERROR(logger, "Use in post");
1097}
1098
1099bool Cogs::Core::AssetStatsCommand::calculateStats(Context* context,
1100 PropertyStore& properties,
1101 const StringView& source_,
1102 const StringView& destination_)
1103{
1104 Ctx ctx;
1105 ctx.context = context;
1106
1107 ctx.source = IO::absolute(source_.to_string());
1108 const std::string destination = IO::absolute(destination_.to_string());
1109
1110 ctx.referenceFieldOfView = properties.getProperty("referenceFieldOfView", ctx.referenceFieldOfView);
1111 ctx.referenceScreenSize = properties.getProperty("referenceScreenSize", ctx.referenceScreenSize);
1112 ctx.rules.splitPointScreenCoverMin = properties.getProperty("splitPointScreenCoverMin", ctx.rules.splitPointScreenCoverMin);
1113 ctx.rules.splitPointScreenCoverMax = properties.getProperty("splitPointScreenCoverMax", ctx.rules.splitPointScreenCoverMax);
1114 ctx.rules.splitPointScreenCoverFurthesSplitMax = properties.getProperty("splitPointScreenCoverFurthesSplitMax", ctx.rules.splitPointScreenCoverFurthesSplitMax);
1115 ctx.rules.splitPointMinTriangleEdgeLength = properties.getProperty("splitPointMinTriangleEdgeLength", ctx.rules.splitPointMinTriangleEdgeLength);
1116 ctx.rules.perLevelMinTriangleIncrease = properties.getProperty("perLevelMinTriangleIncrease", ctx.rules.perLevelMinTriangleIncrease);
1117
1118 const Cogs::Reflection::Type& assetCompType = Cogs::Reflection::TypeDatabase::getType<AssetComponent>();
1119 ctx.assetCompTypeId = assetCompType.getTypeId();
1120
1121 const Cogs::Reflection::Field* assetCompAssetFieldType = assetCompType.getField("asset");
1122 ctx.assetCompAssetFieldId = assetCompAssetFieldType ? assetCompType.getFieldId(assetCompAssetFieldType) : Cogs::Reflection::NoField;
1123
1124 if (!IO::isFile(ctx.source)) {
1125 LOG_ERROR(logger, "'source' (=\"%s\") must point to the root asset file.", ctx.source.c_str());
1126 return false;
1127 }
1128
1129 size_t maxActive = 2 * Threads::hardwareConcurrency();
1130
1131 enqueueAsset(&ctx, getErrorRange(ctx.rootRanges, 0, 0.f, std::numeric_limits<float>::infinity()), ctx.source, true);
1132 processAssetQueueUntilDone(&ctx, maxActive);
1133
1134 float screenSizeWorld = 2.f * std::tan(0.5f * ctx.referenceFieldOfView);
1135 analyzeRanges(ctx, ctx.rootRanges, screenSizeWorld, ctx.referenceScreenSize, 0);
1136
1137
1138 writeStatsAsLogger(ctx, ctx.source.c_str());
1139
1140 if (Cogs::IO::isDirectory(destination)) {
1141 std::string outfile = IO::fileName(ctx.source);
1142 const char* stripEndings[] = { ".asset", ".zst" };
1143 for (const char* ending : stripEndings) {
1144 if (size_t o = outfile.find(ending); o != std::string::npos) {
1145 outfile = outfile.substr(0, o);
1146 }
1147 }
1148 writeStatsAsJson(ctx, Cogs::IO::combine(destination, outfile + "_stats.json"));
1149 }
1150 else {
1151 writeStatsAsJson(ctx, destination);
1152 }
1153
1154 LOG_DEBUG(logger, "Done, failure=%s", ctx.failure ? "true" : "false");
1155 return !ctx.failure;
1156}
1157
1158
1159void Cogs::Core::AssetStatsCommand::applyPost()
1160{
1161 calculateStats(context,
1162 properties,
1163 properties.getProperty("source", StringView()),
1164 properties.getProperty("destination", StringView()));
1165}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
static constexpr TaskQueueId ResourceQueue
Resource task queue.
Definition: TaskManager.h:232
Log implementation class.
Definition: LogManager.h:139
Field definition describing a single data member of a data structure.
Definition: Field.h:68
Represents a discrete type definition, describing a native type class.
Definition: Type.h:89
FieldId getFieldId(const Field *field) const
Get the Reflection::FieldId of the given field.
Definition: Type.cpp:65
const Field * getField(const Name &name) const
Get a pointer to the field info of the field with the given name.
Definition: Type.cpp:53
constexpr TypeId getTypeId() const
Get the unique Reflection::TypeId of this instance.
Definition: Type.h:325
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
Old timer class.
Definition: Timer.h:37
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
AssetResourceType
Utility function to format a resource index as a filename.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
uint16_t TypeId
Built in type used to uniquely identify a single type instance.
Definition: Name.h:48
uint16_t FieldId
Type used to index fields.
Definition: Name.h:54
constexpr FieldId NoField
No field id.
Definition: Name.h:60
constexpr TypeId NoType
Definition of no type.
Definition: Name.h:51
@ Position
Position semantic.
void apply() override
Run the command.
Defines a value to apply to a field.
bool isIndexed() const
If the mesh uses indexed geometry.
Definition: Mesh.h:953
Model resources define a template for a set of connected entities, with resources such as meshes,...
Definition: Model.h:56
Resource handle base class handling reference counting of resources derived from ResourceBase.
struct Cogs::Core::SceneEntityDefinition::@28::@31 model
SceneEntityFlags::Model is set.
uint32_t flags
Really enum of SceneEntityFlags.
uint32_t nextSibling
Next sibling in this or next lod-level.
struct Cogs::Core::SceneEntityDefinition::@28::@32 lod
SceneEntityFlags::LodGroup is set.
uint32_t nextLodSibling
Next sibling within this lod-level.
struct Cogs::Core::SceneEntityDefinition::@28::@30 asset
SceneEntityFlags::Asset is set.
Task id struct used to identify unique Task instances.
Definition: TaskManager.h:20
@ TriangleList
List of triangles.
Definition: Common.h:116