Cogs.Core
CogsBin3Writer.cpp
1#include "ModelWriter.h"
2#include "Context.h"
3#include "CogsBin3.h"
4
5#include "Components/Core/TransformComponent.h"
6#include "Components/Core/SceneComponent.h"
7#include "Components/Core/AnimationComponent.h"
8#include "Components/Core/MeshComponent.h"
9#include "Components/Core/MeshRenderComponent.h"
10
11#include "Systems/Core/TransformSystem.h"
12#include "Systems/Core/RenderSystem.h"
13
14#include "Resources/Buffer.h"
15#include "Resources/Mesh.h"
16#include "Resources/MaterialInstance.h"
17#include "Resources/Texture.h"
18#include "Resources/Model.h"
19#include "Resources/Animation.h"
20
21#include "Resources/DefaultMaterial.h"
22
23#include "Foundation/HashSequence.h"
24#include "Foundation/Logging/Logger.h"
25#include "Foundation/Platform/IO.h"
26#include "Foundation/Platform/Timer.h"
27
28#include "zstd.h"
29
30#ifndef EMSCRIPTEN
31#define STB_IMAGE_WRITE_IMPLEMENTATION
32#endif
33#include "stb_image_write.h"
34
35#include <map>
36#include <stdio.h>
37
38#ifndef EMSCRIPTEN
39
40//#define MAKE_DEBUGABLE
41
42#if defined(_WIN32) && defined(MAKE_DEBUGABLE)
43#pragma optimize( "", off )
44#endif
45
46using namespace Cogs::Geometry;
47
48namespace
49{
50 using namespace Cogs::Core;
51
52 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("CogsBin3Writer");
53
54
55 template<typename T>
56 uint32_t nextIndex(T& t)
57 {
58 auto ix = t.size();
59 if (static_cast<size_t>(std::numeric_limits<uint32_t>::max()) < ix) {
60 LOG_ERROR(logger, "Index out of range. Model data too large.");
61 }
62
63 return static_cast<uint32_t>(ix);
64 }
65
66 struct WSubSection
67 {
68 uint64_t offset;
69 uint32_t size;
70 CogsBin3::SubSectionType type;
71 };
72
73 struct WSection
74 {
75 CogsBin3::SectionType type;
76 uint32_t index;
77 uint64_t writePos;
78 uint64_t dataSize; // # bytes of data not in a subsection.
79 std::vector<uint8_t> contents;
80 std::vector<WSubSection> subSections;
81 };
82
83 struct WVertexFormatRec
84 {
85 const Cogs::VertexFormat* format;
86 uint32_t index;
87 };
88
89 struct WBufferContext
90 {
92 uint32_t alignment = 0;
93 };
94
95 struct WriteContext
96 {
97 uint32_t numVertes = 0;
98 uint32_t numIndexes = 0;
99 std::string fileName;
100 WriteModelSettings settings;
101 WriteModelFlags flags = WriteModelFlags::NONE;
102
103 Animation* currentAnimation = nullptr;
104
105 std::vector<std::unique_ptr<WSection>> sections; // FIXME: use a more clever container. unique ptr so mem refs is not invalidated by resizes.
106
107 std::vector<const TranslationKey*> animTrackTranslateSource;
108 std::vector<const RotationKey*> animTrackRotationSource;
109 std::vector<const ScaleKey*> animTrackScaleSource;
110 std::vector<WBufferContext> bufferContexts;
111
112 std::vector<uint8_t> stringData;
113 std::vector<uint8_t> propertyData;
114 std::vector<Cogs::Core::CogsBin3::Buffer> buffers;
115 std::vector<Cogs::Core::CogsBin3::Texture> textures;
116 std::vector<Cogs::Core::CogsBin3::String> strings;
117 std::vector<Cogs::Core::CogsBin3::Property> properties;
118 std::vector<Cogs::Core::CogsBin3::MaterialInstance> materialInstances;
119 std::vector<Cogs::Core::CogsBin3::Transform> transforms;
120 std::vector<Cogs::Geometry::BoundingBox> boundingBoxes;
121 std::vector<Cogs::Core::CogsBin3::Node> nodes;
122 std::vector<Cogs::Core::CogsBin3::Mesh> meshes;
123 std::vector<Cogs::Core::CogsBin3::VertexStream> vertexStreams;
124 std::vector<Cogs::Core::CogsBin3::VertexAttribute> vertexAttributes;
125 std::vector<Cogs::Core::CogsBin3::Bone> bones;
126 std::vector<Cogs::Core::CogsBin3::Skeleton> skeletons;
127 std::vector<Cogs::Core::CogsBin3::Model> models;
128 std::vector<Cogs::Core::CogsBin3::AnimTrack> animTracks;
129 std::vector<Cogs::Core::CogsBin3::AnimClip> animClips;
130
131 std::vector<CogsBin3::SectionInfo> sectionInfos;
132
133 std::map<const Cogs::Core::Texture*, uint32_t> knownTexturesByPointer;
134 std::map<const Cogs::Core::MaterialInstance*, uint32_t> knownMaterialInstancesByPointer;
135 std::map<size_t, uint32_t> knownMaterialInstancesByHash;
136 std::map<size_t, uint32_t> propByHash;
137 std::map<size_t, uint32_t> stringByHash;
138 std::map<size_t, uint32_t> transformByHash;
139 std::map<size_t, uint32_t> boundingBoxByHash;
140 std::map<const Cogs::Core::Mesh*, uint32_t> meshByPointer;
141 std::map<const Cogs::Core::BufferResource*, uint32_t> bufferByPointer;
142 std::map<size_t, WVertexFormatRec> vertexFormatByHash;
143 };
144
145 template<typename T, typename S>
146 T checkedCast(const S v)
147 {
148 assert(v <= std::numeric_limits<T>::max());
149 return T(v);
150 }
151
152 inline uint64_t align(uint64_t x)
153 {
154 const uint64_t m = CogsBin3::CogsSectionAlignment - 1;
155 return (x + m) & ~m;
156 }
157
158 CogsBin3::PrimitiveType translatePrimitiveType(WriteContext& /*wctx*/, Cogs::PrimitiveType primitiveType)
159 {
160 switch (primitiveType) {
161 case Cogs::PrimitiveType::TriangleList: return CogsBin3::PrimitiveType::TriangleList; break;
162 case Cogs::PrimitiveType::TriangleStrip: return CogsBin3::PrimitiveType::TriangleStrip; break;
163 case Cogs::PrimitiveType::LineList: return CogsBin3::PrimitiveType::LineList; break;
164 case Cogs::PrimitiveType::LineStrip: return CogsBin3::PrimitiveType::LineStrip; break;
165 case Cogs::PrimitiveType::PointList: return CogsBin3::PrimitiveType::PointList; break;
166 case Cogs::PrimitiveType::TriangleListAdjacency: return CogsBin3::PrimitiveType::TriangleListAdjacency; break;
167 case Cogs::PrimitiveType::TriangleStripAdjacency: return CogsBin3::PrimitiveType::TriangleStripAdjacency; break;
168 case Cogs::PrimitiveType::LineListAdjacency: return CogsBin3::PrimitiveType::LineListAdjacency; break;
169 case Cogs::PrimitiveType::LineStripAdjacency: return CogsBin3::PrimitiveType::LineStripAdjacency; break;
170 case Cogs::PrimitiveType::ControlPoint1PatchList: return CogsBin3::PrimitiveType::ControlPoint1PatchList; break;
171 case Cogs::PrimitiveType::ControlPoint2PatchList: return CogsBin3::PrimitiveType::ControlPoint2PatchList; break;
172 case Cogs::PrimitiveType::ControlPoint3PatchList: return CogsBin3::PrimitiveType::ControlPoint3PatchList; break;
173 case Cogs::PrimitiveType::ControlPoint4PatchList: return CogsBin3::PrimitiveType::ControlPoint4PatchList; break;
174 default:
175 break;
176 }
177 assert(false && "Illegal primitive type encountered");
178 return CogsBin3::PrimitiveType::TriangleList;
179 }
180
181 CogsBin3::Format translateFormat(Cogs::TextureFormat format)
182 {
183 switch (format) {
184 case Cogs::TextureFormat::Unknown: return CogsBin3::Format::Unknown; break;
185 case Cogs::TextureFormat::R8_UNORM: return CogsBin3::Format::R8_UNORM; break;
186 case Cogs::TextureFormat::R8G8_UNORM: return CogsBin3::Format::R8G8_UNORM; break;
187 case Cogs::TextureFormat::R8G8B8_UNORM: return CogsBin3::Format::R8G8B8_UNORM; break;
188 case Cogs::TextureFormat::R8G8B8A8_UNORM: return CogsBin3::Format::R8G8B8A8_UNORM; break;
189 case Cogs::TextureFormat::R16_UNORM: return CogsBin3::Format::R16_UNORM; break;
190 case Cogs::TextureFormat::R16G16_UNORM: return CogsBin3::Format::R16G16_UNORM; break;
191 case Cogs::TextureFormat::R16G16B16_UNORM: return CogsBin3::Format::R16G16B16_UNORM; break;
192 case Cogs::TextureFormat::R16G16B16A16_UNORM: return CogsBin3::Format::R16G16B16A16_UNORM; break;
193 case Cogs::TextureFormat::R8_SNORM: return CogsBin3::Format::R8_SNORM; break;
194 case Cogs::TextureFormat::R8G8_SNORM: return CogsBin3::Format::R8G8_SNORM; break;
195 case Cogs::TextureFormat::R8G8B8_SNORM: return CogsBin3::Format::R8G8B8_SNORM; break;
196 case Cogs::TextureFormat::R8G8B8A8_SNORM: return CogsBin3::Format::R8G8B8A8_SNORM; break;
197 case Cogs::TextureFormat::R16_SNORM: return CogsBin3::Format::R16_SNORM; break;
198 case Cogs::TextureFormat::R16G16_SNORM: return CogsBin3::Format::R16G16_SNORM; break;
199 case Cogs::TextureFormat::R16G16B16_SNORM: return CogsBin3::Format::R16G16B16_SNORM; break;
200 case Cogs::TextureFormat::R16G16B16A16_SNORM: return CogsBin3::Format::R16G16B16A16_SNORM; break;
201 case Cogs::TextureFormat::R8_UINT: return CogsBin3::Format::R8_UINT; break;
202 case Cogs::TextureFormat::R8G8_UINT: return CogsBin3::Format::R8G8_UINT; break;
203 case Cogs::TextureFormat::R8G8B8_UINT: return CogsBin3::Format::R8G8B8_UINT; break;
204 case Cogs::TextureFormat::R8G8B8A8_UINT: return CogsBin3::Format::R8G8B8A8_UINT; break;
205 case Cogs::TextureFormat::R16_UINT: return CogsBin3::Format::R16_UINT; break;
206 case Cogs::TextureFormat::R16G16_UINT: return CogsBin3::Format::R16G16_UINT; break;
207 case Cogs::TextureFormat::R16G16B16_UINT: return CogsBin3::Format::R16G16B16_UINT; break;
208 case Cogs::TextureFormat::R16G16B16A16_UINT: return CogsBin3::Format::R16G16B16A16_UINT; break;
209 case Cogs::TextureFormat::R32_UINT: return CogsBin3::Format::R32_UINT; break;
210 case Cogs::TextureFormat::R32G32_UINT: return CogsBin3::Format::R32G32_UINT; break;
211 case Cogs::TextureFormat::R32G32B32_UINT: return CogsBin3::Format::R32G32B32_UINT; break;
212 case Cogs::TextureFormat::R32G32B32A32_UINT: return CogsBin3::Format::R32G32B32A32_UINT; break;
213 case Cogs::TextureFormat::R8_SINT: return CogsBin3::Format::R8_SINT; break;
214 case Cogs::TextureFormat::R8G8_SINT: return CogsBin3::Format::R8G8_SINT; break;
215 case Cogs::TextureFormat::R8G8B8_SINT: return CogsBin3::Format::R8G8B8_SINT; break;
216 case Cogs::TextureFormat::R8G8B8A8_SINT: return CogsBin3::Format::R8G8B8A8_SINT; break;
217 case Cogs::TextureFormat::R16_SINT: return CogsBin3::Format::R16_SINT; break;
218 case Cogs::TextureFormat::R16G16_SINT: return CogsBin3::Format::R16G16_SINT; break;
219 case Cogs::TextureFormat::R16G16B16_SINT: return CogsBin3::Format::R16G16B16_SINT; break;
220 case Cogs::TextureFormat::R16G16B16A16_SINT: return CogsBin3::Format::R16G16B16A16_SINT; break;
221 case Cogs::TextureFormat::R32_SINT: return CogsBin3::Format::R32_SINT; break;
222 case Cogs::TextureFormat::R32G32_SINT: return CogsBin3::Format::R32G32_SINT; break;
223 case Cogs::TextureFormat::R32G32B32_SINT: return CogsBin3::Format::R32G32B32_SINT; break;
224 case Cogs::TextureFormat::R32G32B32A32_SINT: return CogsBin3::Format::R32G32B32A32_SINT; break;
225 case Cogs::TextureFormat::R16_FLOAT: return CogsBin3::Format::R16_FLOAT; break;
226 case Cogs::TextureFormat::R16G16_FLOAT: return CogsBin3::Format::R16G16_FLOAT; break;
227 case Cogs::TextureFormat::R16G16B16_FLOAT: return CogsBin3::Format::R16G16B16_FLOAT; break;
228 case Cogs::TextureFormat::R16G16B16A16_FLOAT: return CogsBin3::Format::R16G16B16A16_FLOAT; break;
229 case Cogs::TextureFormat::R32_FLOAT: return CogsBin3::Format::R32_FLOAT; break;
230 case Cogs::TextureFormat::R32G32_FLOAT: return CogsBin3::Format::R32G32_FLOAT; break;
231 case Cogs::TextureFormat::R32G32B32_FLOAT: return CogsBin3::Format::R32G32B32_FLOAT; break;
232 case Cogs::TextureFormat::R32G32B32A32_FLOAT: return CogsBin3::Format::R32G32B32A32_FLOAT; break;
233 case Cogs::TextureFormat::D16_UNORM: return CogsBin3::Format::D16_UNORM; break;
234 case Cogs::TextureFormat::D24_UNORM: return CogsBin3::Format::D24_UNORM; break;
235 case Cogs::TextureFormat::D24S8_UNORM: return CogsBin3::Format::D24S8_UNORM; break;
236 case Cogs::TextureFormat::D32_FLOAT: return CogsBin3::Format::D32_FLOAT; break;
237 case Cogs::TextureFormat::R32_TYPELESS: return CogsBin3::Format::R32_TYPELESS; break;
238 case Cogs::TextureFormat::R16_TYPELESS: return CogsBin3::Format::R16_TYPELESS; break;
239 case Cogs::TextureFormat::R8T: return CogsBin3::Format::R8T; break;
240 case Cogs::TextureFormat::R8G8T: return CogsBin3::Format::R8G8T; break;
241 case Cogs::TextureFormat::R8G8B8T: return CogsBin3::Format::R8G8B8T; break;
242 case Cogs::TextureFormat::R8G8B8A8T: return CogsBin3::Format::R8G8B8A8T; break;
243 case Cogs::TextureFormat::B8G8R8: return CogsBin3::Format::B8G8R8; break;
244 case Cogs::TextureFormat::B8G8R8A8: return CogsBin3::Format::B8G8R8A8; break;
245 case Cogs::TextureFormat::A8_UNORM: return CogsBin3::Format::A8_UNORM; break;
246 case Cogs::TextureFormat::BC1_TYPELESS: return CogsBin3::Format::BC1_TYPELESS; break;
247 case Cogs::TextureFormat::BC1_UNORM: return CogsBin3::Format::BC1_UNORM; break;
248 case Cogs::TextureFormat::BC1_UNORM_SRGB: return CogsBin3::Format::BC1_UNORM_SRGB; break;
249 case Cogs::TextureFormat::BC2_TYPELESS: return CogsBin3::Format::BC2_TYPELESS; break;
250 case Cogs::TextureFormat::BC2_UNORM: return CogsBin3::Format::BC2_UNORM; break;
251 case Cogs::TextureFormat::BC2_UNORM_SRGB: return CogsBin3::Format::BC2_UNORM_SRGB; break;
252 case Cogs::TextureFormat::BC3_TYPELESS: return CogsBin3::Format::BC3_TYPELESS; break;
253 case Cogs::TextureFormat::BC3_UNORM: return CogsBin3::Format::BC3_UNORM; break;
254 case Cogs::TextureFormat::BC3_UNORM_SRGB: return CogsBin3::Format::BC3_UNORM_SRGB; break;
255 case Cogs::TextureFormat::BC4_TYPELESS: return CogsBin3::Format::BC4_TYPELESS; break;
256 case Cogs::TextureFormat::BC4_UNORM: return CogsBin3::Format::BC4_UNORM; break;
257 case Cogs::TextureFormat::BC4_SNORM: return CogsBin3::Format::BC4_SNORM; break;
258 case Cogs::TextureFormat::BC5_TYPELESS: return CogsBin3::Format::BC5_TYPELESS; break;
259 case Cogs::TextureFormat::BC5_UNORM: return CogsBin3::Format::BC5_UNORM; break;
260 case Cogs::TextureFormat::BC5_SNORM: return CogsBin3::Format::BC5_SNORM; break;
261 case Cogs::TextureFormat::R8G8B8A8_UNORM_SRGB: return CogsBin3::Format::R8G8B8A8_UNORM_SRGB; break;
262 default:
263 assert(false && "Illegal format");
264 break;
265 }
266 return CogsBin3::Format::Unknown;
267 }
268
269 WSection& getSection(WriteContext& wctx, CogsBin3::SectionType type, uint16_t index, bool create=true)
270 {
271 if (wctx.sections.size() <= index) wctx.sections.resize((size_t)index + 1);
272 if (!wctx.sections[index]) {
273 assert(create && "Trying to retrieve non-existing section");
274 wctx.sections[index] = std::make_unique<WSection>(WSection{ type, index, 0, 0, {}, {} });
275 }
276 auto & section = *wctx.sections[index].get();
277 section.type = section.type | type;
278
279 return section;
280 }
281
282 uint32_t addMetaSubsection(WriteContext& /*wctx*/, WSection& section, CogsBin3::SubSectionType type, size_t size)
283 {
284 assert(section.contents.empty() && "Cannot add metasubsections after data payload has been added.");
285 assert(size < std::numeric_limits<uint32_t>::max());
286
287 auto index = nextIndex(section.subSections);
288 section.subSections.push_back(WSubSection{ ~0u, uint32_t(size), type });
289 return index;
290 }
291
292 uint32_t newNode(WriteContext& wctx)
293 {
294 uint32_t index = nextIndex(wctx.nodes);
295 auto & node = wctx.nodes.emplace_back();
296 node.parent = ~0u;
297 node.name = ~0u;
298 node.mesh = ~0u;
299 node.materialInstance = ~0u;
300 node.transform = ~0u;
301 node.boundingBox = ~0u;
302 node.primitiveType = CogsBin3::PrimitiveType::TriangleList;
303 node.vertexFirst = 0;
304 node.vertexCount = 0;
305 return index;
306 }
307
308 uint32_t recordTransform(WriteContext& wctx, const glm::mat4& M)
309 {
310 glm::mat4 I;
311 if (std::memcmp(glm::value_ptr(I), glm::value_ptr(M), sizeof(M)) == 0) {
312 //LOG_DEBUG(logger, "Identity matrix.");
313 return ~0u;
314 }
315
316 // assume affine transform matrix
317 const auto * m = glm::value_ptr(M);
319 m[0], m[1], m[2],
320 m[4], m[5], m[6],
321 m[8], m[9], m[10],
322 m[12], m[13], m[14]
323 };
324 size_t hashValue = T.hash();
325
326 if (auto it = wctx.transformByHash.find(hashValue); it != wctx.transformByHash.end()) {
327 if (std::memcmp(&wctx.transforms[it->second], &T, sizeof(T)) == 0) {
328 //LOG_DEBUG(logger, "Found duplicate transform.");
329 return it->second;
330 }
331 }
332
333 auto index = nextIndex(wctx.transforms);
334 wctx.transforms.emplace_back(T);
335 wctx.transformByHash[hashValue] = index;
336 return index;
337 }
338
339 uint32_t recordBoundingBox(WriteContext& wctx, const Cogs::Geometry::BoundingBox& bbox)
340 {
341 if (isEmpty(bbox)) {
342 //LOG_DEBUG(logger, "Empty bbox.\n");
343 return ~0u;
344 }
345
346 // FIXME (chrisdy): Not really sure if this is worthwhile
347 size_t hashValue = bbox.hash();
348 if (auto it = wctx.boundingBoxByHash.find(hashValue); it != wctx.boundingBoxByHash.end()) {
349 if (std::memcmp(&wctx.boundingBoxes[it->second], &bbox, sizeof(bbox)) == 0) {
350 LOG_DEBUG(logger, "Found duplicate bounding box.");
351 return it->second;
352 }
353 }
354
355 auto index = nextIndex(wctx.boundingBoxes);
356 wctx.boundingBoxes.emplace_back(bbox);
357 wctx.transformByHash[hashValue] = index;
358 return index;
359 }
360
361 size_t getMaterialInstanceHash(MaterialInstance * material)
362 {
363 size_t hashValue = material->getName().hash();
364
365 if (material->material->getName() == "DefaultMaterial") {
366 hashValue = Cogs::hash(material->getVec4Property(DefaultMaterial::DiffuseColor), hashValue);
367 hashValue = Cogs::hash(material->getVec3Property(DefaultMaterial::SpecularColor), hashValue);
368 hashValue = Cogs::hash(material->getVec3Property(DefaultMaterial::EmissiveColor), hashValue);
369 hashValue = Cogs::hash(material->getFloatProperty(DefaultMaterial::SpecularPower), hashValue);
370 hashValue = Cogs::hash(reinterpret_cast<intptr_t>(material->getTextureProperty(DefaultMaterial::DiffuseMap).texture.handle.get()), hashValue);
371 }
372 else {
373 hashValue = Cogs::hash(reinterpret_cast<intptr_t>(material));
374 }
375 return hashValue;
376 }
377
378 uint32_t recordString(WriteContext& wctx, Cogs::StringView string)
379 {
380 if (string.empty()) return ~0u;
381
382 auto hash = string.hash();
383 if (auto it = wctx.stringByHash.find(hash); it != wctx.stringByHash.end()) {
384 auto & str = wctx.strings[it->second];
385 if (string == Cogs::StringView((const char*)(wctx.stringData.data() + str.offset), str.length)) {
386 return it->second;
387 }
388 }
389
390 auto offset = wctx.stringData.size();
391 assert(offset < std::numeric_limits<uint32_t>::max());
392
393 auto length = string.length();
394 assert(length < std::numeric_limits<uint32_t>::max());
395
396 auto index = nextIndex(wctx.strings);
397 wctx.strings.emplace_back(CogsBin3::String{ uint32_t(offset), uint32_t(length) });
398
399 wctx.stringData.resize(offset + length);
400 std::memcpy(wctx.stringData.data() + offset, string.begin(), length);
401
402 wctx.stringByHash[hash] = index;
403 return index;
404 }
405
406 uint32_t recordAttribute(WriteContext& wctx, const Cogs::VertexElement& element)
407 {
408 assert(element.offset <= std::numeric_limits<uint16_t>::max());
409
410 auto index = nextIndex(wctx.vertexAttributes);
411
412 wctx.vertexAttributes.emplace_back(CogsBin3::VertexAttribute{});
413 auto & attribute = wctx.vertexAttributes.back();
414
415 attribute.offset = uint16_t(element.offset);
416 attribute.format = (CogsBin3::Format)element.format;
417
418 switch (element.semantic) {
419 case Cogs::ElementSemantic::Position: attribute.semantic = CogsBin3::Semantic::Position; break;
420 case Cogs::ElementSemantic::Normal: attribute.semantic = CogsBin3::Semantic::Normal; break;
421 case Cogs::ElementSemantic::Color: attribute.semantic = CogsBin3::Semantic::Color; break;
422 case Cogs::ElementSemantic::TextureCoordinate: attribute.semantic = CogsBin3::Semantic::TexCoord; break;
423 case Cogs::ElementSemantic::Tangent: attribute.semantic = CogsBin3::Semantic::Tangent; break;
424 case Cogs::ElementSemantic::InstanceVector: attribute.semantic = CogsBin3::Semantic::InstanceVector; break;
425 case Cogs::ElementSemantic::InstanceMatrix: attribute.semantic = CogsBin3::Semantic::InstanceMatrix; break;
426 default:
427 assert(false && "Unknown element.format");
428 }
429 assert(element.semanticIndex <= std::numeric_limits<uint8_t>::max());
430 attribute.semanticIndex = uint8_t(element.semanticIndex);
431
432 if (element.inputType == Cogs::InputType::VertexData) {
433 attribute.instanceStepRate = 0;
434 }
435 else {
436 attribute.instanceStepRate = uint16_t(std::min(1 + (uint32_t)element.instanceStep, unsigned(std::numeric_limits<uint16_t>::max())));
437 }
438 return index;
439 }
440
441 bool match(const Cogs::VertexFormat& a, const Cogs::VertexFormat& b)
442 {
443 if (a.elements.size() != b.elements.size()) return false;
444 for (size_t i = 0; i < a.elements.size(); i++) {
445 const auto & ae = a.elements[i];
446 const auto & be = b.elements[i];
447
448 if ((ae.offset != be.offset) ||
449 (ae.format != be.format) ||
450 (ae.semantic != be.semantic) ||
451 (ae.semanticIndex != be.semanticIndex) ||
452 (ae.inputType != be.inputType) ||
453 (ae.instanceStep != be.instanceStep))
454 {
455 return false;
456 }
457 }
458 return true;
459 }
460
461 uint32_t recordBuffer(WriteContext& wctx, uint64_t size, CogsBin3::BufferContentFlags flags, uint32_t alignment, uint16_t sectionIx)
462 {
463 assert(alignment);
464 assert(size);
465 assert(alignment <= CogsBin3::CogsSectionAlignment);
466
467 const auto lowerMask = uint64_t(alignment - 1);
468
469 auto & section = getSection(wctx, CogsBin3::SectionType::DATA, sectionIx);
470 section.dataSize = (section.dataSize + lowerMask)&(~lowerMask);
471 section.dataSize += size;
472
473 auto index = nextIndex(wctx.buffers);
474 wctx.buffers.emplace_back(CogsBin3::Buffer{});
475 wctx.bufferContexts.emplace_back();
476
477 auto & buffer = wctx.buffers.back();
478 buffer.sectionOffset = sectionIx;
479 //TODO: Review buffer size usage.
480 buffer.size = (uint32_t)size;
481 buffer.flags = flags;
482
483 wctx.bufferContexts[index].contents.resize(size, false);
484 wctx.bufferContexts[index].alignment = alignment;
485
486 return index;
487 }
488
489 uint8_t* getBufferPointer(WriteContext& wctx, uint32_t bufferIx, uint32_t offset, uint64_t size)
490 {
491 assert(bufferIx < wctx.buffers.size());
492 auto & bufferCtx = wctx.bufferContexts[bufferIx];
493 assert(offset + size <= bufferCtx.contents.size());
494 return (uint8_t*)bufferCtx.contents.data() + offset;
495 }
496
497 uint32_t recordBufferResource(WriteContext& wctx, BufferResource* srcBuffer, uint16_t sectionIx)
498 {
499 if (srcBuffer == nullptr || srcBuffer->empty()) {
500 return ~0u;
501 }
502
503 if (auto it = wctx.bufferByPointer.find(srcBuffer); it != wctx.bufferByPointer.end()) {
504 return it->second;
505 }
506
507 CogsBin3::BufferContentFlags flags = CogsBin3::BufferContentFlags::None;
508 if (srcBuffer->isVertexBuffer()) flags |= CogsBin3::BufferContentFlags::VertexData;
509 if (srcBuffer->isIndexBuffer()) flags |= CogsBin3::BufferContentFlags::IndexData;
510
511 constexpr uint32_t bufferStride = 16;
512 auto alignment = std::min(uint32_t(CogsBin3::CogsSectionAlignment), uint32_t(bufferStride)); // No point in aligning beyond a cache line.
513 uint32_t index = recordBuffer(wctx, srcBuffer->size(), flags, alignment, sectionIx);
514 std::memcpy(getBufferPointer(wctx, index, 0, srcBuffer->size()), srcBuffer->data(), srcBuffer->size());
515
516 wctx.bufferByPointer[srcBuffer] = index;
517
518 return index;
519 }
520
521 void recordVertexFormat(WriteContext& wctx, CogsBin3::VertexStream& stream, const DataStream& srcStream)
522 {
523 const auto & srcFormat = *Cogs::VertexFormats::getVertexFormat(srcStream.format);
524 assert(srcFormat.size <= std::numeric_limits<uint16_t>::max());
525 stream.stride = uint16_t(srcFormat.size);
526
527 assert(srcFormat.elements.size() < std::numeric_limits<uint32_t>::max());
528 stream.attributeCount = uint32_t(srcFormat.elements.size());
529
530 auto hash = Cogs::getHash(srcFormat);
531 assert(hash);
532 if (auto it = wctx.vertexFormatByHash.find(hash); it != wctx.vertexFormatByHash.end() && match(*it->second.format, srcFormat)) {
533 stream.firstAttribute = it->second.index;
534 }
535 else {
536 stream.firstAttribute = recordAttribute(wctx, srcFormat.elements[0]);
537 for (size_t i = 1; i < srcFormat.elements.size(); i++) {
538 auto ix = recordAttribute(wctx, srcFormat.elements[i]);
539 assert(stream.firstAttribute + i == ix);
540 }
541 wctx.vertexFormatByHash[hash] = WVertexFormatRec{ &srcFormat, stream.firstAttribute };
542 }
543 }
544
545 uint32_t recordVertexStream(WriteContext& wctx, const DataStream& srcStream, uint16_t sectionIx)
546 {
547 assert(srcStream.size() <= std::numeric_limits<uint32_t>::max());
548
549 auto index = nextIndex(wctx.vertexStreams);
550 auto & stream = wctx.vertexStreams.emplace_back(CogsBin3::VertexStream{ 0, 0, 0, 0, 0, 0, 0, 0 });
551 stream.firstAttribute = ~0u;
552 stream.vertexDataType = srcStream.type;
553 stream.encoding = 0;
554
555 if (srcStream.format) {
556 recordVertexFormat(wctx, stream, srcStream);
557 } else {
558 stream.stride = (uint16_t)srcStream.stride;
559 }
560
561 assert((size_t)stream.count * (size_t)stream.stride <= srcStream.size());
562
563 stream.count = srcStream.numElements;
564 stream.buffer = recordBufferResource(wctx, srcStream.buffer.resolve(), sectionIx);
565 stream.offset = srcStream.offset;
566
567 return index;
568 }
569
570 uint32_t recordMesh(WriteContext& wctx, Mesh* srcMesh, uint16_t section)
571 {
572 if (srcMesh == nullptr || srcMesh->streams.empty()) {
573 return ~0u;
574 }
575
576 if (auto it = wctx.meshByPointer.find(srcMesh); it != wctx.meshByPointer.end()) {
577 LOG_DEBUG(logger, "Duplicate mesh, recycling.");
578 return it->second;
579 }
580
581 uint32_t firstStream = 0;
582 uint32_t streamCount = 0;
583 for (size_t i = 0; i < srcMesh->streams.size(); i++) {
584 if (srcMesh->streams[i].size() != 0) {
585 uint32_t streamIx = recordVertexStream(wctx, srcMesh->streams[i], section);
586 if (streamCount == 0) {
587 firstStream = streamIx;
588 }
589 assert(firstStream + streamCount == streamIx);
590 streamCount++;
591 }
592 }
593
594 auto index = nextIndex(wctx.meshes);
595 auto & mesh = wctx.meshes.emplace_back();
596 mesh.name = recordString(wctx, srcMesh->getName());
597 mesh.firstStream = firstStream;
598 mesh.streamCount = (uint8_t)streamCount;
599 mesh.boundingBox = recordBoundingBox(wctx, srcMesh->boundingBox);
600 mesh.flags =
601 (srcMesh->isCCW() ? CogsBin3::MeshFlags::None : CogsBin3::MeshFlags::Clockwise) |
602 (srcMesh->isMeshFlagSet(MeshFlags::Skinned) ? CogsBin3::MeshFlags::Skinned : CogsBin3::MeshFlags::None);
603
604 mesh.primitiveType = translatePrimitiveType(wctx, srcMesh->primitiveType);
605
606 wctx.meshByPointer[srcMesh] = index;
607
608 return index;
609 }
610
611 template<typename T>
612 uint32_t recordProperty(WriteContext& wctx, const Cogs::StringView& key, CogsBin3::PropertyValueType valueType, const T & value)
613 {
614 auto keyIx = recordString(wctx, key);
615
616 // TODO (chrisdy): Check if this caching is worthwhile in practice.
617 size_t hash = Cogs::hashSequence(keyIx, valueType, value);
618 if (auto it = wctx.propByHash.find(hash); it != wctx.propByHash.end()) {
619 auto & p = wctx.properties[it->second];
620 if (p.keyType == CogsBin3::PropertyKeyType::String &&
621 p.key == keyIx &&
622 p.valueType == valueType)
623 {
624 if constexpr (sizeof(T) <= sizeof(uint32_t)) {
625 if (*(T*)(&p.value) == value) {
626 //LOG_DEBUG(logger, "Found duplicate property (small), recycling.");
627 return it->second;
628 }
629 }
630 else {
631 if (*(T*)(wctx.propertyData.data() + p.value) == value) {
632 //LOG_DEBUG(logger, "Found duplicate property (large), recycling.");
633 return it->second;
634 }
635 }
636 }
637 }
638
639 auto index = nextIndex(wctx.properties);
640 wctx.properties.emplace_back(CogsBin3::Property{ CogsBin3::PropertyKeyType::String, valueType, keyIx, ~0u });
641 auto & prop = wctx.properties.back();
642
643 if constexpr (sizeof(T) <= sizeof(uint32_t)) { // values that fit inside the index are stored inline
644 *(T*)(&prop.value) = value;
645 }
646 else {
647 constexpr auto align = alignof(T);
648 static_assert(align != 0 && (align & (align - 1)) == 0 && "alignment is not a power of two");
649 const auto mask = align - 1;
650 auto offset = (wctx.propertyData.size() + mask) & ~mask; // pad to natural alignment
651 assert(offset < std::numeric_limits<uint32_t>::max());
652 prop.value = uint32_t(offset);
653 wctx.propertyData.resize(wctx.propertyData.size() + sizeof(value));
654 *(T*)(wctx.propertyData.data() + offset) = value;
655 }
656 return index;
657 }
658
659 uint32_t recordStringBasedProperty(WriteContext& wctx, const Cogs::StringView& key, CogsBin3::PropertyValueType valueType, const Cogs::StringView& value)
660 {
661 auto index = nextIndex(wctx.properties);
662 wctx.properties.emplace_back();
663 auto & prop = wctx.properties.back();
664 prop.keyType = CogsBin3::PropertyKeyType::String;
665 prop.key = recordString(wctx, key);
666 prop.valueType = valueType;
667 prop.value = recordString(wctx, value);
668 return index;
669 }
670
671 uint32_t recordTexture(WriteContext& wctx, const Texture* srcTex, uint16_t sectionIx)
672 {
673 assert(srcTex);
674 assert(srcTex->storage.data.size() != 0);
675
676 if (auto it = wctx.knownTexturesByPointer.find(srcTex); it != wctx.knownTexturesByPointer.end()) {
677 return it->second;
678 }
679 auto texIx = nextIndex(wctx.textures);
680 wctx.textures.emplace_back(CogsBin3::Texture{});
681 wctx.knownTexturesByPointer[srcTex] = texIx;
682 auto & dstTex = wctx.textures.back();
683
684 // Store image data
685 assert(srcTex->storage.data.size() <= std::numeric_limits<uint32_t>::max());
686 const auto size = uint32_t(srcTex->storage.data.size());
687 dstTex.buffer = recordBuffer(wctx, size, CogsBin3::BufferContentFlags::TextureData, 4, sectionIx);
688 std::memcpy(getBufferPointer(wctx, dstTex.buffer, 0, size), srcTex->storage.data.data(), size);
689
690 dstTex.offset = 0;
691 dstTex.size = size;
692 dstTex.encoding = CogsBin3::TextureEncoding::None;
693 dstTex.name = recordString(wctx, srcTex->getName());
694 dstTex.width = srcTex->description.width;
695 dstTex.height = srcTex->description.height;
696 dstTex.depth = srcTex->description.depth;
697 dstTex.format = translateFormat(srcTex->description.format);
698 dstTex.flags = CogsBin3::TextureFlags::None;
699
700 return texIx;
701 }
702
703 uint32_t recordMaterialInstance(WriteContext& wctx, MaterialInstance* srcMatInst, uint16_t sectionIx)
704 {
705 if (srcMatInst == nullptr) return ~0u;
706
707 if (auto it = wctx.knownMaterialInstancesByPointer.find(srcMatInst); it != wctx.knownMaterialInstancesByPointer.end()) {
708 return it->second;
709 }
710
711 auto hash = getMaterialInstanceHash(srcMatInst);
712 if (auto it = wctx.knownMaterialInstancesByHash.find(hash); it != wctx.knownMaterialInstancesByHash.end()) {
713 return it->second;
714 }
715
716 auto index = nextIndex(wctx.materialInstances);
717 wctx.materialInstances.emplace_back();
718 auto & dstMatInst = wctx.materialInstances.back();
719
720 wctx.knownMaterialInstancesByPointer[srcMatInst] = index;
721 wctx.knownMaterialInstancesByHash[hash] = index;
722
723 dstMatInst.material = recordString(wctx, srcMatInst->material->getName());
724 dstMatInst.permutation = recordString(wctx, srcMatInst->getPermutation());
725 dstMatInst.propertyFirst = nextIndex(wctx.properties);
726 dstMatInst.propertyCount = 0;
727
728 for (auto & prop : srcMatInst->material->constantBuffers.variables) {
729 switch (prop.type) {
730 case MaterialDataType::Float:
731 if (auto val = srcMatInst->getFloatProperty(prop.key); val != prop.defaultFloat()) {
732 recordProperty(wctx, prop.name, CogsBin3::PropertyValueType::Float, val);
733 dstMatInst.propertyCount++;
734 }
735 break;
736 case MaterialDataType::Float2:
737 if (auto val = srcMatInst->getVec2Property(prop.key); val != prop.defaultVec2()) {
738 recordProperty(wctx, prop.name, CogsBin3::PropertyValueType::Float2, val);
739 dstMatInst.propertyCount++;
740 }
741 break;
742 case MaterialDataType::Float3:
743 if (auto val = srcMatInst->getVec3Property(prop.key); val != prop.defaultVec3()) {
744 recordProperty(wctx, prop.name, CogsBin3::PropertyValueType::Float3, val);
745 dstMatInst.propertyCount++;
746 }
747 break;
748 case MaterialDataType::Float4:
749 if (auto val = srcMatInst->getVec4Property(prop.key); val != prop.defaultVec4()) {
750 recordProperty(wctx, prop.name, CogsBin3::PropertyValueType::Float4, val);
751 dstMatInst.propertyCount++;
752 }
753 break;
754 case MaterialDataType::Bool:
755 if (auto val = srcMatInst->getBoolProperty(prop.key); val != prop.defaultBool()) {
756 recordProperty(wctx, prop.name, CogsBin3::PropertyValueType::Bool, (uint32_t)val);
757 dstMatInst.propertyCount++;
758 }
759 break;
760 case MaterialDataType::UInt:
761 if (auto val = srcMatInst->getProperty<uint32_t>(prop.key); val != prop.defaultUInt()) {
762 recordProperty(wctx, prop.name, CogsBin3::PropertyValueType::UInt, (uint32_t)val);
763 dstMatInst.propertyCount++;
764 }
765 break;
766 default:
767 LOG_WARNING(logger, "Skipped serialization of unsupported material property %s of type %u.", prop.name.c_str(), unsigned(prop.type));
768 break;
769 }
770 }
771
772
773 if ((wctx.flags & WriteModelFlags::EMBED_TEXTURES) != 0) {
774 for (auto & prop : srcMatInst->material->textureProperties) {
775 auto value = srcMatInst->getTextureProperty(prop.key);
776 if (value.texture.handle && value.texture.handle != prop.texture.handle) { // different texture on instance than on material.
777 auto * tex = value.texture.handle.resolve();
778 if (tex->storage.data.size() == 0) {
779 LOG_WARNING(logger, "Empty texture, skipping.");
780 continue;
781 }
782 uint32_t texIx = recordTexture(wctx, tex, sectionIx);
783 recordProperty(wctx, prop.name, CogsBin3::PropertyValueType::EmbeddedTexture, texIx);
784 dstMatInst.propertyCount++;
785 }
786 }
787 }
788
789 else {
790 for (uint32_t texI = 0; texI < srcMatInst->material->textureProperties.size(); texI++) {
791 const TextureProperty& prop = srcMatInst->material->textureProperties[texI];
792 const TextureValue& value = srcMatInst->getTextureProperty(prop.key);
793 if (value.texture.handle && value.texture.handle != prop.texture.handle) {
794 std::string source = value.texture.handle->getSource().to_string();
795 const std::string dir = Cogs::IO::parentPath(wctx.fileName);
796 if (source.empty()) {
797 // If the source is empty, try to write the texture data to a png and use that as source.
798 const Texture* tex = value.texture.handle.resolve();
799 if (tex->storage.data.size() == 0) {
800 LOG_WARNING(logger, "Skipping texture without source or data.");
801 continue;
802 }
803 const std::string textureName = Cogs::IO::stem(wctx.fileName) + "_texture_" + std::to_string(index) + std::to_string(texI) + ".png";
804 source = Cogs::IO::combine(dir, textureName);
805 const Cogs::FormatInfo* formatInfo = getFormatInfo(tex->description.format);
806 if (!stbi_write_png(source.c_str(), tex->description.width, tex->description.height, formatInfo->elements, tex->storage.data.data(), 0)) {
807 LOG_ERROR(logger, "Failed to write texture %s.", source.c_str());
808 return ~0u;
809 }
810 }
811 recordStringBasedProperty(wctx, prop.name, CogsBin3::PropertyValueType::String, Cogs::IO::relativePath(source, dir));
812 dstMatInst.propertyCount++;
813 }
814 }
815 }
816
817 const ShaderVariants& variants = srcMatInst->material->definition.variants;
818 for (const ShaderVariantSelector& selector : srcMatInst->variantSelectors) {
819 const ShaderVariantDefinition& variant = variants[selector.index];
820 if (variant.isShared) continue; // Do not store shared variants.
821 if (selector.value != variant.defaultValue) {
822 if (variant.type == ShaderVariantType::Enum) {
823 recordStringBasedProperty(wctx, variant.name, CogsBin3::PropertyValueType::Variant, variant.values[selector.value].key);
824 dstMatInst.propertyCount++;
825 }
826 else {
827 recordStringBasedProperty(wctx, variant.name, CogsBin3::PropertyValueType::Variant, selector.value ? "true" : "false");
828 dstMatInst.propertyCount++;
829 }
830 }
831 }
832
833 assert(dstMatInst.propertyFirst + dstMatInst.propertyCount == nextIndex(wctx.properties));
834 return index;
835 }
836
837 uint32_t recordSkeleton(WriteContext& wctx, const Skeleton& srcSkeleton)
838 {
839 if (srcSkeleton.bones.empty()) return ~0u;
840
841 auto index = nextIndex(wctx.skeletons);
842 wctx.skeletons.emplace_back(CogsBin3::Skeleton{});
843 auto & dstSkeleton = wctx.skeletons.back();
844 dstSkeleton.bindPose = srcSkeleton.bindPose;
845 dstSkeleton.firstBone = nextIndex(wctx.bones);
846 dstSkeleton.boneCount = uint32_t(srcSkeleton.bones.size());
847 for (const auto & srcBone : srcSkeleton.bones) {
848 wctx.bones.emplace_back(CogsBin3::Bone{});
849 auto & dstBone = wctx.bones.back();
850 dstBone.inverseBindPose = srcBone.inverseBindPose;
851 dstBone.relative = srcBone.relative;
852 dstBone.rot = srcBone.rot;
853 dstBone.pos = srcBone.pos;
854 dstBone.scale = srcBone.scale;
855 dstBone.name = recordString(wctx, srcBone.name);
856 dstBone.parentBone = uint16_t(srcBone.parentBone);
857 dstBone.flags =
858 (srcBone.hasOffset ? CogsBin3::BoneFlags::HasOffset : CogsBin3::BoneFlags::None) |
859 (srcBone.used ? CogsBin3::BoneFlags::Used : CogsBin3::BoneFlags::None) |
860 (srcBone.animated ? CogsBin3::BoneFlags::Animated : CogsBin3::BoneFlags::None);
861 }
862 return index;
863 }
864
865 uint32_t recordPart(WriteContext& wctx, const Model& model, const ModelPart& part, uint16_t section)
866 {
867 auto nodeIndex = newNode(wctx);
868 auto & node = wctx.nodes[nodeIndex];
869
870 node.parent = part.parentIndex;
871 if (part.nameIndex != -1) {
872 node.name = recordString(wctx, model.getPartName(part));
873 }
874 if (part.transformIndex != -1) {
875 node.transform = recordTransform(wctx, model.getPartTransform(part));
876 }
877
878 if (part.meshIndex != -1) {
879 auto * mesh = model.meshes[part.meshIndex].resolve();
880 node.mesh = recordMesh(wctx, mesh, 0);
881 node.boundingBox = recordBoundingBox(wctx, mesh->boundingBox);
882 node.primitiveType = translatePrimitiveType(wctx, mesh->primitiveType); // FIXME: currently just use the primitive type of mesh since part has primtitive type.
883 node.vertexFirst = part.startIndex;
884 node.vertexCount = part.vertexCount;
885 if (part.materialIndex != -1) {
886 node.materialInstance = recordMaterialInstance(wctx, model.materials[part.materialIndex].resolve(), section);
887 }
888 }
889
890 return nodeIndex;
891 }
892
893 void recordAnimTrack(WriteContext& wctx, const AnimationTrack& srcTrack, uint16_t sectionIx)
894 {
895 wctx.animTracks.emplace_back(CogsBin3::AnimTrack{});
896 auto & dstTrack = wctx.animTracks.back();
897
898 const auto translationCount = uint32_t(srcTrack.translations.size());
899 const auto rotationCount = uint32_t(srcTrack.rotations.size());
900 const auto scaleCount = uint32_t(srcTrack.scales.size());
901
902 dstTrack.boneIndex = uint32_t(srcTrack.boneIndex);
903 dstTrack.buffer = recordBuffer(wctx,
904 sizeof(float)*(4 * (size_t)translationCount + 5 * (size_t)rotationCount + 4 * (size_t)scaleCount),
905 CogsBin3::BufferContentFlags::AnimationData, 4, sectionIx);
906
907 auto * base = (float*)wctx.bufferContexts[dstTrack.buffer].contents.data();
908 auto * ptr = base;
909
910 dstTrack.translationOffset = uint32_t((uint8_t*)ptr - (uint8_t*)base);
911 for(auto & translation : srcTrack.translations) {
912 *ptr++ = translation.t;
913 *ptr++ = translation.value[0];
914 *ptr++ = translation.value[1];
915 *ptr++ = translation.value[2];
916 }
917 dstTrack.translationCount = translationCount;
918 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) - dstTrack.translationOffset == sizeof(float) * 4 * translationCount);
919
920 dstTrack.rotationOffset = uint32_t((uint8_t*)ptr - (uint8_t*)base);
921 for (auto & rotation : srcTrack.rotations) {
922 *ptr++ = rotation.t;
923 *ptr++ = rotation.value[0];
924 *ptr++ = rotation.value[1];
925 *ptr++ = rotation.value[2];
926 *ptr++ = rotation.value[3];
927 }
928 dstTrack.rotationCount = rotationCount;
929 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) - dstTrack.rotationOffset == sizeof(float) * 5 * rotationCount);
930
931 dstTrack.scaleOffset = uint32_t((uint8_t*)ptr - (uint8_t*)base);
932 for (auto & scale : srcTrack.scales) {
933 *ptr++ = scale.t;
934 *ptr++ = scale.value[0];
935 *ptr++ = scale.value[1];
936 *ptr++ = scale.value[2];
937 }
938 dstTrack.scaleCount = scaleCount;
939 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) - dstTrack.scaleOffset == sizeof(float) * 4 * scaleCount);
940 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) == sizeof(float) * (4 * translationCount + 5 * rotationCount + 4 * scaleCount));
941 }
942
943 void recordAnimClip(WriteContext& wctx, const AnimationClip& srcClip, uint16_t sectionIx)
944 {
945 wctx.animClips.emplace_back(CogsBin3::AnimClip{});
946 auto & dstClip = wctx.animClips.back();
947 dstClip.name = recordString(wctx, srcClip.name);
948 dstClip.duration = srcClip.duration;
949 dstClip.resolution = srcClip.resolution;
950 if (!srcClip.tracks.empty()) {
951 dstClip.trackFirst = nextIndex(wctx.animTracks);
952 dstClip.trackCount = uint32_t(srcClip.tracks.size());
953 for (auto const & track : srcClip.tracks) recordAnimTrack(wctx, track, sectionIx);
954 assert(dstClip.trackFirst + dstClip.trackCount == wctx.animTracks.size());
955 }
956 else {
957 dstClip.trackFirst = ~0u;
958 dstClip.trackCount = 0;
959 }
960 }
961
962 void recordModel(WriteContext& wctx, const Model * srcModel, WriteModelFlags /*flags*/)
963 {
964 if (srcModel->parts.empty()) return; // Now we know that it is atleast one part.
965
966 uint16_t section = 0;
967
968 wctx.models.emplace_back(CogsBin3::Model{});
969 auto & dstModel = wctx.models.back();
970 dstModel.skeleton = (uint32_t)-1;
971 dstModel.animClipFirst = (uint32_t)-1;
972 dstModel.animClipCount = 0;
973 if (auto * animation = srcModel->animation.resolve(); animation) {
974 dstModel.skeleton = recordSkeleton(wctx, animation->skeleton);
975 if (!animation->clips.empty()) {
976 dstModel.animClipFirst = nextIndex(wctx.animClips);
977 dstModel.animClipCount = uint32_t(animation->clips.size());
978 for (const auto & clip : animation->clips) recordAnimClip(wctx, clip, section);
979 assert(dstModel.animClipFirst + dstModel.animClipCount == wctx.animClips.size());
980 }
981 }
982
983 dstModel.firstNode = recordPart(wctx, *srcModel, srcModel->parts[0], section);
984 for (size_t i = 1; i < srcModel->parts.size(); i++) {
985 auto ix = recordPart(wctx, *srcModel, srcModel->parts[i], 0);
986 assert(dstModel.firstNode + i == ix);
987 }
988 dstModel.nodeCount = uint32_t(srcModel->parts.size());
989 }
990
991 uint32_t recordEntity(Context* context, WriteContext& wctx, uint32_t parent, Cogs::ComponentModel::Entity* entity, uint16_t section)
992 {
993 auto nodeIndex = newNode(wctx);
994 auto & node = wctx.nodes[nodeIndex];
995
996 node.parent = parent;
997 node.name = recordString(wctx, entity->getName());
998
999 if (const auto * trComp = entity->getComponent<TransformComponent>(); trComp) {
1000 node.transform = recordTransform(wctx, context->transformSystem->getLocalTransform(trComp));
1001 }
1002
1003 if (auto meshComponent = entity->getComponent<MeshComponent>(); meshComponent && meshComponent->meshHandle) {
1004 auto mesh = meshComponent->meshHandle;
1005 if (mesh && mesh->streams.size()) {
1006 auto renderComponent = entity->getComponent<MeshRenderComponent>();
1007 if (renderComponent) {
1008 node.mesh = recordMesh(wctx, mesh.resolve(), section);
1009 if (renderComponent->primitiveType != (Cogs::PrimitiveType)-1) {
1010 node.primitiveType = translatePrimitiveType(wctx, renderComponent->primitiveType);
1011 } else {
1012 node.primitiveType = CogsBin3::PrimitiveType::Unknown;
1013 }
1014 node.vertexFirst = renderComponent->startIndex;
1015 node.vertexCount = renderComponent->vertexCount;
1016 node.boundingBox = recordBoundingBox(wctx, context->renderSystem->getLocalBounds(renderComponent));
1017 if (renderComponent->material) {
1018 node.materialInstance = recordMaterialInstance(wctx, renderComponent->material.resolve(), section);
1019 }
1020 }
1021 else {
1022 LOG_ERROR(logger, "MeshComponent must be accompanied by a MeshRenderComponent for export.");
1023 }
1024 }
1025 }
1026
1027 if (auto * sceneComponent = entity->getComponent<SceneComponent>(); sceneComponent) {
1028 for (auto & child : sceneComponent->children) {
1029 recordEntity(context, wctx, nodeIndex, child.get(), section);
1030 }
1031 }
1032
1033 if (auto * animComp = entity->getComponent<AnimationComponent>(); animComp) {
1034
1035 auto * anim = animComp->animation.resolve();
1036 if (animComp->master) {
1037 auto * master = animComp->master.resolveComponent<AnimationComponent>();
1038 anim = master->animation.resolve();
1039 }
1040 if (wctx.currentAnimation == nullptr) {
1041 wctx.currentAnimation = anim;
1042 }
1043 else if (wctx.currentAnimation != anim) {
1044 LOG_ERROR(logger, "Multiple animations within a single model's hierarchy");
1045 }
1046 }
1047
1048 return nodeIndex;
1049 }
1050
1051 void layoutSections(WriteContext& wctx)
1052 {
1053 for (size_t i = 0; i < wctx.sections.size(); i++) {
1054 auto & section = *wctx.sections[i].get();
1055
1056 assert(section.contents.empty() && "Sections already laid out");
1057
1058 // lay out subsections at start
1059 section.writePos = sizeof(CogsBin3::SubSectionInfo)*section.subSections.size();
1060 for (auto & subsection : section.subSections) {
1061 section.writePos = align(section.writePos);
1062 assert(section.writePos < std::numeric_limits<uint32_t>::max());
1063 subsection.offset = section.writePos;
1064 section.writePos += subsection.size;
1065 }
1066 section.writePos = align(section.writePos);
1067
1068 // Add data size and create buffer for contents.
1069 section.contents.resize(section.writePos + section.dataSize);
1070 LOG_TRACE(logger, "Section %zu allocated %zu bytes, data offset=%zu", i, section.contents.size(), section.writePos);
1071
1072 // write subsection directory
1073 auto * ptr = (CogsBin3::SubSectionInfo*)section.contents.data();
1074 for (auto & subsection : section.subSections) {
1075 LOG_TRACE(logger, " Subsection offset=%zu, size=%u, type=%u", subsection.offset, subsection.size, unsigned(subsection.type));
1076 ptr->offset = subsection.offset;
1077 ptr->size = subsection.size;
1078 ptr->type = subsection.type;
1079 ptr++;
1080 }
1081 }
1082 }
1083
1084
1085 template<typename T>
1086 void fillSubsection(WSection& section, uint32_t index, const std::vector<T>& source)
1087 {
1088 if (index == ~0u) return;
1089 auto & subSection = section.subSections[index];
1090 assert(sizeof(T)*source.size() == subSection.size);
1091
1092 std::memcpy(section.contents.data() + subSection.offset, source.data(), subSection.size);
1093 }
1094
1095 bool pack(Context* /*context*/, WriteContext& wctx)
1096 {
1097 // First section has meta data, and only this one can have subsections.
1098 auto & metaSection = getSection(wctx, CogsBin3::SectionType::META, 0);
1099
1100 uint32_t subSections[size_t(CogsBin3::SubSectionType::EnumSize)];
1101 for (auto & subSection : subSections) subSection = (uint32_t)-1;
1102
1103#define REGISTER(T,A) if(!(A).empty()) subSections[size_t(T)] = addMetaSubsection(wctx, metaSection, T, sizeof((A)[0])*((A).size()))
1104 REGISTER(CogsBin3::SubSectionType::Buffers, wctx.buffers);
1105 REGISTER(CogsBin3::SubSectionType::Textures, wctx.textures);
1106 REGISTER(CogsBin3::SubSectionType::Strings, wctx.strings);
1107 REGISTER(CogsBin3::SubSectionType::StringData, wctx.stringData);
1108 REGISTER(CogsBin3::SubSectionType::Nodes, wctx.nodes);
1109 REGISTER(CogsBin3::SubSectionType::Transforms, wctx.transforms);
1110 REGISTER(CogsBin3::SubSectionType::BoundingBoxes, wctx.boundingBoxes);
1111 REGISTER(CogsBin3::SubSectionType::MaterialInstances, wctx.materialInstances);
1112 REGISTER(CogsBin3::SubSectionType::Properties, wctx.properties);
1113 REGISTER(CogsBin3::SubSectionType::PropertyData, wctx.propertyData);
1114 REGISTER(CogsBin3::SubSectionType::Meshes, wctx.meshes);
1115 REGISTER(CogsBin3::SubSectionType::VertexStreams, wctx.vertexStreams);
1116 REGISTER(CogsBin3::SubSectionType::VertexAttributes, wctx.vertexAttributes);
1117 REGISTER(CogsBin3::SubSectionType::Bones, wctx.bones);
1118 REGISTER(CogsBin3::SubSectionType::Skeletons, wctx.skeletons);
1119 REGISTER(CogsBin3::SubSectionType::AnimTracks, wctx.animTracks);
1120 REGISTER(CogsBin3::SubSectionType::AnimClips, wctx.animClips);
1121 REGISTER(CogsBin3::SubSectionType::Models, wctx.models);
1122#undef REGISTER
1123
1124 layoutSections(wctx);
1125
1126 // copy contents of buffers
1127 for (size_t i = 0; i < wctx.buffers.size(); i++) {
1128 auto & buffer = wctx.buffers[i];
1129 const auto & bufCtx = wctx.bufferContexts[i];
1130 const auto lowerMask = uint64_t(bufCtx.alignment - 1);
1131
1132 const auto sectionIx = buffer.sectionOffset;
1133 auto & section = getSection(wctx, CogsBin3::SectionType::DATA, uint16_t(sectionIx), false);
1134 auto offset = (section.writePos + lowerMask) & (~lowerMask);
1135 buffer.sectionOffset = offset | (sectionIx << 48);
1136
1137 assert(offset + buffer.size <= section.contents.size());
1138 std::memcpy(section.contents.data() + offset, bufCtx.contents.data(), buffer.size);
1139 section.writePos = offset + buffer.size;
1140 }
1141
1142 // copy contents of meta subsection
1143 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Buffers)], wctx.buffers);
1144 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Textures)], wctx.textures);
1145 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Strings)], wctx.strings);
1146 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::StringData)], wctx.stringData);
1147 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Nodes)], wctx.nodes);
1148 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Transforms)], wctx.transforms);
1149 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::BoundingBoxes)], wctx.boundingBoxes);
1150 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::MaterialInstances)], wctx.materialInstances);
1151 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Properties)], wctx.properties);
1152 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::PropertyData)], wctx.propertyData);
1153 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Meshes)], wctx.meshes);
1154 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::VertexStreams)], wctx.vertexStreams);
1155 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::VertexAttributes)], wctx.vertexAttributes);
1156 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Bones)], wctx.bones);
1157 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Skeletons)], wctx.skeletons);
1158 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::AnimTracks)], wctx.animTracks);
1159 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::AnimClips)], wctx.animClips);
1160 fillSubsection(metaSection, subSections[size_t(CogsBin3::SubSectionType::Models)], wctx.models);
1161
1162 return true;
1163 }
1164
1165#if defined(_WIN32) && defined(MAKE_DEBUGABLE)
1166#pragma optimize( "", on )
1167#endif
1168 bool compressSections(Context* /*context*/, WriteContext& wctx)
1169 {
1170 wctx.sectionInfos.resize(wctx.sections.size());
1171 for (auto & section : wctx.sections) {
1172 auto & info = wctx.sectionInfos[section->index]; // Initialize section infos
1173 info.fileSize = section->contents.size();
1174 info.uncompressedSize = info.fileSize;
1175 info.compression = CogsBin3::Compression::None;
1176 info.type = section->type;
1177 info.subSectionCount = uint16_t(section->subSections.size());
1178 }
1179
1180
1181 if ((wctx.flags & WriteModelFlags::COMPRESS_ZSTD) != 0) {
1182
1183 int compressionLevel = ZSTD_CLEVEL_DEFAULT;
1184 if ((wctx.flags & WriteModelFlags::COMPRESS_MAX) != 0) {
1185 compressionLevel = ZSTD_maxCLevel();
1186 } else {
1187 compressionLevel = wctx.settings.compressionLevel;
1188 }
1189
1190 // Recycle context between sections. There is an opportunity for multi-threading with multiple sections.
1191 ZSTD_CCtx* zstd_ctx = ZSTD_createCCtx();
1192 if (zstd_ctx == nullptr) {
1193 LOG_ERROR(logger, "Failed to create zstd context.");
1194 return false;
1195 }
1196
1197 std::vector<uint8_t> temp;
1198 for (auto & section : wctx.sections) {
1199 auto & info = wctx.sectionInfos[section->index];
1200 auto maxSize = ZSTD_compressBound(info.uncompressedSize);
1201 temp.resize(maxSize);
1202
1203 // Run compression and check result
1204 auto result = ZSTD_compressCCtx(zstd_ctx, temp.data(), maxSize, section->contents.data(), info.uncompressedSize, compressionLevel);
1205 if (ZSTD_isError(result)) {
1206 LOG_ERROR(logger, "zstd compression failed: %s", ZSTD_getErrorName(result));
1207 continue;
1208 }
1209 else if (info.uncompressedSize <= result) {
1210 //LOG_DEBUG(logger, "compressed size is greater or equal to uncompressed size.");
1211 continue;
1212 }
1213
1214 // Success, note compressed size etc.
1215 temp.resize(result);
1216 info.compression = CogsBin3::Compression::ZSTD;
1217 info.fileSize = result;
1218 section->contents.swap(temp);
1219 }
1220
1221 ZSTD_freeCCtx(zstd_ctx);
1222 return true;
1223 }
1224 else {
1225 return true; // No compression
1226 }
1227 }
1228#if defined(_WIN32) && defined(MAKE_DEBUGABLE)
1229#pragma optimize( "", off )
1230#endif
1231
1232 bool write(Context* context, WriteContext& wctx)
1233 {
1234 pack(context, wctx);
1235
1236 compressSections(context, wctx);
1237
1238 // layout file and populate header
1239 uint64_t uncompressedSize = sizeof(CogsBin3::Header) + sizeof(CogsBin3::SectionInfo)*wctx.sectionInfos.size();
1240
1241 CogsBin3::Header header{ };
1242 header.magic = CogsBin3::HeaderMagic;
1243 header.version = CogsBin3::Version;
1244 header.fileLength = sizeof(CogsBin3::Header) + sizeof(CogsBin3::SectionInfo) * wctx.sectionInfos.size();
1245 header.sectionCount = nextIndex(wctx.sectionInfos);
1246 for (auto & info : wctx.sectionInfos) {
1247 if (info.compression == CogsBin3::Compression::None) {
1248 // if no compression, align
1249 header.fileLength = (header.fileLength + (CogsBin3::CogsSectionAlignment - 1)) & ~(CogsBin3::CogsSectionAlignment - 1);
1250 }
1251 info.fileOffset = header.fileLength;
1252 header.fileLength += info.fileSize;
1253 uncompressedSize += info.uncompressedSize;
1254 }
1255
1256 // write file.
1257 FILE * file = fopen(wctx.fileName.c_str(), "wb");
1258 if (file == nullptr) {
1259 LOG_ERROR(logger, "Failed to open \"%s\" for writing.", wctx.fileName.c_str());
1260 return false;
1261 }
1262
1263 // write header
1264 auto r = fwrite(&header, sizeof(CogsBin3::Header), 1, file);
1265 if (r != 1){
1266 fclose(file);
1267 LOG_ERROR(logger, "Error writing to %s", wctx.fileName.c_str());
1268 return false;
1269 }
1270
1271 // write section infos
1272 r = fwrite(wctx.sectionInfos.data(), sizeof(CogsBin3::SectionInfo)*header.sectionCount, 1, file);
1273 if (r != 1){
1274 fclose(file);
1275 LOG_ERROR(logger, "Error writing to %s", wctx.fileName.c_str());
1276 return false;
1277 }
1278
1279 // write section contents
1280 for (uint32_t i = 0; i < header.sectionCount; i++) {
1281 auto & info = wctx.sectionInfos[i];
1282 auto & section = *wctx.sections[i].get();
1283
1284#ifdef _WIN32
1285 auto pos = uint64_t(_ftelli64(file));
1286#else
1287 auto pos = uint64_t(ftell(file));
1288#endif
1289 assert(pos <= info.fileOffset);
1290 if (pos != info.fileOffset) {
1291#ifdef _WIN32
1292 _fseeki64(file, info.fileOffset, SEEK_SET);
1293#else
1294 fseek(file, info.fileOffset, SEEK_SET);
1295#endif
1296 }
1297
1298 r = fwrite(section.contents.data(), info.fileSize, 1, file);
1299 if (r != 1){
1300 fclose(file);
1301 LOG_ERROR(logger, "Error writing to %s", wctx.fileName.c_str());
1302 return false;
1303 }
1304 }
1305
1306#ifdef _WIN32
1307 auto fileLength = _ftelli64(file);
1308#else
1309 auto fileLength = ftell(file);
1310#endif
1311 assert(fileLength == (long long)header.fileLength);
1312 LOG_TRACE(logger, "Wrote %zu bytes (=%zu uncompressed, ratio=%.1f%%) (%u sections)",
1313 size_t(fileLength),
1314 size_t(uncompressedSize),
1315 (100.f*float(fileLength))/float(uncompressedSize),
1316 unsigned(header.sectionCount));
1317 fclose(file);
1318 return true;
1319 }
1320
1321}
1322
1323bool Cogs::Core::writeCogsBin3Model(Context * context, uint32_t & numVertes, uint32_t & numIndexes, const StringView & fileName, const Model * model, WriteModelSettings * settings)
1324{
1325 auto flags = settings->flags;
1326
1327 if (!model) return false;
1328
1329 WriteContext wctx{};
1330 wctx.fileName = fileName.to_string();
1331 wctx.flags = WriteModelFlags(flags);
1332 if (settings) {
1333 wctx.settings = *settings;
1334 }
1335 recordModel(wctx, model, flags);
1336 write(context, wctx);
1337 numVertes = wctx.numVertes;
1338 numIndexes = wctx.numIndexes;
1339 return true;
1340}
1341
1342bool Cogs::Core::writeCogsBin3Models(Context * context, uint32_t& numVertes, uint32_t& numIndexes, const StringView & fileName, ComponentModel::Entity ** entity, size_t N, WriteModelFlags flags)
1343{
1344 if (!entity || N == 0) return false;
1345
1346 uint16_t section = 0;
1347 WriteContext wctx{};
1348 wctx.fileName = fileName.to_string();
1349 wctx.flags = WriteModelFlags(flags);
1350 wctx.currentAnimation = nullptr;
1351 wctx.models.emplace_back(CogsBin3::Model{});
1352 auto & dstModel = wctx.models.back();
1353
1354 dstModel.firstNode = recordEntity(context, wctx, ~0u, entity[0], section);
1355 for (size_t i = 1; i < N; i++) {
1356 recordEntity(context, wctx, ~0u, entity[i], section);
1357 }
1358 dstModel.nodeCount = uint32_t(wctx.nodes.size() - dstModel.firstNode);
1359
1360 if (wctx.currentAnimation) {
1361 dstModel.skeleton = recordSkeleton(wctx, wctx.currentAnimation->skeleton);
1362 dstModel.animClipFirst = nextIndex(wctx.animClips);
1363 dstModel.animClipCount = uint32_t(wctx.currentAnimation->clips.size());
1364 for (const auto & clip : wctx.currentAnimation->clips) recordAnimClip(wctx, clip, section);
1365 assert(dstModel.animClipFirst + dstModel.animClipCount == wctx.animClips.size());
1366 }
1367 else {
1368 dstModel.skeleton = ~0u;
1369 dstModel.animClipFirst = ~0u;
1370 dstModel.animClipCount = 0;
1371 }
1372
1373 write(context, wctx);
1374 numVertes = wctx.numVertes;
1375 numIndexes = wctx.numIndexes;
1376
1377 return true;
1378}
1379
1380#if defined(_WIN32) && defined(MAKE_DEBUGABLE)
1381#pragma optimize( "", on )
1382#endif
1383
1384#else
1385// Emscripten
1386
1387bool Cogs::Core::writeCogsBin3Model(Context * /*context*/, uint32_t & /*numVertes*/, uint32_t & /*numIndexes*/, const StringView & /*fileName*/, const Model * /*model*/, WriteModelSettings * /*settings*/)
1388{
1389 return false;
1390}
1391
1392bool Cogs::Core::writeCogsBin3Models(Context * /*context*/, uint32_t& /*numVertes*/, uint32_t& /*numIndexes*/, const StringView & /*fileName*/, ComponentModel::Entity ** /*entity*/, size_t /*N*/, WriteModelFlags /*flags*/)
1393{
1394 return false;
1395}
1396
1397#endif
Container for components, providing composition of dynamic entities.
Definition: Entity.h:18
T * getComponent() const
Get a pointer to the first component implementing the given type in the entity.
Definition: Entity.h:35
const std::string & getName() const noexcept
Get the name of this entity.
Definition: Entity.h:120
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
MeshHandle meshHandle
Handle to a Mesh resource to use when rendering.
Definition: MeshComponent.h:29
Renders the contents of a MeshComponent using the given materials.
Contains information on how the entity behaves in the scene.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Log implementation class.
Definition: LogManager.h:140
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
constexpr size_t hash() const noexcept
Get the hash code of the string.
Definition: StringView.h:200
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
constexpr size_t hashSequence(const T &t, const U &u)
Hash the last two items in a sequence of objects.
Definition: HashSequence.h:8
@ VertexData
Per vertex data.
PrimitiveType
Primitive types for interpreting vertex data sent to the graphics pipeline.
Definition: Common.h:112
@ PointList
List of points.
@ TriangleStrip
Triangle strip.
@ LineList
List of lines.
@ TriangleStripAdjacency
Triangle strip with adjacency.
@ LineStripAdjacency
Line strip with adjacency.
@ LineListAdjacency
List of lines with adjacency.
@ TriangleListAdjacency
List of triangles with adjacency.
@ LineStrip
Line strip.
@ TriangleList
List of triangles.
@ Position
Position semantic.
@ Tangent
Tangent semantic.
@ Normal
Normal semantic.
@ InstanceMatrix
Instance matrix semantic.
@ InstanceVector
Instance vector semantic.
@ Color
Color semantic.
@ TextureCoordinate
Texture coordinate semantic.
Component handling animation of multiple Poses of an Animation Resource.
AnimationHandle animation
Animation resource handle.
uint32_t boneIndex
Specifies which bone of a skeleton that is animated by this track.
Definition: Animation.h:38
Buffer resource.
Definition: Buffer.h:50
bool empty() const
If the buffer is empty. Note that an empty buffer may still have reserved storage.
Definition: Buffer.h:65
bool isVertexBuffer() const
Gets if the buffer data is usable as a vertex data.
Definition: Buffer.h:98
size_t size() const
Size of the buffer in bytes.
Definition: Buffer.h:62
void * data()
Get a pointer to the buffer data.
Definition: Buffer.h:74
bool isIndexBuffer() const
Gets if the buffer data is usable as index buffer data.
Definition: Buffer.h:101
Generic blob of data.
Definition: CogsBin3.h:281
std::vector< MaterialProperty > variables
Individual variables from all buffer instances.
Contains a stream of data used by Mesh resources.
Definition: Mesh.h:80
uint32_t numElements
Number of elements of the type given by format contained in data.
Definition: Mesh.h:108
uint32_t offset
Byte offset from the start of the buffer.
Definition: Mesh.h:102
VertexFormatHandle format
A pointer to the format describing the contents of the byte buffer.
Definition: Mesh.h:99
VertexDataType::EVertexDataType type
Type index used to index streams in a Mesh resource.
Definition: Mesh.h:111
uint32_t stride
Element stride.
Definition: Mesh.h:105
Cogs::Core::ResourceBufferHandle buffer
Data buffer.
Definition: Mesh.h:96
Material instances represent a specialized Material combined with state for all its buffers and prope...
glm::vec4 getVec4Property(const VariableKey key) const
Get the value of the property with the given key.
bool getBoolProperty(const VariableKey key) const
Get the value of the property with the given key.
TextureValue getTextureProperty(const VariableKey key) const
Get the value of the property with the given key.
float getFloatProperty(const VariableKey key) const
Get the value of the property with the given key.
glm::vec3 getVec3Property(const VariableKey key) const
Get the value of the property with the given key.
glm::vec2 getVec2Property(const VariableKey key) const
Get the value of the property with the given key.
Material * material
Material resource this MaterialInstance is created from.
ShaderVariantSelectors variantSelectors
Variant selectors.
VariableKey key
Key of the property, used to index into the property collection the property will be placed into.
PropertyName name
Name of the property, used to reference named uniforms of constant buffer members in shaders.
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
bool isCCW() const
If triangles in the mesh are specified counter-clockwise, which is the default.
Definition: Mesh.h:943
constexpr bool isMeshFlagSet(MeshFlags::EMeshFlags flag) const
Check if the given mesh flag(s) is set.
Definition: Mesh.h:683
Model resources define a template for a set of connected entities, with resources such as meshes,...
Definition: Model.h:56
StringView getName() const
Get the name of the resource.
Definition: ResourceBase.h:307
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
Property value for texture samplers.
TextureWithSampler texture
Value of the property for the material instance this belongs to.
TextureHandle handle
Handle to a texture resource, or TextureHandle::NoHandle if texture should be disabled.
Texture resources contain raster bitmap data to use for texturing.
Definition: Texture.h:91
uint16_t elements
Number of channels in a data item.
Definition: DataFormat.h:259
Vertex element structure used to describe a single data element in a vertex for the input assembler.
Definition: VertexFormat.h:38
InputType inputType
Input type of the element, vertex or instance data.
Definition: VertexFormat.h:43
DataFormat format
Format of the element.
Definition: VertexFormat.h:40
uint16_t offset
Offset in bytes from the vertex position in memory.
Definition: VertexFormat.h:39
uint16_t semanticIndex
Index for the semantic mapping.
Definition: VertexFormat.h:42
ElementSemantic semantic
Semantic mapping of the element (position, normal, etc...).
Definition: VertexFormat.h:41
uint16_t instanceStep
Instance step factor.
Definition: VertexFormat.h:44
Vertex format structure used to describe a single vertex for the input assembler.
Definition: VertexFormat.h:60