3#include <unordered_set>
5#include "AssetPipeCommand.h"
9#include "Foundation/Platform/Atomic.h"
10#include "Foundation/Logging/Logger.h"
11#include "Foundation/Platform/IO.h"
12#include "Foundation/Platform/Timer.h"
14#include "Rendering/VertexFormat.h"
16#include "Services/TaskManager.h"
17#include "Resources/AssetManager.h"
18#include "Resources/ModelManager.h"
19#include "Resources/MaterialInstance.h"
20#include "Serialization/AssetWriter.h"
21#include "Serialization/ModelWriter.h"
23#include "Components/Core/AssetComponent.h"
26#include "rapidjson/stringbuffer.h"
27#include "rapidjson/prettywriter.h"
29#include "PackMeshCommand.h"
36 constexpr uint32_t NoValue =
static_cast<uint32_t
>(-1);
46 typedef std::vector<IdRange> IdRanges;
59 } state = State::None;
62 std::string sourcePath;
63 std::string targetPath;
64 std::vector<uint32_t> resourceDependencies;
66 uint32_t resourceIndex = ~0u;
67 bool useRelativePaths =
true;
80 } state = State::None;
83 std::vector<glm::mat4> meshTransforms;
84 std::vector<IdRanges> partIdRanges;
85 std::string sourcePath;
86 std::string targetPath;
88 uint32_t resourceIndex = ~0u;
95 Ctx(
const Ctx&) =
delete;
96 Ctx& operator=(
const Ctx&) =
delete;
99 std::string destination;
100 TaskId processGroup = NoTask;
102 Cogs::Atomic<bool> failure =
false;
106 std::queue<std::unique_ptr<ModelItem>> queue;
112 std::queue<std::unique_ptr<AssetItem>> queue;
118 std::unordered_map<std::string, uint32_t> map;
119 std::vector<bool> processed;
120 std::vector<std::vector<uint32_t>> partSizes;
121 std::vector<std::vector<IdRanges>> resourcePartIdRanges;
129 bool prettyPrint =
false;
130 bool compressAsFile =
true;
131 uint32_t trackIdRanges = 0;
133 uint32_t assetsProcessed = 0;
134 uint32_t modelsProcessed = 0;
138 void validateIdRanges(
const Ctx* ,
const IdRanges& ranges)
140 size_t n = ranges.size();
143 for (
size_t i = 0; i < n; i++) {
144 assert(ranges[i].min <= ranges[i].max);
148 for (
size_t i = 0; i + 1 < n; i++) {
149 assert(ranges[i].max + 1 < ranges[i + 1].min);
159 void restrictIdRangeCount(
const Ctx* ctx, IdRanges& ranges)
161 size_t n = ranges.size();
162 if (n <= ctx->trackIdRanges)
return;
163 assert(1 <= ctx->trackIdRanges);
165 validateIdRanges(ctx, ranges);
169 .min = ranges.front().min,
170 .max = ranges.back().max
173 std::vector<Gap> gaps;
174 for (
size_t i = 0; i + 1 < ranges.size(); i++) {
176 .size = ranges[i + 1].min - ranges[i].max,
177 .offset =
static_cast<uint32_t
>(i)
180 std::sort(gaps.begin(), gaps.end(), [](
const Gap& a,
const Gap& b) { return a.size < b.size; });
181 assert(gaps.front().size <= gaps.back().size);
186 size_t toRemove = n - ctx->trackIdRanges;
187 for (
size_t i = 0; i < toRemove; i++) {
188 const Gap& gap = gaps[i];
189 ranges[gap.offset + 1].min = ranges[gap.offset].min;
190 ranges[gap.offset].max = ranges[gap.offset + 1].max;
192 assert(ranges.front().min == overall.min);
193 assert(ranges.back().max == overall.max);
199 for (
size_t i = 1; i < n; i++) {
200 if (ranges[d].max < ranges[i].min) {
202 ranges[d] = ranges[i];
205 ranges[d].max = ranges[i].max;
208 ranges.resize(d + 1);
211 assert(ranges.size() == ctx->trackIdRanges);
213 validateIdRanges(ctx, ranges);
216 assert(ranges.front().min == overall.min);
217 assert(ranges.back().max == overall.max);
220 IdRanges mergeIdRanges(
const Ctx* ctx,
const IdRanges& a,
const IdRanges& b)
223 validateIdRanges(ctx, a);
224 validateIdRanges(ctx, b);
229 else if (b.empty()) {
232 assert(!a.empty() && !b.empty());
236 .min = std::min(a.front().min, b.front().min),
237 .max = std::max(a.back().max, b.back().max)
240 size_t na = a.size();
241 size_t nb = b.size();
250 bool any_a = ia < na;
251 bool any_b = ib < nb;
255 if (any_a && ((any_b && (a[ia].min <= b[ib].min)) || !any_b)) {
262 assert(!any_a && !any_b);
267 if (d.empty() || d.back().max + 1 < t.min) {
271 d.back().max = std::max(d.back().max, t.max);
276 validateIdRanges(ctx, d);
279 assert(d.front().min == overall.min);
280 assert(d.back().max == overall.max);
285 void findIdRanges(Ctx* , IdRanges& idRanges, std::vector<uint32_t>& ids)
288 std::sort(ids.begin(), ids.end());
292 idRanges.push_back(IdRange{ .min = ids.front(), .max = ids.front() });
293 for (uint32_t
id : ids) {
296 if (
id <= idRanges.back().max + 1) {
297 idRanges.back().max = id;
301 assert(idRanges.back().max + 1 <
id);
302 idRanges.push_back(IdRange{ .min = id, .max =
id });
308 void markResourceAsDone(Ctx* ctx, std::vector<uint32_t>&& partSizes, std::vector<IdRanges>&& partIdRanges, uint32_t resourceIndex)
310 if (resourceIndex == ::NoValue)
return;
312 Cogs::LockGuard guard(ctx->resources.lock);
313 assert(ctx->resources.processed[resourceIndex] ==
false);
314 assert(ctx->resources.map.size() == ctx->resources.processed.size());
315 assert(ctx->resources.map.size() == ctx->resources.partSizes.size());
316 assert(ctx->resources.map.size() == ctx->resources.resourcePartIdRanges.size());
317 ctx->resources.processed[resourceIndex] =
true;
318 ctx->resources.partSizes[resourceIndex] = std::move(partSizes);
319 ctx->resources .resourcePartIdRanges[resourceIndex] = std::move(partIdRanges);
322 [[nodiscard]]
bool enqueueModel(Ctx* ctx,
const std::string& sourcePath,
const std::string& targetPath, uint32_t resourceIndex)
324 if (ctx->failure)
return false;
326 std::unique_ptr<ModelItem> modelItem = std::make_unique<ModelItem>(ModelItem{
327 .state = ModelItem::State::Issued,
328 .sourcePath = sourcePath,
329 .targetPath = targetPath,
330 .resourceIndex = resourceIndex
333 Cogs::LockGuard guard(ctx->modelQueue.lock);
334 ctx->modelQueue.queue.push(std::move(modelItem));
335 ctx->modelQueue.count++;
339 [[nodiscard]]
bool enqueueAsset(Ctx* ctx,
const std::string& sourcePath,
const std::string& targetPath, uint32_t resourceIndex,
bool useRelativePaths)
341 if (ctx->failure)
return false;
343 std::unique_ptr<AssetItem> assetItem = std::make_unique<AssetItem>(AssetItem{
344 .state = AssetItem::State::Issued,
345 .sourcePath = sourcePath,
346 .targetPath = targetPath,
347 .resourceIndex = resourceIndex,
348 .useRelativePaths = useRelativePaths
353 Cogs::LockGuard guard(ctx->assetQueue.lock);
354 ctx->assetQueue.queue.push(std::move(assetItem));
355 ctx->assetQueue.count++;
359 [[nodiscard]]
bool packModel(Ctx* ctx, ModelItem* item)
366 Model* model = item->handle.resolve();
367 const size_t initialMeshCount = model->meshes.size();
369 std::vector<MeshHandle> newMeshes;
370 newMeshes.reserve(initialMeshCount);
372 std::vector<uint32_t> newMeshIndices(initialMeshCount, NoIndex);
374 std::vector<IdRanges> meshIdRanges(initialMeshCount);
376 for (
size_t i = 0; i < initialMeshCount; i++) {
378 MeshHandle mesh = std::move(model->meshes[i]);
380 if (ctx->trackIdRanges) {
381 std::vector<uint32_t> ids;
382 if (mesh->
hasStream(VertexDataType::TexCoords0)) {
386 for (
size_t k = 0; k < dataStream.
numElements; k++) {
387 uint32_t
id = uint32_t(tex[k].x);
388 if (ids.empty() || ids.back() !=
id) {
393 findIdRanges(ctx, idRanges, ids);
394 restrictIdRangeCount(ctx, idRanges);
395 meshIdRanges[i] = std::move(idRanges);
399 glm::mat4 transform(1.f);
401 if (!PackMeshCommand::pack(ctx->context, message, transform, mesh, ctx->packMeshOptions)) {
402 LOG_ERROR(logger,
"PackMeshCommand failed: %s", message.c_str());
405 else if (!message.empty()) {
411 newMeshIndices[i] =
static_cast<uint32_t
>(newMeshes.size());
412 newMeshes.emplace_back(std::move(mesh));
413 item->meshTransforms.emplace_back(transform);
416 model->meshes = std::move(newMeshes);
419 size_t partCount = model->parts.size();
420 item->partIdRanges.resize(partCount);
421 for (
size_t i = 0; i < partCount; i++) {
423 if (part.meshIndex != NoIndex) {
424 assert(part.meshIndex < newMeshIndices.size());
425 item->partIdRanges[i] = std::move(meshIdRanges[part.meshIndex]);
426 part.meshIndex = newMeshIndices[part.meshIndex];
430 assert(item->meshTransforms.size() == model->meshes.size());
435 [[nodiscard]]
bool processModel(Ctx* ctx, ModelItem* item)
437 Model* model = item->handle.resolve();
438 const size_t partCount = model->parts.size();
441 const std::vector<glm::mat4>& meshTransforms = item->meshTransforms;
443 std::vector<uint32_t> partSizes(partCount);
444 for (
size_t i = 0; i < partCount; i++) {
448 if (part.meshIndex != NoIndex) {
449 assert(part.meshIndex < model->meshes.size());
451 const glm::mat4& tt = meshTransforms[part.meshIndex];
452 if (part.transformIndex != NoIndex) {
453 std::span<float> values = model->properties.getFloatArray(part.transformIndex);
454 assert(values.size() == 16);
456 glm::mat4 transform = glm::make_mat4(values.data()) * tt;
457 std::memcpy(values.data(), glm::value_ptr(transform), 16 *
sizeof(
float));
460 part.transformIndex = model->properties.addProperty(
"t", std::span(glm::value_ptr(tt), 16));
463 const Mesh* mesh = model->meshes[part.meshIndex].resolve();
465 size_t vertexByteSize = 0;
467 for (
size_t k = 0; k < layout.
numStreams; k++) {
472 uint32_t minIndex = ~0u;
473 uint32_t maxIndex = 0u;
475 if (indexStream.
stride ==
sizeof(uint16_t)) {
476 const uint16_t* indices =
reinterpret_cast<const uint16_t*
>(indexStream.data());
477 for (
size_t k = 0; k < indexStream.
numElements; k++) {
478 minIndex = std::min(minIndex, uint32_t(indices[k]));
479 maxIndex = std::max(maxIndex, uint32_t(indices[k]));
482 else if (indexStream.
stride ==
sizeof(uint32_t)) {
483 const uint32_t* indices =
reinterpret_cast<const uint32_t*
>(indexStream.data());
484 for (
size_t k = 0; k < indexStream.
numElements; k++) {
485 minIndex = std::min(minIndex, indices[k]);
486 maxIndex = std::max(maxIndex, indices[k]);
490 if (minIndex < maxIndex) {
491 partSizes[i] = uint32_t(vertexByteSize * (maxIndex - minIndex) + indexStream.
stride * indexStream.
numElements);
495 uint32_t startIndex = std::min(part.startIndex, mesh->
getCount());
496 uint32_t vertexCount = std::min(part.vertexCount, mesh->
getCount() - startIndex);
497 partSizes[i] = uint32_t(vertexByteSize * vertexCount);
505 if (!Cogs::IO::copyFile(item->sourcePath, item->targetPath))
return false;
507 uint32_t numVerticess = 0;
508 uint32_t numIndices = 0;
510 .flags = (ctx->compressAsFile ? WriteModelFlags::COMPRESS_ZSTD_AS_FILE : WriteModelFlags::COMPRESS_ZSTD) | WriteModelFlags::COMPRESS_MAX
512 if (!writeModel(ctx->context, numVerticess, numIndices, item->targetPath, model, &settings))
return false;
516 markResourceAsDone(ctx, std::move(partSizes), std::move(item->partIdRanges), item->resourceIndex);
521 [[nodiscard]]
bool getSourcePath(std::string& sourcePath,
const std::string& sourceDirectoryPath,
const AssetDefinition& definition, uint32_t sourcePropertyIndex,
AssetResourceType resourceKind,
bool relativePath)
523 if (sourcePropertyIndex == ::NoValue) {
524 LOG_ERROR(logger,
"Source property is neither uint nor string");
528 const PropertyInfo& pathItem = definition.scene.properties.getPropertyByIndex(sourcePropertyIndex);
529 if (pathItem.type == PropertyType::UnsignedInteger) {
530 sourcePath = createAssetResourcePathFromIndex(pathItem.uintValue, resourceKind);
532 else if (pathItem.type == PropertyType::String) {
533 sourcePath = definition.scene.properties.getString(pathItem).
to_string();
536 LOG_ERROR(logger,
"Source property is neither uint nor string");
540 if (relativePath && Cogs::IO::isRelative(sourcePath)) {
541 sourcePath = Cogs::IO::combine(sourceDirectoryPath, sourcePath);
547 auto checkResourceIndex(Ctx* ctx,
const std::string& sourcePath)
549 uint32_t resourceIndex = ~0u;
550 bool isProcessed =
false;
551 bool doEnqueue =
false;
553 Cogs::LockGuard guard(ctx->resources.lock);
554 if (
auto it = ctx->resources.map.find(sourcePath); it != ctx->resources.map.end()) {
555 resourceIndex = it->second;
556 isProcessed = ctx->resources.processed[resourceIndex];
559 resourceIndex = uint32_t(ctx->resources.map.size());
560 ctx->resources.map[sourcePath] = resourceIndex;
561 ctx->resources.processed.emplace_back(
false);
562 ctx->resources.partSizes.emplace_back();
563 ctx->resources.resourcePartIdRanges.emplace_back();
564 assert(ctx->resources.map.size() == ctx->resources.processed.size());
565 assert(ctx->resources.map.size() == ctx->resources.partSizes.size());
566 assert(ctx->resources.map.size() == ctx->resources.resourcePartIdRanges.size());
569 return std::make_tuple(resourceIndex, isProcessed, doEnqueue);
573 [[nodiscard]]
bool issueAssetDependencies(Ctx* ctx, AssetItem* item)
575 assert(
HandleIsValid(item->handle) && item->handle->isLoaded());
576 Asset* asset = item->handle.resolve();
586 std::unordered_set<uint32_t> depSet;
588 std::string sourceDirectoryPath = Cogs::IO::parentPath(item->sourcePath);
589 for (
size_t i = 0; i < definition.scene.entities.size(); ++i) {
592 if (entityDef.isModel()) {
594 std::string sourcePath;
595 if (!getSourcePath(sourcePath, sourceDirectoryPath, definition, entityDef.
model.index, AssetResourceType::Model, item->useRelativePaths))
return false;
597 auto [resourceIndex, isProcessed, doEnqueue] = checkResourceIndex(ctx, sourcePath);
599 PropertyInfo& pathItem = definition.scene.properties.getPropertyByIndex(entityDef.
model.index);
600 pathItem.type = PropertyType::UnsignedInteger;
601 pathItem.uintValue = resourceIndex;
604 if(!depSet.contains(resourceIndex)) {
605 depSet.insert(resourceIndex);
608 item->resourceDependencies.push_back(resourceIndex);
611 std::string targetPath = Cogs::IO::combine(ctx->destination, createAssetResourcePathFromIndex(resourceIndex, AssetResourceType::Model));
613 if (doEnqueue && !enqueueModel(ctx, sourcePath, targetPath, resourceIndex))
return false;
617 else if (entityDef.isAsset()) {
619 std::string sourcePath;
620 if (!getSourcePath(sourcePath, sourceDirectoryPath, definition, entityDef.
model.index, AssetResourceType::Asset, item->useRelativePaths))
return false;
622 auto [resourceIndex, isProcessed, doEnqueue] = checkResourceIndex(ctx, sourcePath);
624 PropertyInfo& pathItem = definition.scene.properties.getPropertyByIndex(entityDef.
model.index);
625 pathItem.type = PropertyType::UnsignedInteger;
626 pathItem.uintValue = resourceIndex;
628 if (!depSet.contains(resourceIndex)) {
629 depSet.insert(resourceIndex);
632 item->resourceDependencies.push_back(resourceIndex);
635 std::string targetPath = Cogs::IO::combine(ctx->destination, createAssetResourcePathFromIndex(resourceIndex, AssetResourceType::Asset));
637 bool useRelative = entityDef.
asset.
flags & uint32_t(AssetFlags::RelativePaths);
638 entityDef.
asset.
flags |= uint32_t(AssetFlags::RelativePaths);
640 if (doEnqueue && !enqueueAsset(ctx, sourcePath, targetPath, resourceIndex, useRelative))
return false;
645 if (entityDef.numFields) {
646 for (
FieldValue& entry : std::span(definition.scene.fieldValues).subspan(entityDef.firstField, entityDef.numFields)) {
648 if (entry.componentId == assetCompTypeId && entry.fieldId == assetCompAssetFieldId && entry.type == DefaultValueType::Asset) {
650 std::string sourcePath = entry.value;
651 if(item->useRelativePaths && Cogs::IO::isRelative(sourcePath)) {
652 sourcePath = Cogs::IO::combine(sourceDirectoryPath, sourcePath);
654 if (!Cogs::IO::exists(sourcePath)) {
655 LOG_ERROR(logger,
"Path %s does not exist", sourcePath.c_str());
659 auto [resourceIndex, isProcessed, doEnqueue] = checkResourceIndex(ctx, sourcePath);
660 entry.value = Cogs::IO::combine(ctx->destination, createAssetResourcePathFromIndex(resourceIndex, AssetResourceType::Asset));;
662 if (!depSet.contains(resourceIndex)) {
663 depSet.insert(resourceIndex);
666 item->resourceDependencies.push_back(resourceIndex);
669 if (doEnqueue && !enqueueAsset(ctx, sourcePath, entry.value, resourceIndex, item->useRelativePaths))
return false;
682 [[nodiscard]]
bool processAsset(Ctx* ctx, AssetItem* item)
685 Asset* asset = item->handle.resolve();
691 std::vector<IdRanges> assetIdRanges(1);
692 std::vector<IdRanges> entityIdRanges(definition.scene.entities.size());
693 if(ctx->trackIdRanges) {
694 for (
size_t i = 0; i < definition.scene.entities.size(); i++) {
696 IdRanges itemIdRanges;
699 if (entityDef.isModel()) {
700 const PropertyInfo& pathItem = definition.scene.properties.getPropertyByIndex(entityDef.
model.index);
701 assert(pathItem.type == PropertyType::UnsignedInteger);
704 if (entityDef.
model.part != NoValue) {
705 part = entityDef.
model.part;
709 if (ctx->trackIdRanges) {
710 Cogs::LockGuard guard(ctx->resources.lock);
711 assert(pathItem.uintValue < ctx->resources.resourcePartIdRanges.size());
712 assert(part < ctx->resources.resourcePartIdRanges[pathItem.uintValue].size());
713 itemIdRanges = ctx->resources.resourcePartIdRanges[pathItem.uintValue][part];
714 assert(!itemIdRanges.empty());
719 assetIdRanges[0] = mergeIdRanges(ctx, assetIdRanges[0], itemIdRanges);
720 restrictIdRangeCount(ctx, assetIdRanges[0]);
724 if (!itemIdRanges.empty()) {
725 uint32_t parentIndex =
static_cast<uint32_t
>(i);
727 entityIdRanges[parentIndex] = mergeIdRanges(ctx, entityIdRanges[parentIndex], itemIdRanges);
728 restrictIdRangeCount(ctx, entityIdRanges[parentIndex]);
729 parentIndex = definition.scene.entities[parentIndex].parentIndex;
730 }
while (parentIndex != SceneEntityDefinition::NoIndex);
735 for (
size_t i = 0; i < definition.scene.entities.size(); ++i) {
737 uint32_t firstProperty = newProperties.size();
740 entityDef.nameIndex = ::NoValue;
742 const PropertyRange propertyRange = definition.scene.getProperties(entityDef);
744 if (entityDef.isModel()) {
745 assert(entityDef.
model.index != ::NoValue);
748 const PropertyInfo& pathItem = definition.scene.properties.getPropertyByIndex(entityDef.
model.index);
749 assert(pathItem.type == PropertyType::UnsignedInteger);
751 entityDef.
model.index = newProperties.addProperty(definition.scene.properties, entityDef.
model.index);
753 IdRanges itemIdRanges;
754 std::vector<uint32_t> partSizes;
756 Cogs::LockGuard guard(ctx->resources.lock);
757 assert(pathItem.uintValue < ctx->resources.partSizes.size());
758 assert(ctx->resources.processed[pathItem.uintValue]);
759 partSizes = ctx->resources.partSizes[pathItem.uintValue];
762 if (entityDef.
model.part != NoValue) {
763 if (partSizes.size() <= entityDef.
model.part) {
764 LOG_ERROR(logger,
"Illegal part index %u, model has %zu parts", entityDef.
model.part, partSizes.size());
771 newProperties.addProperty(definition.scene.properties, ix);
775 else if (entityDef.isAsset()) {
777 entityDef.
asset.index = newProperties.addProperty(definition.scene.properties, entityDef.
asset.index);
781 else if (entityDef.isLodGroup()) {
783 if (ctx->trackIdRanges && !entityIdRanges[i].empty()) {
784 size_t n = entityIdRanges[i].size();
785 std::vector<uint32_t> ids(2 * n);
786 for (
size_t k = 0; k < n; k++) {
787 ids[2 * k + 0] = entityIdRanges[i][k].min;
788 ids[2 * k + 1] = entityIdRanges[i][k].max;
790 newProperties.addProperty(
"ids", ids);
792 newProperties.addProperty(definition.scene.properties, propertyRange.getPropertyIndex(bboxString));
793 newProperties.addProperty(definition.scene.properties, propertyRange.getPropertyIndex(errorsString));
797 entityDef.firstProperty = firstProperty;
798 entityDef.numProperties = newProperties.size()-firstProperty;
802 definition.scene.properties = std::move(newProperties);
804 if (!writeAsset(ctx->context,
805 item->targetPath.c_str(),
807 ctx->prettyPrint ? AssetWriteFlags::PrettyPrint : (AssetWriteFlags::Compress | AssetWriteFlags::Strip)))
809 LOG_ERROR(logger,
"Failed to write %s -> %s", item->sourcePath.c_str(), item->targetPath.c_str());
813 std::vector<uint32_t> partSizes(1);
814 markResourceAsDone(ctx, std::move(partSizes), std::move(assetIdRanges), item->resourceIndex);
819 void manageActiveModels(Ctx* ctx,
820 std::vector<std::unique_ptr<ModelItem>>& loadingModels,
821 std::vector<std::unique_ptr<ModelItem>>& loadingModelsNext)
823 for (std::unique_ptr<ModelItem>& item : loadingModels) {
826 switch (item->state) {
828 case ModelItem::State::Loading:
829 if (item->handle->isLoaded()) {
833 const Model* model = item->handle.resolve();
835 for (
auto& mesh : model->meshes) {
836 if (!mesh->isLoaded()) {
838 if (mesh->hasFailedLoad()) {
845 for (
auto& material : model->materials) {
846 if (!material->isLoaded()) {
848 if (material->hasFailedLoad()) {
856 item->task = ctx->context->taskManager->enqueueChild(ctx->group,
857 [ctx, item_=item.get()]()
859 if (!packModel(ctx, item_)) ctx->failure = true;
861 if (item->task.isValid()) {
862 item->state = ModelItem::State::Packing;
870 else if (item->handle->hasFailedLoad()) {
876 case ModelItem::State::Packing:
877 if (!ctx->context->taskManager->isActive(item->task)) {
880 const Model* model = item->handle.resolve();
881 for (
auto& mesh : model->meshes) {
882 if (!mesh->isLoaded()) {
884 if (mesh->hasFailedLoad()) {
892 ctx->context->taskManager->wait(item->task);
894 item->task = ctx->context->taskManager->enqueueChild(ctx->group,
895 [ctx, item_ = item.get()]()
897 if (!processModel(ctx, item_)) ctx->failure = true;
899 if (item->task.isValid()) {
900 item->state = ModelItem::State::Writing;
911 case ModelItem::State::Writing:
912 if (!ctx->context->taskManager->isActive(item->task)) {
913 ctx->context->taskManager->wait(item->task);
915 item->state = ModelItem::State::Done;
921 assert(
false &&
"Unexpected state");
926 if (keep) loadingModelsNext.emplace_back(std::move(item));
927 else ctx->modelsProcessed++;
931 void addNewActiveModels(Ctx* ctx,
932 std::vector<std::unique_ptr<ModelItem>>& loadingModelsNext,
935 while (loadingModelsNext.size() < maxCount) {
936 std::unique_ptr<ModelItem> item;
938 Cogs::LockGuard guard(ctx->modelQueue.lock);
939 if (ctx->modelQueue.queue.empty())
break;
940 item = std::move(ctx->modelQueue.queue.front());
941 ctx->modelQueue.queue.pop();
942 ctx->modelQueue.count--;
945 item->state = ModelItem::State::Loading;
946 item->handle = ctx->context->modelManager->loadModel(item->sourcePath, NoResourceId, ModelLoadFlags::None);
948 if (!Cogs::IO::exists(item->targetPath)) {
949 Cogs::IO::createDirectories(item->targetPath);
951 loadingModelsNext.emplace_back(std::move(item));
955 void manageActiveAssets(Ctx* ctx,
956 std::vector<std::unique_ptr<AssetItem>>& loadingAssets,
957 std::vector<std::unique_ptr<AssetItem>>& loadingAssetsNext)
959 for (std::unique_ptr<AssetItem>& item : loadingAssets) {
962 switch (item->state) {
964 case AssetItem::State::Loading:
965 if (item->handle->isLoaded()) {
966 item->task = ctx->context->taskManager->enqueueChild(ctx->group,
967 [ctx, item_=item.get()]()
969 if (!issueAssetDependencies(ctx, item_)) ctx->failure = true;
971 if (item->task.isValid()) {
972 item->state = AssetItem::State::IssueDependencies;
979 else if (item->handle->hasFailedLoad()) {
985 case AssetItem::State::IssueDependencies:
986 if (!ctx->context->taskManager->isActive(item->task)) {
987 ctx->context->taskManager->wait(item->task);
989 item->state = AssetItem::State::WaitingForDeps;
993 case AssetItem::State::WaitingForDeps:
995 if (!item->resourceDependencies.empty()) {
997 bool depchange =
false;
998 Cogs::LockGuard guard(ctx->resources.lock);
999 for (
size_t i = 0; i < item->resourceDependencies.size();) {
1000 if (ctx->resources.processed[item->resourceDependencies[i]]) {
1001 std::swap(item->resourceDependencies[i], item->resourceDependencies.back());
1002 item->resourceDependencies.pop_back();
1015 if (item->resourceDependencies.empty()) {
1016 item->task = ctx->context->taskManager->enqueueChild(ctx->group,
1017 [ctx, item_=item.get()]()
1019 if (!processAsset(ctx, item_)) ctx->failure = true;
1021 if (item->task.isValid()) {
1022 item->state = AssetItem::State::Processing;
1025 ctx->failure =
true;
1031 case AssetItem::State::Processing:
1032 if (!ctx->context->taskManager->isActive(item->task)) {
1033 item->state = AssetItem::State::Done;
1039 assert(
false &&
"Unexpected state");
1044 if (keep) loadingAssetsNext.emplace_back(std::move(item));
1045 else ctx->assetsProcessed++;
1049 void addNewActiveAssets(Ctx* ctx, std::vector<std::unique_ptr<AssetItem>>& loadingAssetsNext)
1053 std::unique_ptr<AssetItem> item;
1055 Cogs::LockGuard guard(ctx->assetQueue.lock);
1056 if (ctx->assetQueue.queue.empty())
break;
1057 item = std::move(ctx->assetQueue.queue.front());
1058 ctx->assetQueue.queue.pop();
1059 ctx->assetQueue.count--;
1062 item->state = AssetItem::State::Loading;
1063 item->handle = ctx->context->assetManager->loadAsset(item->sourcePath, NoResourceId, AssetLoadFlags::ForceUnique);
1064 if (!Cogs::IO::exists(item->targetPath)) {
1065 Cogs::IO::createDirectories(item->targetPath);
1067 loadingAssetsNext.emplace_back(std::move(item));
1076 LOG_ERROR(logger,
"Use in post");
1079void Cogs::Core::AssetPipeCommand::undo()
1081 LOG_ERROR(logger,
"Use in post");
1084void Cogs::Core::AssetPipeCommand::applyPost()
1087 ctx.context = context;
1089 ctx.destination = IO::absolute(properties.getProperty(
"destination",
StringView()).to_string());
1090 ctx.packMeshOptions.allowIdOffset = properties.getProperty(
"allowIdOffset", ctx.packMeshOptions.allowIdOffset);
1091 ctx.packMeshOptions.allowSeparateIdStream = properties.getProperty(
"allowSeparateIdStream", ctx.packMeshOptions.allowSeparateIdStream);
1092 ctx.packMeshOptions.optimizeTriangleOrder = properties.getProperty(
"optimizeTriangleOrder", ctx.packMeshOptions.optimizeTriangleOrder);
1093 ctx.packMeshOptions.optimizeVertexOrder = properties.getProperty(
"optimizeVertexOrder", ctx.packMeshOptions.optimizeVertexOrder);
1094 ctx.prettyPrint = properties.getProperty(
"prettyPrint", ctx.prettyPrint);
1095 ctx.compressAsFile = properties.getProperty(
"compressAsFile", ctx.compressAsFile);
1096 ctx.trackIdRanges = properties.getProperty(
"trackIdRanges", ctx.trackIdRanges);
1098 LOG_DEBUG(logger,
"destination=%s", ctx.destination.c_str());
1099 LOG_DEBUG(logger,
"packMeshOptions: allowIdOffset=%d allowSeparateIdStream=%d optimizeTriangleOrder=%d optimizeVertexOrder=%d",
1100 ctx.packMeshOptions.allowIdOffset ? 1 : 0,
1101 ctx.packMeshOptions.allowSeparateIdStream ? 1 : 0,
1102 ctx.packMeshOptions.optimizeTriangleOrder ? 1 : 0,
1103 ctx.packMeshOptions.optimizeVertexOrder ? 1 : 0);
1104 LOG_DEBUG(logger,
"prettyPrint=%d compressAsFile=%d trackIdRanges=%u", ctx.prettyPrint ? 1 : 0, ctx.compressAsFile ? 1 : 0, ctx.trackIdRanges);
1106 if (
const PropertyInfo propInfo = properties.getProperty(
"target"); propInfo.type != PropertyType::Unknown) {
1108 const StringView target = properties.getString(propInfo);
1114 LOG_DEBUG(logger,
"PackMesh target WebGL1_Low");
1120 LOG_DEBUG(logger,
"PackMesh target WebGL2_Low");
1127 LOG_DEBUG(logger,
"PackMesh target WebGL2_Med");
1131 LOG_ERROR(logger,
"Unrecognized target value '%.*s'", StringViewFormat(target));
1137 const std::string source = IO::absolute(properties.getProperty(
"source",
StringView()).to_string());
1138 if (!IO::isFile(source)) {
1139 LOG_ERROR(logger,
"'source' (=\"%s\") must point to the root asset file.", source.c_str());
1144 std::string outfile = IO::fileName(source);
1145 const char* stripEndings[] = {
".asset",
".zst" };
1146 for (
const char* ending : stripEndings) {
1147 if (
size_t o = outfile.find(ending); o != std::string::npos) {
1148 outfile = outfile.substr(0, o);
1153 (void)enqueueAsset(&ctx, source, IO::combine(ctx.destination, outfile +
".asset"), ~0u,
true);
1155 size_t maxConcurrentModelLoads = (2 * Cogs::Thread::hardware_concurrency() + 1);
1158 double lastReport = -1000.0;
1159 Timer timer = Timer::startNew();
1160 std::vector<std::unique_ptr<ModelItem>> loadingModels, loadingModelsNext;
1161 std::vector<std::unique_ptr<AssetItem>> loadingAssets, loadingAssetsNext;
1163 runFrames(context, 1,
true,
false);
1165 loadingModelsNext.clear();
1166 manageActiveModels(&ctx, loadingModels, loadingModelsNext);
1167 addNewActiveModels(&ctx, loadingModelsNext, maxConcurrentModelLoads);
1168 loadingModels.swap(loadingModelsNext);
1170 loadingAssetsNext.clear();
1171 manageActiveAssets(&ctx, loadingAssets, loadingAssetsNext);
1172 addNewActiveAssets(&ctx, loadingAssetsNext);
1173 loadingAssets.swap(loadingAssetsNext);
1175 done = loadingModels.empty() && loadingAssets.empty();
1176 double elapsed = timer.elapsedSeconds();
1177 if (done || std::floor(lastReport) != std::floor(elapsed)) {
1178 lastReport = elapsed;
1180 size_t assetQueueCount = 0;
1182 Cogs::LockGuard guard(ctx.assetQueue.lock);
1183 assetQueueCount = ctx.assetQueue.count;
1185 size_t modelQueueCount = 0;
1187 Cogs::LockGuard guard(ctx.modelQueue.lock);
1188 modelQueueCount = ctx.modelQueue.count;
1191 LOG_DEBUG(logger,
"%.0fs: assets: done=%u act=%zu queue=%zu, models: done=%u act=%zu queue=%zu",
1192 std::floor(elapsed),
1193 ctx.assetsProcessed, loadingAssets.size(), assetQueueCount,
1194 ctx.modelsProcessed, loadingModels.size(), modelQueueCount);
1199 context->taskManager->wait(ctx.group);
1200 LOG_DEBUG(logger,
"Done, failure=%s", ctx.failure ?
"true" :
"false");
A Context instance contains all the services, systems and runtime components needed to use Cogs.
static constexpr TaskQueueId ResourceQueue
Resource task queue.
Log implementation class.
Field definition describing a single data member of a data structure.
Represents a discrete type definition, describing a native type class.
FieldId getFieldId(const Field *field) const
Get the Reflection::FieldId of the given field.
const Field * getField(const Name &name) const
Get a pointer to the field info of the field with the given name.
constexpr TypeId getTypeId() const
Get the unique Reflection::TypeId of this instance.
Provides a weakly referenced view over the contents of a string.
size_t hashLowercase(size_t hashValue=Cogs::hash()) const noexcept
Get the hash code of the string converted to lowercase.
std::string to_string() const
String conversion method.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
AssetResourceType
Utility function to format a resource index as a filename.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
uint16_t TypeId
Built in type used to uniquely identify a single type instance.
uint16_t FieldId
Type used to index fields.
constexpr FieldId NoField
No field id.
Contains all Cogs related functionality.
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
void apply() override
Run the command.
Contains a stream of data used by Mesh resources.
uint32_t numElements
Number of elements of the type given by format contained in data.
uint32_t stride
Element stride.
Defines a value to apply to a field.
Wrapper for read-only access to mapped stream data.
VertexFormatHandle vertexFormats[maxStreams]
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
MappedStreamReadOnly< Element > mapReadOnly(const VertexDataType::EVertexDataType type, VertexFormatHandle format, const size_t start, const size_t count)
Maps the data stream corresponding to the given type for read-only access.
bool isIndexed() const
If the mesh uses indexed geometry.
bool hasStream(VertexDataType::EVertexDataType type) const
Check if the Mesh has a DataStream for the given type.
DataStream & getStream(const VertexDataType::EVertexDataType dataType)
Get the stream corresponding to the given dataType.
uint32_t getCount() const
Get the vertex count of the mesh.
Model resources define a template for a set of connected entities, with resources such as meshes,...
static constexpr uint32_t NoProperty
Return from findProperty if key not found.
struct Cogs::Core::SceneEntityDefinition::@28::@31 model
SceneEntityFlags::Model is set.
uint32_t flags
Really enum of SceneEntityFlags.
struct Cogs::Core::SceneEntityDefinition::@28::@30 asset
SceneEntityFlags::Asset is set.
Task id struct used to identify unique Task instances.