Cogs.Core
CogsBin3Loader.cpp
1#include "Context.h"
2#include "ModelLoader.h"
3#include "CogsBin3.h"
4
5#include "Resources/Buffer.h"
6#include "Resources/ResourceStore.h"
7#include "Resources/ModelManager.h"
8#include "Resources/MeshManager.h"
9#include "Resources/MaterialManager.h"
10#include "Resources/TextureManager.h"
11#include "Resources/AnimationManager.h"
12#include "Resources/BufferManager.h"
13
14#include "Platform/ResourceBufferBackedFileContents.h"
15
16#include "Foundation/Logging/Logger.h"
17#include "Foundation/Geometry/BoundingBox.hpp"
18#include "Foundation/Platform/FileHandle.h"
19#include "Foundation/Platform/IO.h"
20#include "Foundation/Platform/MMapBackedFileContents.h"
21#include "Foundation/Platform/Threads.h"
22#include "Foundation/Platform/Timer.h"
23
24#include "zstd.h"
25#include <span>
26#include <type_traits>
27
28// Use of sub-tasks for loading will be disabled when more than the
29// following number of models are currently in flight...
30// Increasing this number could lead to a stack overflow if many
31// models are being loaded concurrently.
32#define COGS_MODELLOADER_MAX_TASKS 20
33
34//#pragma optimize( "", off )
35
36#ifndef EMSCRIPTEN
37// Toggle using memory mapped IO when reading file.
38#define COGS_USE_MMAP
39#endif
40
41// NOTE (chrisdy): I've tried to use asserts on errors resulting from bugs/illegal usage from cogs code,
42// But illegal data in file should only produce errors and bail out.
43
44namespace
45{
46 using namespace Cogs::Core;
47 Cogs::Logging::Log logger = Cogs::Logging::getLogger("CogsBin3Loader");
48 std::atomic<uint32_t> loadCount(0);
49
50 template<typename T>
51 struct TypedView
52 {
53 const T* base = nullptr;
54 size_t count = 0;
55
56 const T& operator[](size_t i) const { assert(i < count); return base[i]; }
57 };
58
59 template<typename T>
60 constexpr bool isValidIndex(T v)
61 {
62 static_assert(std::is_integral_v<T>);
63 return v != T(-1);
64 }
65
66 struct ReadContext
67 {
68 Context* context = nullptr;
69 std::unique_ptr<Cogs::FileContents> contents;
70 Cogs::Core::TaskId group = NoTask;
71 std::string path;
72 bool success = false;
73
74 CogsBin3::Header* header = nullptr;
75 std::span<CogsBin3::SectionInfo> sectionInfo;
76 std::vector<const uint8_t*> sectionPtr;
77 std::unique_ptr<Cogs::Mutex[]> sectionLocks;
78 Cogs::Mutex buffersMutex;
79
80 TypedView<CogsBin3::Buffer> buffers;
81 TypedView<CogsBin3::Texture> textures;
82 TypedView<CogsBin3::String> strings;
83 TypedView<char> stringData;
84 TypedView<CogsBin3::Node> nodes;
85 TypedView<CogsBin3::Transform> transforms;
86 TypedView<Cogs::Geometry::BoundingBox> boundingBoxes;
87 TypedView<CogsBin3::MaterialInstance> materialInstances;
88 TypedView<CogsBin3::Property> props;
89 TypedView<uint8_t> propData;
90 TypedView<CogsBin3::Mesh> meshes;
91 TypedView<CogsBin3::VertexStream> vertexStreams;
92 TypedView<CogsBin3::VertexAttribute> vertexAttributes;
93 TypedView<CogsBin3::Bone> bones;
94 TypedView<CogsBin3::Skeleton> skeletons;
95 TypedView<CogsBin3::AnimTrack> animTracks;
96 TypedView<CogsBin3::AnimClip> animClips;
97 TypedView<CogsBin3::Model> models;
98
99 std::vector<ResourceBufferHandle> bufferCache;
100 std::vector<TextureHandle> textureCache;
101
102 std::list<std::unique_ptr<uint8_t[]>> allocations; // we should have an arena here.
103 };
104
105 Cogs::PrimitiveType::EPrimitiveType translatePrimitiveType(ReadContext& rctx, CogsBin3::PrimitiveType primitiveType)
106 {
107 switch (primitiveType) {
108 case CogsBin3::PrimitiveType::TriangleList: return Cogs::PrimitiveType::TriangleList; break;
109 case CogsBin3::PrimitiveType::TriangleStrip: return Cogs::PrimitiveType::TriangleStrip; break;
110 case CogsBin3::PrimitiveType::LineList: return Cogs::PrimitiveType::LineList; break;
111 case CogsBin3::PrimitiveType::LineStrip: return Cogs::PrimitiveType::LineStrip; break;
112 case CogsBin3::PrimitiveType::PointList: return Cogs::PrimitiveType::PointList; break;
113 case CogsBin3::PrimitiveType::TriangleListAdjacency: return Cogs::PrimitiveType::TriangleListAdjacency; break;
114 case CogsBin3::PrimitiveType::TriangleStripAdjacency: return Cogs::PrimitiveType::TriangleStripAdjacency; break;
115 case CogsBin3::PrimitiveType::LineListAdjacency: return Cogs::PrimitiveType::LineListAdjacency; break;
116 case CogsBin3::PrimitiveType::LineStripAdjacency: return Cogs::PrimitiveType::LineStripAdjacency; break;
117 case CogsBin3::PrimitiveType::ControlPoint1PatchList: return Cogs::PrimitiveType::ControlPoint1PatchList; break;
118 case CogsBin3::PrimitiveType::ControlPoint2PatchList: return Cogs::PrimitiveType::ControlPoint2PatchList; break;
119 case CogsBin3::PrimitiveType::ControlPoint3PatchList: return Cogs::PrimitiveType::ControlPoint3PatchList; break;
120 case CogsBin3::PrimitiveType::ControlPoint4PatchList: return Cogs::PrimitiveType::ControlPoint4PatchList; break;
121 default:
122 break;
123 }
124 rctx.success = false;
125 LOG_ERROR(logger, "Illegal primitive type: %u", unsigned(primitiveType));
127 }
128
129 const uint8_t* sectionPointer(ReadContext& rctx, uint32_t index)
130 {
131 // FIXME (chrisdy): Should we leave this one around indefinitely?
132 thread_local ZSTD_DCtx* zstd_dctx = nullptr;
133
134 assert(index < rctx.header->sectionCount);
135
136 Cogs::LockGuard lock(rctx.sectionLocks[index]);
137 const auto & sectionInfo = rctx.sectionInfo[index];
138 if (rctx.sectionPtr[index] == nullptr) {
139 //LOG_DEBUG(logger, "%zx Resolving section %u (compr=%d, size=%zd, fileOffset=%zu, fileSize=%zu)",
140 // std::hash<std::thread::id>{}(std::this_thread::get_id()),
141 // index,
142 // sectionInfo.compression,
143 // size_t(sectionInfo.uncompressedSize),
144 // size_t(sectionInfo.fileOffset),
145 // size_t(sectionInfo.fileSize));
146 switch (rctx.sectionInfo[index].compression)
147 {
148 case CogsBin3::Compression::None:
149 rctx.sectionPtr[index] = rctx.contents->ptr + rctx.sectionInfo[index].fileOffset;
150 break;
151 case CogsBin3::Compression::ZSTD: {
152 if (zstd_dctx == nullptr) {
153 zstd_dctx = ZSTD_createDCtx();
154 }
155
156 // Allocate mem for uncompressed data. Clamp it to minimum 1 bytes so we don't get a nullptr
157 // for empty sections, as we use a null pointer to flag that something has gone horribly
158 // wrong and we give up.
159 rctx.allocations.emplace_back(new uint8_t[sectionInfo.uncompressedSize ? sectionInfo.uncompressedSize : 1]);
160 rctx.sectionPtr[index] = rctx.allocations.back().get();
161
162 auto result = ZSTD_decompressDCtx(zstd_dctx,
163 rctx.allocations.back().get(), sectionInfo.uncompressedSize,
164 rctx.contents->ptr + sectionInfo.fileOffset, sectionInfo.fileSize);
165 if (ZSTD_isError(result)) {
166 LOG_ERROR(logger, "zstd decompression failed: %s", ZSTD_getErrorName(result));
167 return nullptr;
168 }
169 else if (result != sectionInfo.uncompressedSize) {
170 LOG_ERROR(logger, "Uncompressed data size didn't match size recorded in file.");
171 return nullptr;
172 }
173 break;
174 }
175 default:
176 LOG_ERROR(logger, "Illegal compression flag on section.");
177 return nullptr;
178 }
179 }
180 return rctx.sectionPtr[index];
181 }
182
183 Cogs::StringView getString(ReadContext& rctx, uint32_t ix)
184 {
185 if (ix < rctx.strings.count) {
186 auto & str = rctx.strings[ix];
187 Cogs::StringView rv(rctx.stringData.base + str.offset, str.length);
188 //LOG_DEBUG(logger, "Retrieved string %.*s", StringViewFormat(rv));
189 return rv;
190 }
191 else if (!isValidIndex(ix)) {
192 return Cogs::StringView();
193 }
194 else {
195 LOG_ERROR(logger, "Invalid string index %u", ix);
196 rctx.success = false;
197 return Cogs::StringView();
198 }
199 }
200
201 const Cogs::Geometry::BoundingBox& getBoundingBox(ReadContext& rctx, uint32_t ix)
202 {
203 if (ix < rctx.boundingBoxes.count) {
204 return rctx.boundingBoxes[ix];
205 }
206 else {
207 rctx.success = false;
208 LOG_ERROR(logger, "Invalid bounding box index %u", ix);
209 static const Cogs::Geometry::BoundingBox empty = Cogs::Geometry::makeEmptyBoundingBox<Cogs::Geometry::BoundingBox>();
210 return empty;
211 }
212 }
213
214 Cogs::TextureFormat getTextureFormat(ReadContext& /*rctx*/, CogsBin3::Format format)
215 {
216 switch (format) {
217 case CogsBin3::Format::Unknown: return Cogs::TextureFormat::Unknown; break;
218 case CogsBin3::Format::R8_UINT: return Cogs::TextureFormat::R8_UINT; break;
219 case CogsBin3::Format::R8G8_UINT: return Cogs::TextureFormat::R8G8_UINT; break;
220 case CogsBin3::Format::R8G8B8_UINT: return Cogs::TextureFormat::R8G8B8_UINT; break;
221 case CogsBin3::Format::R8G8B8A8_UINT: return Cogs::TextureFormat::R8G8B8A8_UINT; break;
222 case CogsBin3::Format::R16_UINT: return Cogs::TextureFormat::R16_UINT; break;
223 case CogsBin3::Format::R16G16_UINT: return Cogs::TextureFormat::R16G16_UINT; break;
224 case CogsBin3::Format::R16G16B16_UINT: return Cogs::TextureFormat::R16G16B16_UINT; break;
225 case CogsBin3::Format::R16G16B16A16_UINT: return Cogs::TextureFormat::R16G16B16A16_UINT; break;
226 case CogsBin3::Format::R32_UINT: return Cogs::TextureFormat::R32_UINT; break;
227 case CogsBin3::Format::R32G32_UINT: return Cogs::TextureFormat::R32G32_UINT; break;
228 case CogsBin3::Format::R32G32B32_UINT: return Cogs::TextureFormat::R32G32B32_UINT; break;
229 case CogsBin3::Format::R32G32B32A32_UINT: return Cogs::TextureFormat::R32G32B32A32_UINT; break;
230 case CogsBin3::Format::R8_SINT: return Cogs::TextureFormat::R8_SINT; break;
231 case CogsBin3::Format::R8G8_SINT: return Cogs::TextureFormat::R8G8_SINT; break;
232 case CogsBin3::Format::R8G8B8_SINT: return Cogs::TextureFormat::R8G8B8_SINT; break;
233 case CogsBin3::Format::R8G8B8A8_SINT: return Cogs::TextureFormat::R8G8B8A8_SINT; break;
234 case CogsBin3::Format::R16_SINT: return Cogs::TextureFormat::R16_SINT; break;
235 case CogsBin3::Format::R16G16_SINT: return Cogs::TextureFormat::R16G16_SINT; break;
236 case CogsBin3::Format::R16G16B16_SINT: return Cogs::TextureFormat::R16G16B16_SINT; break;
237 case CogsBin3::Format::R16G16B16A16_SINT: return Cogs::TextureFormat::R16G16B16A16_SINT; break;
238 case CogsBin3::Format::R32_SINT: return Cogs::TextureFormat::R32_SINT; break;
239 case CogsBin3::Format::R32G32_SINT: return Cogs::TextureFormat::R32G32_SINT; break;
240 case CogsBin3::Format::R32G32B32_SINT: return Cogs::TextureFormat::R32G32B32_SINT; break;
241 case CogsBin3::Format::R32G32B32A32_SINT: return Cogs::TextureFormat::R32G32B32A32_SINT; break;
242 case CogsBin3::Format::R16_FLOAT: return Cogs::TextureFormat::R16_FLOAT; break;
243 case CogsBin3::Format::R16G16_FLOAT: return Cogs::TextureFormat::R16G16_FLOAT; break;
244 case CogsBin3::Format::R16G16B16_FLOAT: return Cogs::TextureFormat::R16G16B16_FLOAT; break;
245 case CogsBin3::Format::R16G16B16A16_FLOAT: return Cogs::TextureFormat::R16G16B16A16_FLOAT; break;
246 case CogsBin3::Format::R32_FLOAT: return Cogs::TextureFormat::R32_FLOAT; break;
247 case CogsBin3::Format::R32G32_FLOAT: return Cogs::TextureFormat::R32G32_FLOAT; break;
248 case CogsBin3::Format::R32G32B32_FLOAT: return Cogs::TextureFormat::R32G32B32_FLOAT; break;
249 case CogsBin3::Format::R32G32B32A32_FLOAT: return Cogs::TextureFormat::R32G32B32A32_FLOAT; break;
250 case CogsBin3::Format::R64_FLOAT: assert(false && "Illegal texture format"); break;
251 case CogsBin3::Format::R64G64_FLOAT: assert(false && "Illegal texture format"); break;
252 case CogsBin3::Format::R64G64B64_FLOAT: assert(false && "Illegal texture format"); break;
253 case CogsBin3::Format::R64G65B64A64_FLOAT: assert(false && "Illegal texture format"); break;
254 case CogsBin3::Format::R8_UNORM: return Cogs::TextureFormat::R8_UNORM; break;
255 case CogsBin3::Format::R8G8_UNORM: return Cogs::TextureFormat::R8G8_UNORM; break;
256 case CogsBin3::Format::R8G8B8_UNORM: return Cogs::TextureFormat::R8G8B8_UNORM; break;
257 case CogsBin3::Format::R8G8B8A8_UNORM: return Cogs::TextureFormat::R8G8B8A8_UNORM; break;
258 case CogsBin3::Format::R16_UNORM: return Cogs::TextureFormat::R16_UNORM; break;
259 case CogsBin3::Format::R16G16_UNORM: return Cogs::TextureFormat::R16G16_UNORM; break;
260 case CogsBin3::Format::R16G16B16_UNORM: return Cogs::TextureFormat::R16G16B16_UNORM; break;
261 case CogsBin3::Format::R16G16B16A16_UNORM: return Cogs::TextureFormat::R16G16B16A16_UNORM; break;
262 case CogsBin3::Format::R8_SNORM: return Cogs::TextureFormat::R8_SNORM; break;
263 case CogsBin3::Format::R8G8_SNORM: return Cogs::TextureFormat::R8G8_SNORM; break;
264 case CogsBin3::Format::R8G8B8_SNORM: return Cogs::TextureFormat::R8G8B8_SNORM; break;
265 case CogsBin3::Format::R8G8B8A8_SNORM: return Cogs::TextureFormat::R8G8B8A8_SNORM; break;
266 case CogsBin3::Format::R16_SNORM: return Cogs::TextureFormat::R16_SNORM; break;
267 case CogsBin3::Format::R16G16_SNORM: return Cogs::TextureFormat::R16G16_SNORM; break;
268 case CogsBin3::Format::R16G16B16_SNORM: return Cogs::TextureFormat::R16G16B16_SNORM; break;
269 case CogsBin3::Format::R16G16B16A16_SNORM: return Cogs::TextureFormat::R16G16B16A16_SNORM; break;
270 case CogsBin3::Format::D16_UNORM: return Cogs::TextureFormat::D16_UNORM; break;
271 case CogsBin3::Format::D24_UNORM: return Cogs::TextureFormat::D24_UNORM; break;
272 case CogsBin3::Format::D24S8_UNORM: return Cogs::TextureFormat::D24S8_UNORM; break;
273 case CogsBin3::Format::D32_FLOAT: return Cogs::TextureFormat::D32_FLOAT; break;
274 case CogsBin3::Format::R32_TYPELESS: return Cogs::TextureFormat::R32_TYPELESS; break;
275 case CogsBin3::Format::R16_TYPELESS: return Cogs::TextureFormat::R16_TYPELESS; break;
276 case CogsBin3::Format::R8T: return Cogs::TextureFormat::R8T; break;
277 case CogsBin3::Format::R8G8T: return Cogs::TextureFormat::R8G8T; break;
278 case CogsBin3::Format::R8G8B8T: return Cogs::TextureFormat::R8G8B8T; break;
279 case CogsBin3::Format::R8G8B8A8T: return Cogs::TextureFormat::R8G8B8A8T; break;
280 case CogsBin3::Format::B8G8R8: return Cogs::TextureFormat::B8G8R8; break;
281 case CogsBin3::Format::B8G8R8A8: return Cogs::TextureFormat::B8G8R8A8; break;
282 case CogsBin3::Format::A8_UNORM: return Cogs::TextureFormat::A8_UNORM; break;
283 case CogsBin3::Format::BC1_TYPELESS: return Cogs::TextureFormat::BC1_TYPELESS; break;
284 case CogsBin3::Format::BC1_UNORM: return Cogs::TextureFormat::BC1_UNORM; break;
285 case CogsBin3::Format::BC1_UNORM_SRGB: return Cogs::TextureFormat::BC1_UNORM_SRGB; break;
286 case CogsBin3::Format::BC2_TYPELESS: return Cogs::TextureFormat::BC2_TYPELESS; break;
287 case CogsBin3::Format::BC2_UNORM: return Cogs::TextureFormat::BC2_UNORM; break;
288 case CogsBin3::Format::BC2_UNORM_SRGB: return Cogs::TextureFormat::BC2_UNORM_SRGB; break;
289 case CogsBin3::Format::BC3_TYPELESS: return Cogs::TextureFormat::BC3_TYPELESS; break;
290 case CogsBin3::Format::BC3_UNORM: return Cogs::TextureFormat::BC3_UNORM; break;
291 case CogsBin3::Format::BC3_UNORM_SRGB: return Cogs::TextureFormat::BC3_UNORM_SRGB; break;
292 case CogsBin3::Format::BC4_TYPELESS: return Cogs::TextureFormat::BC4_TYPELESS; break;
293 case CogsBin3::Format::BC4_UNORM: return Cogs::TextureFormat::BC4_UNORM; break;
294 case CogsBin3::Format::BC4_SNORM: return Cogs::TextureFormat::BC4_SNORM; break;
295 case CogsBin3::Format::BC5_TYPELESS: return Cogs::TextureFormat::BC5_TYPELESS; break;
296 case CogsBin3::Format::BC5_UNORM: return Cogs::TextureFormat::BC5_UNORM; break;
297 case CogsBin3::Format::BC5_SNORM: return Cogs::TextureFormat::BC5_SNORM; break;
298 case CogsBin3::Format::R8G8B8A8_UNORM_SRGB: return Cogs::TextureFormat::R8G8B8A8_UNORM_SRGB; break;
299 default:
300 assert(false && "Illegal format");
301 break;
302 }
303 return Cogs::TextureFormat::Unknown;
304 }
305
306 Cogs::VertexFormatHandle getVertexFormat(ReadContext& rctx, uint32_t firstAttribute, uint32_t attributeCount)
307 {
308 const auto * attributes = rctx.vertexAttributes.base + firstAttribute;
309
310 std::vector<Cogs::VertexElement> elements;
311 elements.resize(attributeCount);
312 for (unsigned i = 0; i < attributeCount; i++) {
313 const auto & attribute = attributes[i];
314
315 auto & element = elements[i];
316 element.offset = attribute.offset;
317 element.format = (Cogs::DataFormat)attribute.format;
318
319 switch (attribute.semantic) {
320 case CogsBin3::Semantic::Position: element.semantic = Cogs::ElementSemantic::Position; break;
321 case CogsBin3::Semantic::Normal: element.semantic = Cogs::ElementSemantic::Normal; break;
322 case CogsBin3::Semantic::Color: element.semantic = Cogs::ElementSemantic::Color; break;
323 case CogsBin3::Semantic::TexCoord: element.semantic = Cogs::ElementSemantic::TextureCoordinate; break;
324 case CogsBin3::Semantic::Tangent: element.semantic = Cogs::ElementSemantic::Tangent; break;
325 case CogsBin3::Semantic::InstanceVector: element.semantic = Cogs::ElementSemantic::InstanceVector; break;
326 case CogsBin3::Semantic::InstanceMatrix: element.semantic = Cogs::ElementSemantic::InstanceMatrix; break;
327 default:
328 LOG_ERROR(logger, "Illegal vertex element semantic %u", unsigned(attribute.semantic));
329 return {};
330 }
331 element.semanticIndex = attribute.semanticIndex;
332 if (attribute.instanceStepRate == 0) {
333 element.inputType = Cogs::InputType::VertexData;
334 element.instanceStep = 0;
335 }
336 else {
337 element.inputType = Cogs::InputType::InstanceData;
338 element.instanceStep = attribute.instanceStepRate - 1;
339 }
340 element.inputType = attribute.instanceStepRate == 0 ? Cogs::InputType::VertexData : Cogs::InputType::InstanceData;
341 element.instanceStep = attribute.instanceStepRate;
342 }
343
344 return Cogs::VertexFormats::createVertexFormat(elements.data(), elements.size());
345 }
346
347 const uint8_t* resolveDataOffset(ReadContext& rctx, uint64_t offset, uint32_t size)
348 {
349 const auto sectionIx = uint16_t(offset >> 48);
350 const auto * section = sectionPointer(rctx, sectionIx);
351 if (section == nullptr) {
352 LOG_ERROR(logger, "Failed to retrieve data section %u", unsigned(sectionIx));
353 rctx.success = false;
354 return nullptr;
355 }
356 const auto sectionOffset = size_t(offset & 0xFFFFFFFFFFFFull);
357 if (rctx.sectionInfo[sectionIx].uncompressedSize < sectionOffset + size) {
358 LOG_ERROR(logger, "Stream source offset %zX size %u is outside section memory range.", sectionOffset, size);
359 rctx.success = false;
360 return nullptr;
361 }
362 return section + sectionOffset;
363 }
364
365 template<typename D, typename S>
366 void widenAndCopy(std::span<D>& dstSpan, const S* src, uint32_t count)
367 {
368 auto * dst = dstSpan.data();
369 // preserve notzeros. When narrowing in the writer, we check for
370 // *less than*, so an actual value of 256 should trigger uint16_t storage.
371 constexpr auto notzero = std::numeric_limits<S>::max();
372 for (uint32_t i = 0; i < count; i++) {
373 const auto v = *src++;
374 if (v == notzero) *dst++ = ~D(0);
375 else *dst++ = D(v);
376 }
377 }
378
379 void populateMesh(ReadContext& rctx, MeshHandle handle, uint32_t index)
380 {
381 const auto & srcMesh = rctx.meshes[index];
382 auto dstMesh = rctx.context->meshManager->lock(handle);
383
384 if (isValidIndex(srcMesh.name)) {
385 dstMesh->setName(getString(rctx, srcMesh.name));
386 }
387 if (isValidIndex(srcMesh.boundingBox)) {
388 dstMesh->setBounds(getBoundingBox(rctx, srcMesh.boundingBox));
389 }
390 if ((srcMesh.flags & CogsBin3::MeshFlags::Clockwise) != 0) {
391 dstMesh->setMeshFlag(MeshFlags::ClockwiseWinding);
392 }
393 if ((srcMesh.flags & CogsBin3::MeshFlags::Skinned) != 0) {
394 dstMesh->setMeshFlag(MeshFlags::Skinned);
395 }
396
397 dstMesh->primitiveType = translatePrimitiveType(rctx, srcMesh.primitiveType);
398
399 dstMesh->streams.resize(srcMesh.streamCount);
400 for (size_t i = 0; i < srcMesh.streamCount; i++) {
401 assert(srcMesh.firstStream + i < rctx.vertexStreams.count);
402 const auto & srcStream = rctx.vertexStreams[srcMesh.firstStream + i];
403
404 auto & dstStream = dstMesh->streams[i];
405
406 {
407 Cogs::LockGuard lock(rctx.buffersMutex);
408 assert(srcStream.buffer < rctx.buffers.count);
409
410 if (rctx.bufferCache[srcStream.buffer]) {
411 dstStream.buffer = rctx.bufferCache[srcStream.buffer];
412 } else {
413 auto & dstBuffer = rctx.bufferCache[srcStream.buffer] = rctx.context->bufferManager->create();
414 dstStream.buffer = dstBuffer;
415
416 const auto & srcBuffer = rctx.buffers[srcStream.buffer];
417 const auto * srcData = resolveDataOffset(rctx, srcBuffer.sectionOffset, srcBuffer.size);
418
419 dstBuffer->reset(srcBuffer.size, rctx.context->memory->resourceAllocator);
420 std::memcpy(dstBuffer->data(), srcData, srcBuffer.size);
421
422 if ((srcBuffer.flags & CogsBin3::BufferContentFlags::VertexData) != 0) {
423 dstBuffer->setBindFlags(BufferBindFlags::VertexBuffer);
424 }
425
426 if ((srcBuffer.flags & CogsBin3::BufferContentFlags::IndexData) != 0) {
427 dstBuffer->setBindFlags(BufferBindFlags::IndexBuffer);
428 }
429 }
430 }
431
432 dstStream.numElements = srcStream.count;
433 dstStream.type = VertexDataType::EVertexDataType(srcStream.vertexDataType);
434 dstStream.stride = srcStream.stride;
435 dstStream.offset = srcStream.offset;
436
437 dstMesh->streamIndexes[dstStream.type] = uint8_t(i);
438 dstMesh->streamsUpdated |= Mesh::toFlag(dstStream.type);
439 dstMesh->setMeshFlag(MeshFlags::StreamsChanged);
440
441 if (srcStream.vertexDataType < VertexDataType::LastVertexType) {
442 dstStream.format = isValidIndex(srcStream.firstAttribute)
443 ? getVertexFormat(rctx, srcStream.firstAttribute, srcStream.attributeCount)
445
446 if (!dstStream.format) {
447 LOG_ERROR(logger, "Stream %zu has illegal format", i);
448 return;
449 }
450
451 if (!dstMesh->isIndexed()) {
452 switch (dstStream.type) {
453 case VertexDataType::Positions:
454 case VertexDataType::Interleaved0:
455 case VertexDataType::Interleaved1:
456 dstMesh->setCount(srcStream.count);
457 break;
458 default:
459 break;
460 }
461 }
462 } else if (srcStream.vertexDataType == VertexDataType::Indexes) {
463 dstMesh->setCount(srcStream.count);
464 dstMesh->setMeshFlag(MeshFlags::Indexed);
465 dstMesh->setMeshFlag(MeshFlags::IndexesChanged);
466 } else if (srcStream.vertexDataType == VertexDataType::SubMeshes) {
467 dstMesh->submeshCount = (uint16_t)srcStream.count;
468 }
469 }
470 }
471
472
473 template<typename T>
474 const T& getPropertyValue(ReadContext& rctx, const CogsBin3::Property& prop)
475 {
476 if constexpr (sizeof(T) <= sizeof(uint32_t)) {
477 return *(const T*)(&prop.value);
478 }
479 else {
480 if (prop.value < rctx.propData.count) {
481 return *(const T*)(rctx.propData.base + prop.value);
482 }
483 else {
484 LOG_ERROR(logger, "Illegal property data offset %u", prop.value);
485 static T t = {};
486 return t;
487 }
488 }
489 }
490
491 TextureHandle populateEmbeddedTexture(ReadContext& rctx, uint32_t textureIx)
492 {
493 assert(textureIx < rctx.textures.count);
494 if (auto handle = rctx.textureCache[textureIx]; HandleIsValid(handle)) {
495 return handle;
496 }
497 auto & tex = rctx.textures[textureIx];
498
499
500 assert(tex.buffer < rctx.buffers.count);
501 const auto & srcBuffer = rctx.buffers[tex.buffer];
502 const auto * bufferData = resolveDataOffset(rctx, srcBuffer.sectionOffset, srcBuffer.size) + tex.offset;
503
504 TextureLoadInfo * loadInfo = rctx.context->textureManager->createLoadInfo();
505
506 loadInfo->resourceId = (uint32_t)NoResourceId;
507 //loadInfo.resourcePath;
508 if (isValidIndex(tex.name)) {
509 loadInfo->resourceName = getString(rctx, tex.name).to_string();
510 }
511 loadInfo->resourceData.assign(bufferData, bufferData + tex.size);
512 //loadInfo.resourceFlags;
513 //loadInfo.handle;
514 loadInfo->format = getTextureFormat(rctx, tex.format);
515 loadInfo->target = Cogs::ResourceDimensions::Texture2D;
516 loadInfo->width = static_cast<int>(tex.width);
517 loadInfo->height = static_cast<int>(tex.height);
518 loadInfo->depth = static_cast<int>(tex.depth);
519 loadInfo->stride = 0;
520 //loadInfo.flip = 0;
521
522 auto handle = rctx.context->textureManager->loadTexture(loadInfo);
523
524 rctx.textureCache[textureIx] = handle;
525
526 return handle;
527 }
528
529 MaterialInstanceHandle populateMaterialInstance(ReadContext& rctx, uint32_t materialInstanceIx)
530 {
531 const auto & srcMaterialInstance = rctx.materialInstances[materialInstanceIx];
532 if (!isValidIndex(srcMaterialInstance.material)) {
533 LOG_ERROR(logger, "Material instance without material.");
534 rctx.success = false;
536 }
537
538 auto materialName = getString(rctx, srcMaterialInstance.material);
539 if (materialName.empty()) {
540 LOG_ERROR(logger, "Writer should not include empty strings.");
541 rctx.success = false;
543 }
544
545 auto materialTemplate = rctx.context->materialManager->getMaterial(materialName);
546 if (!materialTemplate) {
547 LOG_ERROR(logger, "Invalid material template %.*s", StringViewFormat(materialName));
548 rctx.success = false;
550 }
551 //LOG_DEBUG(logger, "Instancing material %.*s:", StringViewFormat(materialName));
552
553 auto material = rctx.context->materialInstanceManager->createMaterialInstance(materialTemplate);
554
555 if (isValidIndex(srcMaterialInstance.permutation)) {
556 material->setPermutation(getString(rctx, srcMaterialInstance.permutation));
557 }
558
559 for (uint32_t i = 0; i < srcMaterialInstance.propertyCount; i++) {
560 if (rctx.props.count <= srcMaterialInstance.propertyFirst + i) {
561 LOG_ERROR(logger, "Invalid property index.");
562 rctx.success = false;
564 }
565
566 auto & prop = rctx.props[srcMaterialInstance.propertyFirst + i];
567 switch (prop.keyType) {
568 case CogsBin3::PropertyKeyType::Index:
569 assert(false && "FIXME");
570 break;
571
572 case CogsBin3::PropertyKeyType::String: {
573
574 auto keyString = getString(rctx, prop.key);
575 switch (prop.valueType) {
576
577 case CogsBin3::PropertyValueType::Bool:
578 if (auto key = materialTemplate->getBoolKey(keyString); key != NoProperty)
579 material->setBoolProperty(key, getPropertyValue<uint32_t>(rctx, prop));
580 else
581 LOG_ERROR(logger, "Invalid bool key %.*s", StringViewFormat(keyString));
582 break;
583
584 case CogsBin3::PropertyValueType::UInt:
585 if (auto key = materialTemplate->getUIntKey(keyString); key != NoProperty)
586 material->setUIntProperty(key, getPropertyValue<uint32_t>(rctx, prop));
587 else
588 LOG_ERROR(logger, "Invalid uint key %.*s", StringViewFormat(keyString));
589 break;
590
591 case CogsBin3::PropertyValueType::Float:
592 if (auto key = materialTemplate->getFloatKey(keyString); key != NoProperty)
593 material->setFloatProperty(key, getPropertyValue<float>(rctx, prop));
594 else
595 LOG_ERROR(logger, "Invalid float key %.*s", StringViewFormat(keyString));
596 break;
597
598 case CogsBin3::PropertyValueType::Float2:
599 if (auto key = materialTemplate->getVec2Key(keyString); key != NoProperty)
600 material->setVec2Property(key, getPropertyValue<glm::vec2>(rctx, prop));
601 else
602 LOG_ERROR(logger, "Invalid float2 key %.*s", StringViewFormat(keyString));
603 break;
604
605 case CogsBin3::PropertyValueType::Float3:
606 if (auto key = materialTemplate->getVec3Key(keyString); key != NoProperty)
607 material->setVec3Property(key, getPropertyValue<glm::vec3>(rctx, prop));
608 else
609 LOG_ERROR(logger, "Invalid float3 key %.*s", StringViewFormat(keyString));
610 break;
611
612 case CogsBin3::PropertyValueType::Float4:
613 if (auto key = materialTemplate->getVec4Key(keyString); key != NoProperty)
614 material->setVec4Property(key, getPropertyValue<glm::vec4>(rctx, prop));
615 else
616 LOG_ERROR(logger, "Invalid float4 key %.*s", StringViewFormat(keyString));
617 break;
618
619 case CogsBin3::PropertyValueType::String:
620 if (auto key = materialTemplate->getTextureKey(keyString); key != NoProperty) {
621 auto textureFlags = TextureLoadFlags::None;
622 if (const auto & mprop = materialTemplate->textureProperties[key]; ((int)mprop.flags & (int)MaterialPropertyFlags::sRGB) == 0) {
623 textureFlags |= TextureLoadFlags::LinearColorSpace;
624 }
625 std::string sourcePath = getString(rctx, prop.value).to_string();
626 if (Cogs::IO::isRelative(sourcePath)) {
627 sourcePath = Cogs::IO::combine(Cogs::IO::parentPath(rctx.path), sourcePath);
628 }
629 auto texture = rctx.context->textureManager->loadTexture(sourcePath, NoResourceId, textureFlags);
630 material->setTextureProperty(key, texture);
631 }
632 else
633 LOG_ERROR(logger, "Invalid float4 key %.*s", StringViewFormat(keyString));
634 break;
635
636 case CogsBin3::PropertyValueType::Variant: {
637 auto key = getString(rctx, prop.key);
638 auto val = getString(rctx, prop.value);
639 if (materialName == "DefaultMaterial" && key == "NormalMap") {
640 key = "TangentSpaceNormalMap";
641 }
642 material->setVariant(key, val);
643 //LOG_DEBUG(logger, "Variant %.*s=%.*s",
644 // StringViewFormat(key),
645 // StringViewFormat(val));
646 break;
647 }
648 case CogsBin3::PropertyValueType::EmbeddedTexture:
649 if (auto key = materialTemplate->getTextureKey(keyString); key != NoProperty) {
650 auto textureIx = getPropertyValue<uint32_t>(rctx, prop);
651 auto texture = populateEmbeddedTexture(rctx, textureIx);
652 material->setTextureProperty(key, texture);
653 }
654 break;
655
656 default:
657 LOG_ERROR(logger, "Invalid property value type.");
658 rctx.success = false;
660 }
661 break;
662 }
663 default:
664 LOG_ERROR(logger, "Invalid property key type.");
665 rctx.success = false;
667 }
668 }
669 return material;
670
671 }
672
673 template<typename ResourceProxy>
674 void populatePart(ReadContext& rctx, ResourceProxy& model, ModelPart& part, const CogsBin3::Node& node)
675 {
676 // parentIndex is populated by caller
677
678 if (isValidIndex(node.boundingBox)) {
679 part.boundsIndex = uint32_t(model->bounds.size());
680 model->bounds.emplace_back(getBoundingBox(rctx, node.boundingBox));
681 }
682 if (isValidIndex(node.transform)) {
683 const auto * m = rctx.transforms[node.transform].m;
684 model->setPartTransform(part, glm::mat4(m[0], m[1], m[2], 0.f,
685 m[3], m[4], m[5], 0.f,
686 m[6], m[7], m[8], 0.f,
687 m[9], m[10], m[11], 1.f));
688 }
689 if (isValidIndex(node.name)) {
690 model->setPartName(part, getString(rctx, node.name));
691 }
692 if (isValidIndex(node.mesh)) {
693 part.meshIndex = uint32_t(model->meshes.size());
694 model->meshes.emplace_back(rctx.context->meshManager->create());
695 if (rctx.group != NoTask) {
696 rctx.context->taskManager->enqueueChild(rctx.group, [rc = &rctx, h = model->meshes.back(), ix = node.mesh](){populateMesh(*rc, h, ix); });
697 }
698 else {
699 populateMesh(rctx, model->meshes.back(), node.mesh);
700 }
701 }
702 if (isValidIndex(node.materialInstance)) {
703 part.materialIndex = uint32_t(model->materials.size());
704 model->materials.emplace_back(populateMaterialInstance(rctx, node.materialInstance));
705 }
706 if (node.primitiveType != CogsBin3::PrimitiveType::Unknown) {
707 part.primitiveType = translatePrimitiveType(rctx, node.primitiveType);
708 } else {
709 part.primitiveType = (uint32_t)-1;
710 }
711 part.startIndex = node.vertexFirst;
712 part.vertexCount = node.vertexCount;
713 }
714
715 void populateBone(ReadContext& rctx, Bone& dstBone, const uint32_t boneIx, const uint32_t boneCount)
716 {
717 if (rctx.bones.count <= boneIx) {
718 LOG_ERROR(logger, "Invalid bone index %u (bone count is %zu)", boneIx, rctx.bones.count);
719 rctx.success = false;
720 return;
721 }
722 const auto & srcBone = rctx.bones[boneIx];
723 dstBone.name = getString(rctx, srcBone.name).to_string();
724 if (isValidIndex(srcBone.parentBone)) {
725 if (boneCount <= srcBone.parentBone) {
726 LOG_ERROR(logger, "Invalid parent bone index %u (skeleton bone count is %u)", unsigned(srcBone.parentBone), boneCount);
727 rctx.success = false;
728 return;
729 }
730 dstBone.parentBone = srcBone.parentBone;
731 }
732 else {
733 dstBone.parentBone = (size_t)-1;
734 }
735 dstBone.pos = srcBone.pos;
736 dstBone.scale = srcBone.scale;
737 dstBone.rot = srcBone.rot;
738 dstBone.relative = srcBone.relative;
739 dstBone.inverseBindPose = srcBone.inverseBindPose;
740 dstBone.hasOffset = (srcBone.flags & CogsBin3::BoneFlags::HasOffset) != 0;
741 dstBone.used = (srcBone.flags & CogsBin3::BoneFlags::Used) != 0;
742 dstBone.animated = (srcBone.flags & CogsBin3::BoneFlags::Animated) != 0;
743 }
744
745 void populateSkeleton(ReadContext& rctx, Skeleton& dstSkeleton, const uint32_t skeletonIx)
746 {
747 if (rctx.skeletons.count <= skeletonIx) {
748 LOG_ERROR(logger, "Invalid skeleton index %u (skeleton count is %zu)", skeletonIx, rctx.skeletons.count);
749 rctx.success = false;
750 return;
751 }
752 const auto & srcSkeleton = rctx.skeletons[skeletonIx];
753 dstSkeleton.bindPose = srcSkeleton.bindPose;
754 dstSkeleton.bones.resize(srcSkeleton.boneCount);
755 for (uint32_t i = 0; i < srcSkeleton.boneCount && rctx.success; i++) {
756 populateBone(rctx, dstSkeleton.bones[i], srcSkeleton.firstBone + i, srcSkeleton.boneCount);
757 dstSkeleton.bonesByName[dstSkeleton.bones[i].name] = i;
758 }
759 updateSkeletonPose(dstSkeleton);
760 }
761
762 void populateAnimTrack(ReadContext& rctx, AnimationTrack& dstTrack, const uint32_t animTrackIx)
763 {
764 assert(animTrackIx < rctx.animTracks.count);
765 const auto & srcTrack = rctx.animTracks[animTrackIx];
766
767 assert(srcTrack.buffer < rctx.buffers.count);
768 const auto & buffer = rctx.buffers[srcTrack.buffer];
769 const auto * bufferData = resolveDataOffset(rctx, buffer.sectionOffset, buffer.size);
770
771 dstTrack.boneIndex = isValidIndex(srcTrack.boneIndex) ? srcTrack.boneIndex : -1;
772
773 const auto * translationSrc = (const float*)(bufferData + srcTrack.translationOffset);
774 dstTrack.translations.resize(srcTrack.translationCount);
775 for (unsigned i = 0; i < srcTrack.translationCount; i++) {
776 dstTrack.translations[i].t = *translationSrc++;
777 dstTrack.translations[i].value[0] = *translationSrc++;
778 dstTrack.translations[i].value[1] = *translationSrc++;
779 dstTrack.translations[i].value[2] = *translationSrc++;
780 }
781
782 const auto * rotationSrc = (const float*)(bufferData + srcTrack.rotationOffset);
783 dstTrack.rotations.resize(srcTrack.rotationCount);
784 for (unsigned i = 0; i < srcTrack.rotationCount; i++) {
785 dstTrack.rotations[i].t = *rotationSrc++;
786 dstTrack.rotations[i].value[0] = *rotationSrc++;
787 dstTrack.rotations[i].value[1] = *rotationSrc++;
788 dstTrack.rotations[i].value[2] = *rotationSrc++;
789 dstTrack.rotations[i].value[3] = *rotationSrc++;
790 }
791
792 const auto * scaleSrc = (const float*)(bufferData + srcTrack.scaleOffset);
793 dstTrack.scales.resize(srcTrack.scaleCount);
794 for (unsigned i = 0; i < srcTrack.scaleCount; i++) {
795 dstTrack.scales[i].t = *scaleSrc++;
796 dstTrack.scales[i].value[0] = *scaleSrc++;
797 dstTrack.scales[i].value[1] = *scaleSrc++;
798 dstTrack.scales[i].value[2] = *scaleSrc++;
799 }
800 }
801
802 void populateAnimClip(ReadContext& rctx, AnimationClip& dstClip, const uint32_t animClipIx)
803 {
804 if (rctx.animClips.count <= animClipIx) {
805 LOG_ERROR(logger, "Invalid animation clip index %u (animation clip count is %zu)", animClipIx, rctx.animClips.count);
806 rctx.success = false;
807 return;
808 }
809 const CogsBin3::AnimClip& srcClip = rctx.animClips[animClipIx];
810 dstClip.name = getString(rctx, srcClip.name).to_string();
811 dstClip.duration = srcClip.duration;
812 dstClip.resolution = srcClip.resolution;
813 dstClip.tracks.resize(srcClip.trackCount);
814 for (unsigned i = 0; i < srcClip.trackCount && rctx.success; i++) {
815 populateAnimTrack(rctx, dstClip.tracks[i], srcClip.trackFirst + i);
816 }
817 }
818
819 void populateModel(ReadContext& rctx, ResourceHandleBase modelHandle, const uint32_t modelIx)
820 {
821 const auto & srcModel = rctx.models[modelIx];
822 if (srcModel.nodeCount == 0) return;
823
824 assert(modelHandle && "Model handle without resource");
825 auto dstModel = rctx.context->modelManager->lock(modelHandle);
826 assert(dstModel->parts.empty() && "Destination model is not empty");
827
828 // Populate skeleton
829 if (isValidIndex(srcModel.skeleton)) populateSkeleton(rctx, dstModel->skeleton, srcModel.skeleton);
830
831 // Populate animation
832 if (srcModel.animClipCount != 0) {
833 dstModel->animation = rctx.context->animationManager->create();
834
835 auto * anim = dstModel->animation.resolve();
836 anim->skeleton = dstModel->skeleton; // use model skeleton as animation skeleton.
837 anim->clips.resize(srcModel.animClipCount);
838 for (uint32_t i = 0; i < srcModel.animClipCount && rctx.success; i++) {
839 populateAnimClip(rctx, anim->clips[i], srcModel.animClipFirst + i);
840 anim->clipsByName[anim->clips[i].name] = i;
841 }
842 }
843
844 // Populate parts
845 dstModel->parts.resize(srcModel.nodeCount);
846 for (size_t i = 0; i < srcModel.nodeCount && rctx.success; i++) {
847 if (!isValidIndex(srcModel.firstNode) || rctx.nodes.count <= srcModel.firstNode + i) {
848 LOG_ERROR(logger, "Invalid node index %u (node count is %zu)", srcModel.firstNode, rctx.nodes.count);
849 rctx.success = false;
850 return;
851 }
852 const auto & node = rctx.nodes[srcModel.firstNode + i];
853 auto & part = dstModel->parts[i];
854 if (!isValidIndex(node.parent)) {
855 part.parentIndex = ~0u;
856 }
857 else if (node.parent < srcModel.firstNode || srcModel.firstNode + srcModel.nodeCount <= node.parent) {
858 LOG_ERROR(logger, "Illegal parent node index %u (node count is %zu)", node.parent, rctx.nodes.count);
859 rctx.success = false;
860 return;
861 }
862 else {
863 part.parentIndex = node.parent - srcModel.firstNode;
864 }
865 populatePart(rctx, dstModel, part, node);
866 }
867 }
868
869
870}
871
872bool Cogs::Core::loadCogsBin3(Context * context, const ModelLoadInfo & loadInfo, std::unique_ptr<Cogs::FileContents> contents_)
873{
874 if (!contents_ || contents_->size < sizeof(CogsBin3::Header)) return false;
875
876 ReadContext rctx;
877 rctx.context = context;
878 rctx.contents = std::move(contents_);
879
880 if (loadCount.fetch_add(1) < COGS_MODELLOADER_MAX_TASKS) {
881 rctx.group = context->taskManager->createGroup(TaskManager::ResourceQueue);
882 }
883 rctx.path = loadInfo.resourcePath;
884 rctx.success = true;
885
886 unsigned modelIx = 0u;
887 if (auto i = rctx.path.find('#'); i != std::string::npos) {
888 if (i + 1 < rctx.path.length()) {
889 modelIx = std::atoi(rctx.path.c_str() + i + 1);
890 }
891 rctx.path.resize(i);
892 }
893
894 if (rctx.contents->size < sizeof(CogsBin3::Header)) {
895 LOG_ERROR(logger, "Model file too small to contain a header, unable to load.");
896 return false;
897 }
898
899 rctx.header = (CogsBin3::Header *)rctx.contents->ptr;
900 if (rctx.header->sectionCount == 0) {
901 LOG_ERROR(logger, "No sections in file.");
902 return false;
903 }
904 if (rctx.header->fileLength != rctx.contents->size) {
905 LOG_ERROR(logger, "File size mismatch in read context, potentially corrupt or truncated model file.");
906 return false;
907 }
908
909 rctx.sectionInfo = std::span<CogsBin3::SectionInfo>((CogsBin3::SectionInfo*)(rctx.contents->ptr + sizeof(CogsBin3::Header)),
910 (CogsBin3::SectionInfo*)(rctx.contents->ptr + sizeof(CogsBin3::Header)) + rctx.header->sectionCount);
911 rctx.sectionPtr.resize(rctx.header->sectionCount);
912 rctx.sectionLocks.reset(new Cogs::Mutex[rctx.header->sectionCount]);
913
914 LOG_TRACE(logger, "Parsed header, %u sections, filesize=%zu", rctx.header->sectionCount, rctx.contents->size);
915
916
917 if ((rctx.sectionInfo[0].type & CogsBin3::SectionType::META) == 0) {
918 LOG_ERROR(logger, "First section is not a meta section.");
919 return false;
920 }
921
922 const auto * sectionBase = sectionPointer(rctx, 0);
923 if (sectionBase == nullptr) return false;
924
925 for (uint16_t sub = 0; sub < rctx.sectionInfo[0].subSectionCount; sub++) {
926 const auto & metaSubSection = ((CogsBin3::SubSectionInfo*)sectionBase)[sub];
927 switch (metaSubSection.type)
928 {
929 case CogsBin3::SubSectionType::Buffers:
930 rctx.buffers.base = (const CogsBin3::Buffer*)(sectionBase + metaSubSection.offset);
931 rctx.buffers.count = metaSubSection.size / sizeof(CogsBin3::Buffer);
932 rctx.bufferCache.resize(rctx.buffers.count);
933 //LOG_DEBUG(logger, "Found %zu buffers.", rctx.buffers.count);
934 break;
935 case CogsBin3::SubSectionType::Textures:
936 rctx.textures.base = (const CogsBin3::Texture*)(sectionBase + metaSubSection.offset);
937 rctx.textures.count = metaSubSection.size / sizeof(CogsBin3::Texture);
938 rctx.textureCache.resize(rctx.textures.count);
939 //LOG_DEBUG(logger, "Found %zu textures.", rctx.textures.count);
940 break;
941 case CogsBin3::SubSectionType::Strings:
942 rctx.strings.base = (const CogsBin3::String*)(sectionBase + metaSubSection.offset);
943 rctx.strings.count = metaSubSection.size / sizeof(CogsBin3::String);
944 //LOG_DEBUG(logger, "Found %zu strings.", rctx.strings.count);
945 break;
946 case CogsBin3::SubSectionType::StringData:
947 rctx.stringData.base = (char*)(sectionBase + metaSubSection.offset);
948 rctx.stringData.count = metaSubSection.size;
949 //LOG_DEBUG(logger, "Found %zu stringData bytes.", rctx.stringData.count);
950 break;
951 case CogsBin3::SubSectionType::Nodes:
952 rctx.nodes.base = (const CogsBin3::Node*)(sectionBase + metaSubSection.offset);
953 rctx.nodes.count = metaSubSection.size / sizeof(CogsBin3::Node);
954 //LOG_DEBUG(logger, "Found %zu nodes.", rctx.nodes.count);
955 break;
956 case CogsBin3::SubSectionType::Transforms:
957 rctx.transforms.base = (const CogsBin3::Transform*)(sectionBase + metaSubSection.offset);
958 rctx.transforms.count = metaSubSection.size / sizeof(CogsBin3::Transform);
959 //LOG_DEBUG(logger, "Found %zu transforms.", rctx.transforms.count);
960 break;
961 case CogsBin3::SubSectionType::BoundingBoxes:
962 rctx.boundingBoxes.base = (const Cogs::Geometry::BoundingBox*)(sectionBase + metaSubSection.offset);
963 rctx.boundingBoxes.count = metaSubSection.size / sizeof(Cogs::Geometry::BoundingBox);
964 //LOG_DEBUG(logger, "Found %zu bounding boxes.", rctx.boundingBoxes.count);
965 break;
966 case CogsBin3::SubSectionType::MaterialInstances:
967 rctx.materialInstances.base = (const CogsBin3::MaterialInstance*)(sectionBase + metaSubSection.offset);
968 rctx.materialInstances.count = metaSubSection.size / sizeof(CogsBin3::MaterialInstance);
969 //LOG_DEBUG(logger, "Found %zu material instances.", rctx.materialInstances.count);
970 break;
971 case CogsBin3::SubSectionType::Properties:
972 rctx.props.base = (const CogsBin3::Property*)(sectionBase + metaSubSection.offset);
973 rctx.props.count = metaSubSection.size / sizeof(CogsBin3::Property);
974 //LOG_DEBUG(logger, "Found %zu properties.", rctx.props.count);
975 break;
976 case CogsBin3::SubSectionType::PropertyData:
977 rctx.propData.base = sectionBase + metaSubSection.offset;
978 rctx.propData.count = metaSubSection.size;
979 //LOG_DEBUG(logger, "Found %zu propData bytes.", rctx.propData.count);
980 break;
981 case CogsBin3::SubSectionType::Meshes:
982 rctx.meshes.base = (const CogsBin3::Mesh*)(sectionBase + metaSubSection.offset);
983 rctx.meshes.count = metaSubSection.size / sizeof(CogsBin3::Mesh);
984 //LOG_DEBUG(logger, "Found %zu meshes.", rctx.meshes.count);
985 break;
986 case CogsBin3::SubSectionType::VertexStreams:
987 rctx.vertexStreams.base = (const CogsBin3::VertexStream*)(sectionBase + metaSubSection.offset);
988 rctx.vertexStreams.count = metaSubSection.size / sizeof(CogsBin3::VertexStream);
989 //LOG_DEBUG(logger, "Found %zu vertex streams.", rctx.vertexStreams.count);
990 break;
991 case CogsBin3::SubSectionType::VertexAttributes:
992 rctx.vertexAttributes.base = (const CogsBin3::VertexAttribute*)(sectionBase + metaSubSection.offset);
993 rctx.vertexAttributes.count = metaSubSection.size / sizeof(CogsBin3::VertexAttribute);
994 //LOG_DEBUG(logger, "Found %zu vertex attributes.", rctx.vertexAttributes.count);
995 break;
996 case CogsBin3::SubSectionType::Bones:
997 rctx.bones.base = (const CogsBin3::Bone*)(sectionBase + metaSubSection.offset);
998 rctx.bones.count = metaSubSection.size / sizeof(CogsBin3::Bone);
999 //LOG_DEBUG(logger, "Found %zu bones.", rctx.bones.count);
1000 break;
1001 case CogsBin3::SubSectionType::Skeletons:
1002 rctx.skeletons.base = (const CogsBin3::Skeleton*)(sectionBase + metaSubSection.offset);
1003 rctx.skeletons.count = metaSubSection.size / sizeof(CogsBin3::Skeleton);
1004 //LOG_DEBUG(logger, "Found %zu skeletons.", rctx.skeletons.count);
1005 break;
1006 case CogsBin3::SubSectionType::AnimTracks:
1007 rctx.animTracks.base = (const CogsBin3::AnimTrack*)(sectionBase + metaSubSection.offset);
1008 rctx.animTracks.count = metaSubSection.size / sizeof(CogsBin3::AnimTrack);
1009 //LOG_DEBUG(logger, "Found %zu anim tracks.", rctx.animTracks.count);
1010 break;
1011 case CogsBin3::SubSectionType::AnimClips:
1012 rctx.animClips.base = (const CogsBin3::AnimClip*)(sectionBase + metaSubSection.offset);
1013 rctx.animClips.count = metaSubSection.size / sizeof(CogsBin3::AnimClip);
1014 //LOG_DEBUG(logger, "Found %zu anim clips.", rctx.animClips.count);
1015 break;
1016 case CogsBin3::SubSectionType::Models:
1017 rctx.models.base = (const CogsBin3::Model*)(sectionBase + metaSubSection.offset);
1018 rctx.models.count = metaSubSection.size / sizeof(CogsBin3::Model);
1019 //LOG_DEBUG(logger, "Found %zu models.", rctx.models.count);
1020 break;
1021 default:
1022 LOG_ERROR(logger, "Unknown subsection type %u", unsigned(metaSubSection.type));
1023 }
1024 }
1025
1026 // Try to detect problems upfront gracefully
1027 for (uint32_t i = 0; i < rctx.buffers.count; i++) {
1028 const auto & buffer = rctx.buffers[i];
1029 const auto sectionIx = buffer.sectionOffset >> 48;
1030 const auto sectionOffset = size_t(buffer.sectionOffset & 0xFFFFFFFFFFFFull);
1031 if (rctx.header->sectionCount <= sectionIx) {
1032 LOG_ERROR(logger, "Illegal section index %u (section count is %u)", unsigned(sectionIx), rctx.header->sectionCount);
1033 return false;
1034 }
1035 const auto & sectionInfo = rctx.sectionInfo[sectionIx];
1036 if (sectionInfo.uncompressedSize < sectionOffset + buffer.size) {
1037 LOG_ERROR(logger, "Buffer out of bounds");
1038 return false;
1039 }
1040 }
1041
1042 for (uint32_t i = 0; i < rctx.textures.count; i++) {
1043 const auto & tex = rctx.textures[i];
1044 if (rctx.buffers.count <= tex.buffer) { LOG_ERROR(logger, "Texture has illegal buffer index %u", tex.buffer); return false; }
1045 auto & buf = rctx.buffers[tex.buffer];
1046 if (buf.size < tex.offset + tex.size) { LOG_ERROR(logger, "Texture buffer view outside buffer"); return false; }
1047 if (isValidIndex(tex.name) && rctx.strings.count <= tex.name) { LOG_ERROR(logger, "Illegal name string %u", tex.name); return false; }
1048 if (0x10000 < tex.width) { LOG_ERROR(logger, "Suspicious texture width %u", tex.width); return false; }
1049 if (0x10000 < tex.height) { LOG_ERROR(logger, "Suspicious texture height %u", tex.height); return false; }
1050 if (0x10000 < tex.depth) { LOG_ERROR(logger, "Suspicious texture depth %u", tex.depth); return false; }
1051 if (6 < tex.faces) { LOG_ERROR(logger, "Suspicious texture faces %u", tex.faces); return false; }
1052 if (0x10000 < tex.layers) { LOG_ERROR(logger, "Suspicious texture layers %u", tex.layers); return false; }
1053 if (CogsBin3::Format::EnumSize <= tex.format) { LOG_ERROR(logger, "Illegal format %u", unsigned(tex.format)); return false; }
1054 if (CogsBin3::TextureEncoding::EnumSize <= tex.encoding) { LOG_ERROR(logger, "Illegal encoding %u", unsigned(tex.encoding)); return false; }
1055 }
1056
1057 for (uint32_t i = 0; i < rctx.vertexStreams.count; i++) {
1058 const auto & dataStream = rctx.vertexStreams[i];
1059 if (rctx.buffers.count <= dataStream.buffer) {
1060 LOG_ERROR(logger, "Vertex stream has illegal buffer index %u (count is %zu)", dataStream.buffer, rctx.buffers.count);
1061 return false;
1062 }
1063 if (dataStream.vertexDataType < VertexDataType::LastVertexType) {
1064 if (!isValidIndex(dataStream.firstAttribute)) {
1065 LOG_ERROR(logger, "Vertex stream without attributes");
1066 return false;
1067 }
1068 if (rctx.vertexAttributes.count <= dataStream.firstAttribute || rctx.vertexAttributes.count < dataStream.firstAttribute + dataStream.attributeCount) {
1069 LOG_ERROR(logger, "Vertex stream has illegal vertex attribute indices first=%u count=%u (total count is %zu)",
1070 dataStream.firstAttribute, dataStream.attributeCount, rctx.vertexAttributes.count);
1071 return false;
1072 }
1073 } else if (dataStream.vertexDataType == VertexDataType::Indexes) {
1074 if (dataStream.stride != 4 && dataStream.stride != 2) {
1075 LOG_ERROR(logger, "Index stream has unsupported stride.");
1076 return false;
1077 }
1078 }
1079
1080 if (!isValidIndex(dataStream.buffer)) {
1081 LOG_ERROR(logger, "Data stream without valid buffer.");
1082 return false;
1083 }
1084 }
1085
1086 for (uint32_t i = 0; i < rctx.animTracks.count; i++) {
1087 const auto & track = rctx.animTracks[i];
1088 if (kMaxBones <= track.boneIndex) {
1089 LOG_ERROR(logger, "Anim track has illegal bone index %u (kMaxBones is %zu)", track.boneIndex, kMaxBones);
1090 return false;
1091 }
1092 if (rctx.buffers.count <= track.buffer) {
1093 LOG_ERROR(logger, "Anim track has illegal buffer index %u.", track.buffer);
1094 return false;
1095 }
1096 const auto & buffer = rctx.buffers[track.buffer];
1097 if (buffer.size < track.translationOffset + 4 * sizeof(float)*track.translationCount) {
1098 LOG_ERROR(logger, "Anim track translation buffer view is outside buffer.");
1099 return false;
1100 }
1101 if (buffer.size < track.rotationOffset + 5 * sizeof(float)*track.rotationCount) {
1102 LOG_ERROR(logger, "Anim track translation buffer view is outside buffer.");
1103 return false;
1104 }
1105 if (buffer.size < track.scaleOffset + 4 * sizeof(float)*track.scaleCount) {
1106 LOG_ERROR(logger, "Anim track translation buffer view is outside buffer.");
1107 return false;
1108 }
1109 }
1110
1111 for (uint32_t i = 0; i < rctx.meshes.count; i++) {
1112 const auto & mesh = rctx.meshes[i];
1113 if (isValidIndex(mesh.name) && rctx.strings.count <= mesh.name) {
1114 LOG_ERROR(logger, "Mesh name has illegal string index %u (string count is %zu)", mesh.name, rctx.strings.count);
1115 return false;
1116 }
1117 if (isValidIndex(mesh.boundingBox) && rctx.boundingBoxes.count <= mesh.boundingBox) {
1118 LOG_ERROR(logger, "Mesh name has illegal boundingBox index %u (count is %zu)", mesh.boundingBox, rctx.boundingBoxes.count);
1119 return false;
1120 }
1121 if (isValidIndex(mesh.firstStream) && rctx.vertexStreams.count <= mesh.firstStream) {
1122 LOG_ERROR(logger, "Mesh name has illegal data stream index %u (count is %zu)", mesh.firstStream, rctx.vertexStreams.count);
1123 return false;
1124 }
1125 }
1126
1127 if (modelIx < rctx.models.count) {
1128 populateModel(rctx, loadInfo.handle, modelIx);
1129 }
1130 else {
1131 LOG_ERROR(logger, "Invalid model index %u (model count=%zu)", modelIx, rctx.models.count);
1132 rctx.success = false;
1133 }
1134
1135 if (rctx.group != NoTask) {
1136 // wait here before we release the memory mapping.
1137 context->taskManager->wait(rctx.group);
1138 }
1139 loadCount--;
1140
1141 return rctx.success;
1142}
1143
1144// #pragma optimize( "", on )
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
static constexpr TaskQueueId ResourceQueue
Resource task queue.
Definition: TaskManager.h:232
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....
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
@ InstanceData
Per instance data.
@ VertexData
Per vertex data.
@ Position
Position semantic.
@ Tangent
Tangent semantic.
@ Normal
Normal semantic.
@ InstanceMatrix
Instance matrix semantic.
@ InstanceVector
Instance vector semantic.
@ Color
Color semantic.
@ TextureCoordinate
Texture coordinate semantic.
uint32_t boneIndex
Specifies which bone of a skeleton that is animated by this track.
Definition: Animation.h:38
@ VertexBuffer
Buffer can be bound as vertex buffer.
Definition: Buffer.h:40
@ IndexBuffer
Buffer can be bound as index buffer.
Definition: Buffer.h:42
uint32_t name
Offset into strings subsection of current section.
Definition: CogsBin3.h:460
Generic blob of data.
Definition: CogsBin3.h:281
uint32_t mesh
Mesh of node, offset into Meshes subsection.
Definition: CogsBin3.h:471
uint32_t name
Name of node, offset into Strings subsection.
Definition: CogsBin3.h:470
uint32_t transform
Transform of node, offset into Transforms subsection.
Definition: CogsBin3.h:474
PrimitiveType primitiveType
Primitive type to use for this node.
Definition: CogsBin3.h:475
uint32_t materialInstance
Material instance of node, offset into MaterialInstances subsection.
Definition: CogsBin3.h:472
uint32_t vertexCount
Number of vertices to draw.
Definition: CogsBin3.h:477
uint32_t parent
Parent node, offset into Nodes subsection.
Definition: CogsBin3.h:469
uint32_t boundingBox
Bounding box of node, offset into BoundingBoxes subsection.
Definition: CogsBin3.h:473
uint32_t vertexFirst
First vertex of mesh to draw.
Definition: CogsBin3.h:476
@ StreamsChanged
One or more of the data streams in the mesh changed.
Definition: Mesh.h:59
@ IndexesChanged
The index data of the mesh changed.
Definition: Mesh.h:61
@ Indexed
The mesh should be drawn indexed, using index data to order the triangle vertexes.
Definition: Mesh.h:65
@ ClockwiseWinding
The mesh uses clockwise winding order for it's triangles as opposed to the counter-clockwise default.
Definition: Mesh.h:63
static constexpr uint16_t toFlag(VertexDataType::EVertexDataType type)
Convert the given type index to a flag.
Definition: Mesh.h:577
Resource handle base class handling reference counting of resources derived from ResourceBase.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
std::string resourcePath
Resource path. Used to locate resource.
std::string resourceName
Desired resource name. If no name is given, a default name will be chosen.
ResourceId resourceId
Unique resource identifier. Must be unique among resources of the same kind.
ResourceHandleBase handle
Handle to resource structure for holding actual resource data.
std::vector< uint8_t > resourceData
Resource load data.
Task id struct used to identify unique Task instances.
Definition: TaskManager.h:20
EVertexDataType
Contains data types.
Definition: Mesh.h:26
EPrimitiveType
Primitive type enumeration.
Definition: Common.h:114
@ LineStrip
Line strip.
Definition: Common.h:122
@ LineStripAdjacency
Line strip with adjacency.
Definition: Common.h:132
@ LineList
List of lines.
Definition: Common.h:120
@ TriangleStrip
Triangle strip.
Definition: Common.h:118
@ TriangleListAdjacency
List of triangles with adjacency.
Definition: Common.h:126
@ PointList
List of points.
Definition: Common.h:124
@ TriangleStripAdjacency
Triangle strip with adjacency.
Definition: Common.h:128
@ LineListAdjacency
List of lines with adjacency.
Definition: Common.h:130
@ TriangleList
List of triangles.
Definition: Common.h:116