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