Cogs.Core
ModelWriter.cpp
1#include "ModelWriter.h"
2#include "CogsBin.h"
3#include "Context.h"
4#include "Services/Variables.h"
5
6#include "Components/Core/TransformComponent.h"
7#include "Components/Core/SceneComponent.h"
8#include "Components/Core/AnimationComponent.h"
9#include "Components/Core/MeshComponent.h"
10#include "Components/Core/MeshRenderComponent.h"
11
12#include "Systems/Core/TransformSystem.h"
13#include "Systems/Core/RenderSystem.h"
14
15#include "Resources/Buffer.h"
16#include "Resources/Mesh.h"
17#include "Resources/MaterialInstance.h"
18#include "Resources/Texture.h"
19#include "Resources/Model.h"
20#include "Resources/Animation.h"
21#include "Resources/DefaultMaterial.h"
22
23#include "Rendering/SamplerState.h"
24
25#include "Foundation/HashSequence.h"
26#include "Foundation/Logging/Logger.h"
27#include "Foundation/Memory/MemoryBuffer.h"
28#include "Foundation/Platform/IO.h"
29#include "Foundation/Platform/Timer.h"
30#include "Foundation/Reflection/TypeDatabase.h"
31
32#include "zstd.h"
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 Cogs::Logging::Log logger = Cogs::Logging::getLogger("ModelWriter");
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 CogsBin::SubSectionType type;
71 };
72
73 struct WSection
74 {
75 CogsBin::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::CogsBin::Buffer> buffers;
115 std::vector<Cogs::Core::CogsBin::ExternalTexture> externalTextures;
116 std::vector<Cogs::Core::CogsBin::EmbeddedTexture> embeddedTextures;
117 std::vector<Cogs::Core::CogsBin::SamplerState> samplerStates;
118 std::vector<Cogs::Core::CogsBin::String> strings;
119 std::vector<Cogs::Core::CogsBin::Property> properties;
120 std::vector<Cogs::Core::CogsBin::MaterialInstance> materialInstances;
121 std::vector<Cogs::Core::CogsBin::Transform> transforms;
122 std::vector<Cogs::Geometry::BoundingBox> boundingBoxes;
123 std::vector<Cogs::Core::CogsBin::Node> nodes;
124 std::vector<Cogs::Core::CogsBin::Mesh> meshes;
125 std::vector<Cogs::Core::CogsBin::VertexStream> vertexStreams;
126 std::vector<Cogs::Core::CogsBin::VertexAttribute> vertexAttributes;
127 std::vector<Cogs::Core::CogsBin::Bone> bones;
128 std::vector<Cogs::Core::CogsBin::Skeleton> skeletons;
129 std::vector<Cogs::Core::CogsBin::Model> models;
130 std::vector<Cogs::Core::CogsBin::AnimTrack> animTracks;
131 std::vector<Cogs::Core::CogsBin::AnimClip> animClips;
132
133 std::vector<CogsBin::SectionInfo> sectionInfos;
134
135 std::map<const Cogs::Core::Texture*, uint32_t> knownTexturesByPointer;
136 std::map<const Cogs::Core::MaterialInstance*, uint32_t> knownMaterialInstancesByPointer;
137 std::map<size_t, uint32_t> knownMaterialInstancesByHash;
138 std::map<size_t, uint32_t> propByHash;
139 std::map<size_t, uint32_t> stringByHash;
140 std::map<size_t, uint32_t> transformByHash;
141 std::map<size_t, uint32_t> boundingBoxByHash;
142 std::map<const Cogs::Core::Mesh*, uint32_t> meshByPointer;
143 std::map<const Cogs::Core::BufferResource*, uint32_t> bufferByPointer;
144 std::map<size_t, WVertexFormatRec> vertexFormatByHash;
145 };
146
147 template<typename T, typename S>
148 T checkedCast(const S v)
149 {
150 assert(v <= std::numeric_limits<T>::max());
151 return T(v);
152 }
153
154 inline uint64_t align(uint64_t x)
155 {
156 const uint64_t m = CogsBin::CogsSectionAlignment - 1;
157 return (x + m) & ~m;
158 }
159
160 CogsBin::PrimitiveType translatePrimitiveType(WriteContext& /*wctx*/, Cogs::PrimitiveType::EPrimitiveType primitiveType)
161 {
162 switch (primitiveType) {
163 case Cogs::PrimitiveType::TriangleList: return CogsBin::PrimitiveType::TriangleList; break;
164 case Cogs::PrimitiveType::TriangleStrip: return CogsBin::PrimitiveType::TriangleStrip; break;
165 case Cogs::PrimitiveType::LineList: return CogsBin::PrimitiveType::LineList; break;
166 case Cogs::PrimitiveType::LineStrip: return CogsBin::PrimitiveType::LineStrip; break;
167 case Cogs::PrimitiveType::PointList: return CogsBin::PrimitiveType::PointList; break;
168 case Cogs::PrimitiveType::TriangleListAdjacency: return CogsBin::PrimitiveType::TriangleListAdjacency; break;
169 case Cogs::PrimitiveType::TriangleStripAdjacency: return CogsBin::PrimitiveType::TriangleStripAdjacency; break;
170 case Cogs::PrimitiveType::LineListAdjacency: return CogsBin::PrimitiveType::LineListAdjacency; break;
171 case Cogs::PrimitiveType::LineStripAdjacency: return CogsBin::PrimitiveType::LineStripAdjacency; break;
172 case Cogs::PrimitiveType::ControlPoint1PatchList: return CogsBin::PrimitiveType::ControlPoint1PatchList; break;
173 case Cogs::PrimitiveType::ControlPoint2PatchList: return CogsBin::PrimitiveType::ControlPoint2PatchList; break;
174 case Cogs::PrimitiveType::ControlPoint3PatchList: return CogsBin::PrimitiveType::ControlPoint3PatchList; break;
175 case Cogs::PrimitiveType::ControlPoint4PatchList: return CogsBin::PrimitiveType::ControlPoint4PatchList; break;
176 default:
177 break;
178 }
179 assert(false && "Illegal primitive type encountered");
180 return CogsBin::PrimitiveType::TriangleList;
181 }
182
183 CogsBin::Format translateFormat(Cogs::TextureFormat format)
184 {
185 switch (format) {
186 case Cogs::TextureFormat::Unknown: return CogsBin::Format::Unknown; break;
187 case Cogs::TextureFormat::R8_UNORM: return CogsBin::Format::R8_UNORM; break;
188 case Cogs::TextureFormat::R8G8_UNORM: return CogsBin::Format::R8G8_UNORM; break;
189 case Cogs::TextureFormat::R8G8B8_UNORM: return CogsBin::Format::R8G8B8_UNORM; break;
190 case Cogs::TextureFormat::R8G8B8A8_UNORM: return CogsBin::Format::R8G8B8A8_UNORM; break;
191 case Cogs::TextureFormat::R16_UNORM: return CogsBin::Format::R16_UNORM; break;
192 case Cogs::TextureFormat::R16G16_UNORM: return CogsBin::Format::R16G16_UNORM; break;
193 case Cogs::TextureFormat::R16G16B16_UNORM: return CogsBin::Format::R16G16B16_UNORM; break;
194 case Cogs::TextureFormat::R16G16B16A16_UNORM: return CogsBin::Format::R16G16B16A16_UNORM; break;
195 case Cogs::TextureFormat::R8_SNORM: return CogsBin::Format::R8_SNORM; break;
196 case Cogs::TextureFormat::R8G8_SNORM: return CogsBin::Format::R8G8_SNORM; break;
197 case Cogs::TextureFormat::R8G8B8_SNORM: return CogsBin::Format::R8G8B8_SNORM; break;
198 case Cogs::TextureFormat::R8G8B8A8_SNORM: return CogsBin::Format::R8G8B8A8_SNORM; break;
199 case Cogs::TextureFormat::R16_SNORM: return CogsBin::Format::R16_SNORM; break;
200 case Cogs::TextureFormat::R16G16_SNORM: return CogsBin::Format::R16G16_SNORM; break;
201 case Cogs::TextureFormat::R16G16B16_SNORM: return CogsBin::Format::R16G16B16_SNORM; break;
202 case Cogs::TextureFormat::R16G16B16A16_SNORM: return CogsBin::Format::R16G16B16A16_SNORM; break;
203 case Cogs::TextureFormat::R8_UINT: return CogsBin::Format::R8_UINT; break;
204 case Cogs::TextureFormat::R8G8_UINT: return CogsBin::Format::R8G8_UINT; break;
205 case Cogs::TextureFormat::R8G8B8_UINT: return CogsBin::Format::R8G8B8_UINT; break;
206 case Cogs::TextureFormat::R8G8B8A8_UINT: return CogsBin::Format::R8G8B8A8_UINT; break;
207 case Cogs::TextureFormat::R16_UINT: return CogsBin::Format::R16_UINT; break;
208 case Cogs::TextureFormat::R16G16_UINT: return CogsBin::Format::R16G16_UINT; break;
209 case Cogs::TextureFormat::R16G16B16_UINT: return CogsBin::Format::R16G16B16_UINT; break;
210 case Cogs::TextureFormat::R16G16B16A16_UINT: return CogsBin::Format::R16G16B16A16_UINT; break;
211 case Cogs::TextureFormat::R32_UINT: return CogsBin::Format::R32_UINT; break;
212 case Cogs::TextureFormat::R32G32_UINT: return CogsBin::Format::R32G32_UINT; break;
213 case Cogs::TextureFormat::R32G32B32_UINT: return CogsBin::Format::R32G32B32_UINT; break;
214 case Cogs::TextureFormat::R32G32B32A32_UINT: return CogsBin::Format::R32G32B32A32_UINT; break;
215 case Cogs::TextureFormat::R8_SINT: return CogsBin::Format::R8_SINT; break;
216 case Cogs::TextureFormat::R8G8_SINT: return CogsBin::Format::R8G8_SINT; break;
217 case Cogs::TextureFormat::R8G8B8_SINT: return CogsBin::Format::R8G8B8_SINT; break;
218 case Cogs::TextureFormat::R8G8B8A8_SINT: return CogsBin::Format::R8G8B8A8_SINT; break;
219 case Cogs::TextureFormat::R16_SINT: return CogsBin::Format::R16_SINT; break;
220 case Cogs::TextureFormat::R16G16_SINT: return CogsBin::Format::R16G16_SINT; break;
221 case Cogs::TextureFormat::R16G16B16_SINT: return CogsBin::Format::R16G16B16_SINT; break;
222 case Cogs::TextureFormat::R16G16B16A16_SINT: return CogsBin::Format::R16G16B16A16_SINT; break;
223 case Cogs::TextureFormat::R32_SINT: return CogsBin::Format::R32_SINT; break;
224 case Cogs::TextureFormat::R32G32_SINT: return CogsBin::Format::R32G32_SINT; break;
225 case Cogs::TextureFormat::R32G32B32_SINT: return CogsBin::Format::R32G32B32_SINT; break;
226 case Cogs::TextureFormat::R32G32B32A32_SINT: return CogsBin::Format::R32G32B32A32_SINT; break;
227 case Cogs::TextureFormat::R16_FLOAT: return CogsBin::Format::R16_FLOAT; break;
228 case Cogs::TextureFormat::R16G16_FLOAT: return CogsBin::Format::R16G16_FLOAT; break;
229 case Cogs::TextureFormat::R16G16B16_FLOAT: return CogsBin::Format::R16G16B16_FLOAT; break;
230 case Cogs::TextureFormat::R16G16B16A16_FLOAT: return CogsBin::Format::R16G16B16A16_FLOAT; break;
231 case Cogs::TextureFormat::R32_FLOAT: return CogsBin::Format::R32_FLOAT; break;
232 case Cogs::TextureFormat::R32G32_FLOAT: return CogsBin::Format::R32G32_FLOAT; break;
233 case Cogs::TextureFormat::R32G32B32_FLOAT: return CogsBin::Format::R32G32B32_FLOAT; break;
234 case Cogs::TextureFormat::R32G32B32A32_FLOAT: return CogsBin::Format::R32G32B32A32_FLOAT; break;
235 case Cogs::TextureFormat::D16_UNORM: return CogsBin::Format::D16_UNORM; break;
236 case Cogs::TextureFormat::D24_UNORM: return CogsBin::Format::D24_UNORM; break;
237 case Cogs::TextureFormat::D24S8_UNORM: return CogsBin::Format::D24S8_UNORM; break;
238 case Cogs::TextureFormat::D32_FLOAT: return CogsBin::Format::D32_FLOAT; break;
239 case Cogs::TextureFormat::R32_TYPELESS: return CogsBin::Format::R32_TYPELESS; break;
240 case Cogs::TextureFormat::R16_TYPELESS: return CogsBin::Format::R16_TYPELESS; break;
241 case Cogs::TextureFormat::R8T: return CogsBin::Format::R8T; break;
242 case Cogs::TextureFormat::R8G8T: return CogsBin::Format::R8G8T; break;
243 case Cogs::TextureFormat::R8G8B8T: return CogsBin::Format::R8G8B8T; break;
244 case Cogs::TextureFormat::R8G8B8A8T: return CogsBin::Format::R8G8B8A8T; break;
245 case Cogs::TextureFormat::B8G8R8: return CogsBin::Format::B8G8R8; break;
246 case Cogs::TextureFormat::B8G8R8A8: return CogsBin::Format::B8G8R8A8; break;
247 case Cogs::TextureFormat::A8_UNORM: return CogsBin::Format::A8_UNORM; break;
248 case Cogs::TextureFormat::BC1_TYPELESS: return CogsBin::Format::BC1_TYPELESS; break;
249 case Cogs::TextureFormat::BC1_UNORM: return CogsBin::Format::BC1_UNORM; break;
250 case Cogs::TextureFormat::BC1_UNORM_SRGB: return CogsBin::Format::BC1_UNORM_SRGB; break;
251 case Cogs::TextureFormat::BC2_TYPELESS: return CogsBin::Format::BC2_TYPELESS; break;
252 case Cogs::TextureFormat::BC2_UNORM: return CogsBin::Format::BC2_UNORM; break;
253 case Cogs::TextureFormat::BC2_UNORM_SRGB: return CogsBin::Format::BC2_UNORM_SRGB; break;
254 case Cogs::TextureFormat::BC3_TYPELESS: return CogsBin::Format::BC3_TYPELESS; break;
255 case Cogs::TextureFormat::BC3_UNORM: return CogsBin::Format::BC3_UNORM; break;
256 case Cogs::TextureFormat::BC3_UNORM_SRGB: return CogsBin::Format::BC3_UNORM_SRGB; break;
257 case Cogs::TextureFormat::BC4_TYPELESS: return CogsBin::Format::BC4_TYPELESS; break;
258 case Cogs::TextureFormat::BC4_UNORM: return CogsBin::Format::BC4_UNORM; break;
259 case Cogs::TextureFormat::BC4_SNORM: return CogsBin::Format::BC4_SNORM; break;
260 case Cogs::TextureFormat::BC5_TYPELESS: return CogsBin::Format::BC5_TYPELESS; break;
261 case Cogs::TextureFormat::BC5_UNORM: return CogsBin::Format::BC5_UNORM; break;
262 case Cogs::TextureFormat::BC5_SNORM: return CogsBin::Format::BC5_SNORM; break;
263 case Cogs::TextureFormat::R8G8B8A8_UNORM_SRGB: return CogsBin::Format::R8G8B8A8_UNORM_SRGB; break;
264 default:
265 assert(false && "Illegal format");
266 break;
267 }
268 return CogsBin::Format::Unknown;
269 }
270
271 CogsBin::DataFormatV5 translateDataFormat(Cogs::DataFormat fmt)
272 {
273 switch (fmt) {
274 case Cogs::DataFormat::Unknown: return CogsBin::DataFormatV5::Unknown;
275 case Cogs::DataFormat::R8_UNORM: return CogsBin::DataFormatV5::R8_UNORM;
276 case Cogs::DataFormat::R8G8_UNORM: return CogsBin::DataFormatV5::R8G8_UNORM;
277 case Cogs::DataFormat::R8G8B8_UNORM: return CogsBin::DataFormatV5::R8G8B8_UNORM;
278 case Cogs::DataFormat::R8G8B8A8_UNORM: return CogsBin::DataFormatV5::R8G8B8A8_UNORM;
279 case Cogs::DataFormat::R16_UNORM: return CogsBin::DataFormatV5::R16_UNORM;
280 case Cogs::DataFormat::R16G16_UNORM: return CogsBin::DataFormatV5::R16G16_UNORM;
281 case Cogs::DataFormat::R16G16B16_UNORM: return CogsBin::DataFormatV5::R16G16B16_UNORM;
282 case Cogs::DataFormat::R16G16B16A16_UNORM: return CogsBin::DataFormatV5::R16G16B16A16_UNORM;
283 case Cogs::DataFormat::R8_SNORM: return CogsBin::DataFormatV5::R8_SNORM;
284 case Cogs::DataFormat::R8G8_SNORM: return CogsBin::DataFormatV5::R8G8_SNORM;
285 case Cogs::DataFormat::R8G8B8_SNORM: return CogsBin::DataFormatV5::R8G8B8_SNORM;
286 case Cogs::DataFormat::R8G8B8A8_SNORM: return CogsBin::DataFormatV5::R8G8B8A8_SNORM;
287 case Cogs::DataFormat::R16_SNORM: return CogsBin::DataFormatV5::R16_SNORM;
288 case Cogs::DataFormat::R16G16_SNORM: return CogsBin::DataFormatV5::R16G16_SNORM;
289 case Cogs::DataFormat::R16G16B16_SNORM: return CogsBin::DataFormatV5::R16G16B16_SNORM;
290 case Cogs::DataFormat::R16G16B16A16_SNORM: return CogsBin::DataFormatV5::R16G16B16A16_SNORM;
291 case Cogs::DataFormat::R8_UINT: return CogsBin::DataFormatV5::R8_UINT;
292 case Cogs::DataFormat::R8G8_UINT: return CogsBin::DataFormatV5::R8G8_UINT;
293 case Cogs::DataFormat::R8G8B8_UINT: return CogsBin::DataFormatV5::R8G8B8_UINT;
294 case Cogs::DataFormat::R8G8B8A8_UINT: return CogsBin::DataFormatV5::R8G8B8A8_UINT;
295 case Cogs::DataFormat::R16_UINT: return CogsBin::DataFormatV5::R16_UINT;
296 case Cogs::DataFormat::R16G16_UINT: return CogsBin::DataFormatV5::R16G16_UINT;
297 case Cogs::DataFormat::R16G16B16_UINT: return CogsBin::DataFormatV5::R16G16B16_UINT;
298 case Cogs::DataFormat::R16G16B16A16_UINT: return CogsBin::DataFormatV5::R16G16B16A16_UINT;
299 case Cogs::DataFormat::R32_UINT: return CogsBin::DataFormatV5::R32_UINT;
300 case Cogs::DataFormat::R32G32_UINT: return CogsBin::DataFormatV5::R32G32_UINT;
301 case Cogs::DataFormat::R32G32B32_UINT: return CogsBin::DataFormatV5::R32G32B32_UINT;
302 case Cogs::DataFormat::R32G32B32A32_UINT: return CogsBin::DataFormatV5::R32G32B32A32_UINT;
303 case Cogs::DataFormat::R8_SINT: return CogsBin::DataFormatV5::R8_SINT;
304 case Cogs::DataFormat::R8G8_SINT: return CogsBin::DataFormatV5::R8G8_SINT;
305 case Cogs::DataFormat::R8G8B8_SINT: return CogsBin::DataFormatV5::R8G8B8_SINT;
306 case Cogs::DataFormat::R8G8B8A8_SINT: return CogsBin::DataFormatV5::R8G8B8A8_SINT;
307 case Cogs::DataFormat::R16_SINT: return CogsBin::DataFormatV5::R16_SINT;
308 case Cogs::DataFormat::R16G16_SINT: return CogsBin::DataFormatV5::R16G16_SINT;
309 case Cogs::DataFormat::R16G16B16_SINT: return CogsBin::DataFormatV5::R16G16B16_SINT;
310 case Cogs::DataFormat::R16G16B16A16_SINT: return CogsBin::DataFormatV5::R16G16B16A16_SINT;
311 case Cogs::DataFormat::R32_SINT: return CogsBin::DataFormatV5::R32_SINT;
312 case Cogs::DataFormat::R32G32_SINT: return CogsBin::DataFormatV5::R32G32_SINT;
313 case Cogs::DataFormat::R32G32B32_SINT: return CogsBin::DataFormatV5::R32G32B32_SINT;
314 case Cogs::DataFormat::R32G32B32A32_SINT: return CogsBin::DataFormatV5::R32G32B32A32_SINT;
315 case Cogs::DataFormat::R16_FLOAT: return CogsBin::DataFormatV5::R16_FLOAT;
316 case Cogs::DataFormat::R16G16_FLOAT: return CogsBin::DataFormatV5::R16G16_FLOAT;
317 case Cogs::DataFormat::R16G16B16_FLOAT: return CogsBin::DataFormatV5::R16G16B16_FLOAT;
318 case Cogs::DataFormat::R16G16B16A16_FLOAT: return CogsBin::DataFormatV5::R16G16B16A16_FLOAT;
319 case Cogs::DataFormat::R32_FLOAT: return CogsBin::DataFormatV5::R32_FLOAT;
320 case Cogs::DataFormat::R32G32_FLOAT: return CogsBin::DataFormatV5::R32G32_FLOAT;
321 case Cogs::DataFormat::R32G32B32_FLOAT: return CogsBin::DataFormatV5::R32G32B32_FLOAT;
322 case Cogs::DataFormat::R32G32B32A32_FLOAT: return CogsBin::DataFormatV5::R32G32B32A32_FLOAT;
323 case Cogs::DataFormat::D16_UNORM: return CogsBin::DataFormatV5::D16_UNORM;
324 case Cogs::DataFormat::D24_UNORM: return CogsBin::DataFormatV5::D24_UNORM;
325 case Cogs::DataFormat::D24S8_UNORM: return CogsBin::DataFormatV5::D24S8_UNORM;
326 case Cogs::DataFormat::D32_FLOAT: return CogsBin::DataFormatV5::D32_FLOAT;
327 case Cogs::DataFormat::R32_TYPELESS: return CogsBin::DataFormatV5::R32_TYPELESS;
328 case Cogs::DataFormat::R16_TYPELESS: return CogsBin::DataFormatV5::R16_TYPELESS;
329 case Cogs::DataFormat::R8T: return CogsBin::DataFormatV5::R8T;
330 case Cogs::DataFormat::R8G8T: return CogsBin::DataFormatV5::R8G8T;
331 case Cogs::DataFormat::R8G8B8T: return CogsBin::DataFormatV5::R8G8B8T;
332 case Cogs::DataFormat::R8G8B8A8T: return CogsBin::DataFormatV5::R8G8B8A8T;
333 case Cogs::DataFormat::B8G8R8: return CogsBin::DataFormatV5::B8G8R8;
334 case Cogs::DataFormat::B8G8R8A8: return CogsBin::DataFormatV5::B8G8R8A8;
335 case Cogs::DataFormat::A8_UNORM: return CogsBin::DataFormatV5::A8_UNORM;
336 case Cogs::DataFormat::BC1_TYPELESS: return CogsBin::DataFormatV5::BC1_TYPELESS;
337 case Cogs::DataFormat::BC1_UNORM: return CogsBin::DataFormatV5::BC1_UNORM;
338 case Cogs::DataFormat::BC1_UNORM_SRGB: return CogsBin::DataFormatV5::BC1_UNORM_SRGB;
339 case Cogs::DataFormat::BC2_TYPELESS: return CogsBin::DataFormatV5::BC2_TYPELESS;
340 case Cogs::DataFormat::BC2_UNORM: return CogsBin::DataFormatV5::BC2_UNORM;
341 case Cogs::DataFormat::BC2_UNORM_SRGB: return CogsBin::DataFormatV5::BC2_UNORM_SRGB;
342 case Cogs::DataFormat::BC3_TYPELESS: return CogsBin::DataFormatV5::BC3_TYPELESS;
343 case Cogs::DataFormat::BC3_UNORM: return CogsBin::DataFormatV5::BC3_UNORM;
344 case Cogs::DataFormat::BC3_UNORM_SRGB: return CogsBin::DataFormatV5::BC3_UNORM_SRGB;
345 case Cogs::DataFormat::BC4_TYPELESS: return CogsBin::DataFormatV5::BC4_TYPELESS;
346 case Cogs::DataFormat::BC4_UNORM: return CogsBin::DataFormatV5::BC4_UNORM;
347 case Cogs::DataFormat::BC4_SNORM: return CogsBin::DataFormatV5::BC4_SNORM;
348 case Cogs::DataFormat::BC5_TYPELESS: return CogsBin::DataFormatV5::BC5_TYPELESS;
349 case Cogs::DataFormat::BC5_UNORM: return CogsBin::DataFormatV5::BC5_UNORM;
350 case Cogs::DataFormat::BC5_SNORM: return CogsBin::DataFormatV5::BC5_SNORM;
351 case Cogs::DataFormat::BC6H_SIGNED_FLOAT_RGB: return CogsBin::DataFormatV5::BC6H_SIGNED_FLOAT_RGB;
352 case Cogs::DataFormat::BC6H_UNSIGNED_FLOAT_RGB: return CogsBin::DataFormatV5::BC6H_UNSIGNED_FLOAT_RGB;
353 case Cogs::DataFormat::BC7_UNORM_RGBA: return CogsBin::DataFormatV5::BC7_UNORM_RGBA;
354 case Cogs::DataFormat::BC7_UNORM_SRGBA: return CogsBin::DataFormatV5::BC7_UNORM_SRGBA;
355 case Cogs::DataFormat::PVRTC1_2BPP_UNORM_RGB: return CogsBin::DataFormatV5::PVRTC1_2BPP_UNORM_RGB;
356 case Cogs::DataFormat::PVRTC1_4BPP_UNORM_RGB: return CogsBin::DataFormatV5::PVRTC1_4BPP_UNORM_RGB;
357 case Cogs::DataFormat::PVRTC1_2BPP_UNORM_RGBA: return CogsBin::DataFormatV5::PVRTC1_2BPP_UNORM_RGBA;
358 case Cogs::DataFormat::PVRTC1_4BPP_UNORM_RGBA: return CogsBin::DataFormatV5::PVRTC1_4BPP_UNORM_RGBA;
359 case Cogs::DataFormat::EAC_BLOCK8_UNSIGNED_FLOAT_R: return CogsBin::DataFormatV5::EAC_BLOCK8_UNSIGNED_FLOAT_R;
360 case Cogs::DataFormat::EAC_BLOCK8_SIGNED_FLOAT_R: return CogsBin::DataFormatV5::EAC_BLOCK8_SIGNED_FLOAT_R;
361 case Cogs::DataFormat::EAC_BLOCK16_UNSIGNED_FLOAT_RG: return CogsBin::DataFormatV5::EAC_BLOCK16_UNSIGNED_FLOAT_RG;
362 case Cogs::DataFormat::EAC_BLOCK16_SIGNED_FLOAT_RG: return CogsBin::DataFormatV5::EAC_BLOCK16_SIGNED_FLOAT_RG;
363 case Cogs::DataFormat::ETC2_BLOCK8_UNORM_RGB: return CogsBin::DataFormatV5::ETC2_BLOCK8_UNORM_RGB;
364 case Cogs::DataFormat::ETC2_BLOCK8_UNORM_SRGB: return CogsBin::DataFormatV5::ETC2_BLOCK8_UNORM_SRGB;
365 case Cogs::DataFormat::ETC2_BLOCK16_UNORM_RGBA: return CogsBin::DataFormatV5::ETC2_BLOCK16_UNORM_RGBA;
366 case Cogs::DataFormat::ETC2_BLOCK16_UNORM_SRGBA: return CogsBin::DataFormatV5::ETC2_BLOCK16_UNORM_SRGBA;
367 case Cogs::DataFormat::ASTC_4x4_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_4x4_UNORM_RGBA;
368 case Cogs::DataFormat::ASTC_5x4_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_5x4_UNORM_RGBA;
369 case Cogs::DataFormat::ASTC_5x5_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_5x5_UNORM_RGBA;
370 case Cogs::DataFormat::ASTC_6x5_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_6x5_UNORM_RGBA;
371 case Cogs::DataFormat::ASTC_6x6_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_6x6_UNORM_RGBA;
372 case Cogs::DataFormat::ASTC_8x5_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_8x5_UNORM_RGBA;
373 case Cogs::DataFormat::ASTC_8x6_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_8x6_UNORM_RGBA;
374 case Cogs::DataFormat::ASTC_8x8_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_8x8_UNORM_RGBA;
375 case Cogs::DataFormat::ASTC_10x5_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_10x5_UNORM_RGBA;
376 case Cogs::DataFormat::ASTC_10x6_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_10x6_UNORM_RGBA;
377 case Cogs::DataFormat::ASTC_10x8_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_10x8_UNORM_RGBA;
378 case Cogs::DataFormat::ASTC_10x10_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_10x10_UNORM_RGBA;
379 case Cogs::DataFormat::ASTC_12x10_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_12x10_UNORM_RGBA;
380 case Cogs::DataFormat::ASTC_12x12_UNORM_RGBA: return CogsBin::DataFormatV5::ASTC_12x12_UNORM_RGBA;
381 case Cogs::DataFormat::ASTC_4x4_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_4x4_UNORM_SRGBA;
382 case Cogs::DataFormat::ASTC_5x4_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_5x4_UNORM_SRGBA;
383 case Cogs::DataFormat::ASTC_5x5_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_5x5_UNORM_SRGBA;
384 case Cogs::DataFormat::ASTC_6x5_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_6x5_UNORM_SRGBA;
385 case Cogs::DataFormat::ASTC_6x6_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_6x6_UNORM_SRGBA;
386 case Cogs::DataFormat::ASTC_8x5_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_8x5_UNORM_SRGBA;
387 case Cogs::DataFormat::ASTC_8x6_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_8x6_UNORM_SRGBA;
388 case Cogs::DataFormat::ASTC_8x8_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_8x8_UNORM_SRGBA;
389 case Cogs::DataFormat::ASTC_10x5_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_10x5_UNORM_SRGBA;
390 case Cogs::DataFormat::ASTC_10x6_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_10x6_UNORM_SRGBA;
391 case Cogs::DataFormat::ASTC_10x8_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_10x8_UNORM_SRGBA;
392 case Cogs::DataFormat::ASTC_10x10_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_10x10_UNORM_SRGBA;
393 case Cogs::DataFormat::ASTC_12x10_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_12x10_UNORM_SRGBA;
394 case Cogs::DataFormat::ASTC_12x12_UNORM_SRGBA: return CogsBin::DataFormatV5::ASTC_12x12_UNORM_SRGBA;
395 case Cogs::DataFormat::R8G8B8_UNORM_SRGB: return CogsBin::DataFormatV5::R8G8B8_UNORM_SRGB;
396 case Cogs::DataFormat::R8G8B8A8_UNORM_SRGB: return CogsBin::DataFormatV5::R8G8B8A8_UNORM_SRGB;
397 case Cogs::DataFormat::R10G10B10A2_TYPELESS: return CogsBin::DataFormatV5::R10G10B10A2_TYPELESS;
398 case Cogs::DataFormat::R10G10B10A2_UNORM: return CogsBin::DataFormatV5::R10G10B10A2_UNORM;
399 case Cogs::DataFormat::R10G10B10A2_UINT: return CogsBin::DataFormatV5::R10G10B10A2_UINT;
400 case Cogs::DataFormat::R11G11B10_FLOAT: return CogsBin::DataFormatV5::R11G11B10_FLOAT;
401 case Cogs::DataFormat::R5G6B5_UNORM: return CogsBin::DataFormatV5::R5G6B5_UNORM;
402 case Cogs::DataFormat::R5G5B5A1_UNORM: return CogsBin::DataFormatV5::R5G5B5A1_UNORM;
403 case Cogs::DataFormat::R4G4B4A4_UNORM: return CogsBin::DataFormatV5::R4G4B4A4_UNORM;
404 case Cogs::DataFormat::R9G9B9E5_FLOAT: return CogsBin::DataFormatV5::R9G9B9E5_FLOAT;
405 case Cogs::DataFormat::MAT4X4_FLOAT: return CogsBin::DataFormatV5::MAT4X4_FLOAT;
406 default:
407 assert(false && "Invalid data format");
408 return CogsBin::DataFormatV5::Unknown;
409 }
410 }
411
412 CogsBin::TextureDimensions translateTextureDimensions(Cogs::ResourceDimensions dimensions)
413 {
414 switch (dimensions) {
415 case Cogs::ResourceDimensions::Unknown: return CogsBin::TextureDimensions::Unknown; break;
416 case Cogs::ResourceDimensions::Buffer: return CogsBin::TextureDimensions::Buffer; break;
417 case Cogs::ResourceDimensions::Texture1D: return CogsBin::TextureDimensions::Texture1D; break;
418 case Cogs::ResourceDimensions::Texture1DArray: return CogsBin::TextureDimensions::Texture1DArray; break;
419 case Cogs::ResourceDimensions::Texture2D: return CogsBin::TextureDimensions::Texture2D; break;
420 case Cogs::ResourceDimensions::Texture2DArray: return CogsBin::TextureDimensions::Texture2DArray; break;
421 case Cogs::ResourceDimensions::Texture2DMS: return CogsBin::TextureDimensions::Texture2DMS; break;
422 case Cogs::ResourceDimensions::Texture2DMSArray: return CogsBin::TextureDimensions::Texture2DMSArray; break;
423 case Cogs::ResourceDimensions::Texture3D: return CogsBin::TextureDimensions::Texture3D; break;
424 case Cogs::ResourceDimensions::Texture3DArray: return CogsBin::TextureDimensions::Texture3DArray; break;
425 case Cogs::ResourceDimensions::TextureCube: return CogsBin::TextureDimensions::TextureCube; break;
426 case Cogs::ResourceDimensions::TextureCubeArray: return CogsBin::TextureDimensions::TextureCubeArray; break;
427 default:
428 assert(false && "Illegal texture dimension");
429 break;
430 }
431 return CogsBin::TextureDimensions::Unknown;
432 }
433
434 CogsBin::TextureAddressMode translateTextureAddressMode(Cogs::SamplerState::AddressMode addressMode)
435 {
436 switch (addressMode) {
437 case Cogs::SamplerState::AddressMode::Clamp: return CogsBin::TextureAddressMode::Clamp; break;
438 case Cogs::SamplerState::AddressMode::Wrap: return CogsBin::TextureAddressMode::Wrap; break;
439 case Cogs::SamplerState::AddressMode::Mirror: return CogsBin::TextureAddressMode::Mirror; break;
440 case Cogs::SamplerState::AddressMode::Border: return CogsBin::TextureAddressMode::Border; break;
441 default:
442 assert(false && "Illegal texture addressing mode");
443 break;
444 }
445 return CogsBin::TextureAddressMode::Wrap;
446 }
447
448 CogsBin::TextureFilterMode translateTextureFilterMode(Cogs::SamplerState::FilterMode filterMode)
449 {
450 switch (filterMode) {
451 case Cogs::SamplerState::FilterMode::MinMagMipPoint: return CogsBin::TextureFilterMode::MinMagMipPoint; break;
452 case Cogs::SamplerState::FilterMode::MinMagMipLinear: return CogsBin::TextureFilterMode::MinMagMipLinear; break;
453 case Cogs::SamplerState::FilterMode::ComparisonMinMagMipPoint: return CogsBin::TextureFilterMode::ComparisonMinMagMipPoint; break;
454 case Cogs::SamplerState::FilterMode::ComparisonMinMagMipLinear: return CogsBin::TextureFilterMode::ComparisonMinMagMipLinear; break;
455 default:
456 assert(false && "Illegal texture filter mode");
457 break;
458 }
459 return CogsBin::TextureFilterMode::MinMagMipLinear;
460 }
461
462 CogsBin::TextureComparisonFunction translateTextureComparisonFunc(Cogs::SamplerState::ComparisonFunction compFunc)
463 {
464 switch (compFunc) {
465 case Cogs::SamplerState::ComparisonFunction::Never: return CogsBin::TextureComparisonFunction::Never; break;
466 case Cogs::SamplerState::ComparisonFunction::Less: return CogsBin::TextureComparisonFunction::Less; break;
467 case Cogs::SamplerState::ComparisonFunction::Equal: return CogsBin::TextureComparisonFunction::Equal; break;
468 case Cogs::SamplerState::ComparisonFunction::LessEqual: return CogsBin::TextureComparisonFunction::LessEqual; break;
469 case Cogs::SamplerState::ComparisonFunction::Greater: return CogsBin::TextureComparisonFunction::Greater; break;
470 case Cogs::SamplerState::ComparisonFunction::NotEqual: return CogsBin::TextureComparisonFunction::NotEqual; break;
471 case Cogs::SamplerState::ComparisonFunction::GreaterEqual: return CogsBin::TextureComparisonFunction::GreaterEqual; break;
472 case Cogs::SamplerState::ComparisonFunction::Always: return CogsBin::TextureComparisonFunction::Always; break;
473 default:
474 assert(false && "Illegal texture comparison function");
475 break;
476 }
477 return CogsBin::TextureComparisonFunction::Never;
478 }
479
480 WSection& getSection(WriteContext& wctx, CogsBin::SectionType type, uint16_t index, bool create=true)
481 {
482 if (wctx.sections.size() <= index) wctx.sections.resize((size_t)index + 1);
483 if (!wctx.sections[index]) {
484 assert(create && "Trying to retrieve non-existing section");
485 wctx.sections[index] = std::make_unique<WSection>(WSection{ type, index, 0, 0, {}, {} });
486 }
487 auto & section = *wctx.sections[index].get();
488 section.type = section.type | type;
489
490 return section;
491 }
492
493 uint32_t addMetaSubsection(WriteContext& /*wctx*/, WSection& section, CogsBin::SubSectionType type, size_t size)
494 {
495 assert(section.contents.empty() && "Cannot add metasubsections after data payload has been added.");
496 assert(size < std::numeric_limits<uint32_t>::max());
497
498 auto index = nextIndex(section.subSections);
499 section.subSections.push_back(WSubSection{ ~0u, uint32_t(size), type });
500 return index;
501 }
502
503 uint32_t newNode(WriteContext& wctx)
504 {
505 uint32_t index = nextIndex(wctx.nodes);
506 auto & node = wctx.nodes.emplace_back();
507 node.parent = ~0u;
508 node.name = ~0u;
509 node.mesh = ~0u;
510 node.materialInstance = ~0u;
511 node.transform = ~0u;
512 node.boundingBox = ~0u;
513 node.primitiveType = CogsBin::PrimitiveType::TriangleList;
514 node.vertexFirst = 0;
515 node.vertexCount = 0;
516 return index;
517 }
518
519 uint32_t recordTransform(WriteContext& wctx, const glm::mat4& M)
520 {
521 glm::mat4 I;
522 if (std::memcmp(glm::value_ptr(I), glm::value_ptr(M), sizeof(M)) == 0) {
523 //LOG_DEBUG(logger, "Identity matrix.");
524 return ~0u;
525 }
526
527 // assume affine transform matrix
528 const auto * m = glm::value_ptr(M);
530 m[0], m[1], m[2],
531 m[4], m[5], m[6],
532 m[8], m[9], m[10],
533 m[12], m[13], m[14]
534 };
535 size_t hashValue = T.hash();
536
537 if (auto it = wctx.transformByHash.find(hashValue); it != wctx.transformByHash.end()) {
538 if (std::memcmp(&wctx.transforms[it->second], &T, sizeof(T)) == 0) {
539 //LOG_DEBUG(logger, "Found duplicate transform.");
540 return it->second;
541 }
542 }
543
544 auto index = nextIndex(wctx.transforms);
545 wctx.transforms.emplace_back(T);
546 wctx.transformByHash[hashValue] = index;
547 return index;
548 }
549
550 uint32_t recordBoundingBox(WriteContext& wctx, const Cogs::Geometry::BoundingBox& bbox)
551 {
552 if (isEmpty(bbox)) {
553 //LOG_DEBUG(logger, "Empty bbox.\n");
554 return ~0u;
555 }
556
557 // FIXME (chrisdy): Not really sure if this is worthwhile
558 size_t hashValue = bbox.hash();
559 if (auto it = wctx.boundingBoxByHash.find(hashValue); it != wctx.boundingBoxByHash.end()) {
560 if (std::memcmp(&wctx.boundingBoxes[it->second], &bbox, sizeof(bbox)) == 0) {
561 LOG_DEBUG(logger, "Found duplicate bounding box.");
562 return it->second;
563 }
564 }
565
566 auto index = nextIndex(wctx.boundingBoxes);
567 wctx.boundingBoxes.emplace_back(bbox);
568 wctx.transformByHash[hashValue] = index;
569 return index;
570 }
571
572 size_t getMaterialInstanceHash(MaterialInstance * material)
573 {
574 size_t hashValue = material->getName().hash();
575
576 if (material->material->getName() == "DefaultMaterial") {
577 hashValue = Cogs::hash(material->getVec4Property(DefaultMaterial::DiffuseColor), hashValue);
578 hashValue = Cogs::hash(material->getVec3Property(DefaultMaterial::SpecularColor), hashValue);
579 hashValue = Cogs::hash(material->getVec3Property(DefaultMaterial::EmissiveColor), hashValue);
580 hashValue = Cogs::hash(material->getFloatProperty(DefaultMaterial::SpecularPower), hashValue);
581 hashValue = material->getTextureProperty(DefaultMaterial::DiffuseMap).hash(hashValue);
582 }
583 else {
584 hashValue = Cogs::hash(reinterpret_cast<intptr_t>(material));
585 }
586 return hashValue;
587 }
588
589 uint32_t recordString(WriteContext& wctx, Cogs::StringView string)
590 {
591 if (string.empty()) return ~0u;
592
593 auto hash = string.hash();
594 if (auto it = wctx.stringByHash.find(hash); it != wctx.stringByHash.end()) {
595 auto & str = wctx.strings[it->second];
596 if (string == Cogs::StringView((const char*)(wctx.stringData.data() + str.offset), str.length)) {
597 return it->second;
598 }
599 }
600
601 auto offset = wctx.stringData.size();
602 assert(offset < std::numeric_limits<uint32_t>::max());
603
604 auto length = string.length();
605 assert(length < std::numeric_limits<uint32_t>::max());
606
607 auto index = nextIndex(wctx.strings);
608 wctx.strings.emplace_back(CogsBin::String{ uint32_t(offset), uint32_t(length) });
609
610 wctx.stringData.resize(offset + length);
611 std::memcpy(wctx.stringData.data() + offset, string.begin(), length);
612
613 wctx.stringByHash[hash] = index;
614 return index;
615 }
616
617 uint32_t recordAttribute(WriteContext& wctx, const Cogs::VertexElement& element)
618 {
619 assert(element.offset <= std::numeric_limits<uint16_t>::max());
620
621 auto index = nextIndex(wctx.vertexAttributes);
622
623 wctx.vertexAttributes.emplace_back(CogsBin::VertexAttribute{});
624 auto & attribute = wctx.vertexAttributes.back();
625
626 attribute.offset = uint16_t(element.offset);
627 attribute.format = static_cast<CogsBin::Format>(translateDataFormat(element.format));
628
629 switch (element.semantic) {
630 case Cogs::ElementSemantic::Position: attribute.semantic = CogsBin::Semantic::Position; break;
631 case Cogs::ElementSemantic::Normal: attribute.semantic = CogsBin::Semantic::Normal; break;
632 case Cogs::ElementSemantic::Color: attribute.semantic = CogsBin::Semantic::Color; break;
633 case Cogs::ElementSemantic::TextureCoordinate: attribute.semantic = CogsBin::Semantic::TexCoord; break;
634 case Cogs::ElementSemantic::Tangent: attribute.semantic = CogsBin::Semantic::Tangent; break;
635 case Cogs::ElementSemantic::InstanceVector: attribute.semantic = CogsBin::Semantic::InstanceVector; break;
636 case Cogs::ElementSemantic::InstanceMatrix: attribute.semantic = CogsBin::Semantic::InstanceMatrix; break;
637 default:
638 assert(false && "Unknown element.format");
639 }
640 assert(element.semanticIndex <= std::numeric_limits<uint8_t>::max());
641 attribute.semanticIndex = uint8_t(element.semanticIndex);
642
643 if (element.inputType == Cogs::InputType::VertexData) {
644 attribute.instanceStepRate = 0;
645 }
646 else {
647 attribute.instanceStepRate = std::max(uint16_t(1), element.instanceStep);
648 }
649 return index;
650 }
651
652 bool match(const Cogs::VertexFormat& a, const Cogs::VertexFormat& b)
653 {
654 if (a.elements.size() != b.elements.size()) return false;
655 for (size_t i = 0; i < a.elements.size(); i++) {
656 const auto & ae = a.elements[i];
657 const auto & be = b.elements[i];
658
659 if ((ae.offset != be.offset) ||
660 (ae.format != be.format) ||
661 (ae.semantic != be.semantic) ||
662 (ae.semanticIndex != be.semanticIndex) ||
663 (ae.inputType != be.inputType) ||
664 (ae.instanceStep != be.instanceStep))
665 {
666 return false;
667 }
668 }
669 return true;
670 }
671
672 uint32_t recordBuffer(WriteContext& wctx, uint64_t size, CogsBin::BufferContentFlags flags, uint32_t alignment, uint16_t sectionIx)
673 {
674 assert(alignment);
675 assert(size);
676 assert(alignment <= CogsBin::CogsSectionAlignment);
677
678 const auto lowerMask = uint64_t(alignment - 1);
679
680 auto & section = getSection(wctx, CogsBin::SectionType::DATA, sectionIx);
681 section.dataSize = (section.dataSize + lowerMask)&(~lowerMask);
682 section.dataSize += size;
683
684 auto index = nextIndex(wctx.buffers);
685 wctx.buffers.emplace_back(CogsBin::Buffer{});
686 wctx.bufferContexts.emplace_back();
687
688 auto & buffer = wctx.buffers.back();
689 buffer.sectionOffset = sectionIx;
690 //TODO: Review buffer size usage.
691 buffer.size = (uint32_t)size;
692 buffer.flags = flags;
693
694 wctx.bufferContexts[index].contents.resize(size, false);
695 wctx.bufferContexts[index].alignment = alignment;
696
697 return index;
698 }
699
700 uint8_t* getBufferPointer(WriteContext& wctx, uint32_t bufferIx, uint32_t offset, uint64_t size)
701 {
702 assert(bufferIx < wctx.buffers.size());
703 auto & bufferCtx = wctx.bufferContexts[bufferIx];
704 assert(offset + size <= bufferCtx.contents.size());
705 return (uint8_t*)bufferCtx.contents.data() + offset;
706 }
707
708 uint32_t recordBufferResource(WriteContext& wctx, BufferResource* srcBuffer, uint16_t sectionIx)
709 {
710 if (srcBuffer == nullptr || srcBuffer->empty()) {
711 return ~0u;
712 }
713
714 if (auto it = wctx.bufferByPointer.find(srcBuffer); it != wctx.bufferByPointer.end()) {
715 return it->second;
716 }
717
718 CogsBin::BufferContentFlags flags = CogsBin::BufferContentFlags::None;
719 if (srcBuffer->isVertexBuffer()) flags |= CogsBin::BufferContentFlags::VertexData;
720 if (srcBuffer->isIndexBuffer()) flags |= CogsBin::BufferContentFlags::IndexData;
721
722 constexpr uint32_t bufferStride = 16;
723 auto alignment = std::min(uint32_t(CogsBin::CogsSectionAlignment), uint32_t(bufferStride)); // No point in aligning beyond a cache line.
724 uint32_t index = recordBuffer(wctx, srcBuffer->size(), flags, alignment, sectionIx);
725 std::memcpy(getBufferPointer(wctx, index, 0, srcBuffer->size()), srcBuffer->data(), srcBuffer->size());
726
727 wctx.bufferByPointer[srcBuffer] = index;
728
729 return index;
730 }
731
732 void recordVertexFormat(WriteContext& wctx, CogsBin::VertexStream& stream, const DataStream& srcStream)
733 {
734 const auto & srcFormat = *Cogs::VertexFormats::getVertexFormat(srcStream.format);
735 assert(srcFormat.size <= std::numeric_limits<uint16_t>::max());
736 stream.stride = uint16_t(srcFormat.size);
737
738 assert(srcFormat.elements.size() < std::numeric_limits<uint32_t>::max());
739 stream.attributeCount = uint32_t(srcFormat.elements.size());
740
741 auto hash = Cogs::getHash(srcFormat);
742 assert(hash);
743 if (auto it = wctx.vertexFormatByHash.find(hash); it != wctx.vertexFormatByHash.end() && match(*it->second.format, srcFormat)) {
744 stream.firstAttribute = it->second.index;
745 }
746 else {
747 stream.firstAttribute = recordAttribute(wctx, srcFormat.elements[0]);
748 for (size_t i = 1; i < srcFormat.elements.size(); i++) {
749 auto ix = recordAttribute(wctx, srcFormat.elements[i]);
750 assert(stream.firstAttribute + i == ix);
751 }
752 wctx.vertexFormatByHash[hash] = WVertexFormatRec{ &srcFormat, stream.firstAttribute };
753 }
754 }
755
756 uint32_t recordVertexStream(WriteContext& wctx, const DataStream& srcStream, uint16_t sectionIx)
757 {
758 assert(srcStream.size() <= std::numeric_limits<uint32_t>::max());
759
760 auto index = nextIndex(wctx.vertexStreams);
761 auto & stream = wctx.vertexStreams.emplace_back(CogsBin::VertexStream{ 0, 0, 0, 0, 0, 0, 0, 0 });
762 stream.firstAttribute = ~0u;
763 stream.vertexDataType = srcStream.type;
764 stream.encoding = 0;
765
766 if (srcStream.format) {
767 recordVertexFormat(wctx, stream, srcStream);
768 } else {
769 stream.stride = (uint16_t)srcStream.stride;
770 }
771
772 assert((size_t)stream.count * (size_t)stream.stride <= srcStream.size());
773
774 stream.count = srcStream.numElements;
775 stream.buffer = recordBufferResource(wctx, srcStream.buffer.resolve(), sectionIx);
776 stream.offset = srcStream.offset;
777
778 return index;
779 }
780
781 uint32_t recordMesh(WriteContext& wctx, Mesh* srcMesh, uint16_t section)
782 {
783 if (srcMesh == nullptr || srcMesh->streams.empty()) {
784 return ~0u;
785 }
786
787 if (auto it = wctx.meshByPointer.find(srcMesh); it != wctx.meshByPointer.end()) {
788 LOG_DEBUG(logger, "Duplicate mesh, recycling.");
789 return it->second;
790 }
791
792 uint32_t firstStream = 0;
793 uint32_t streamCount = 0;
794 for (size_t i = 0; i < srcMesh->streams.size(); i++) {
795 if (srcMesh->streams[i].size() != 0) {
796 uint32_t streamIx = recordVertexStream(wctx, srcMesh->streams[i], section);
797 if (streamCount == 0) {
798 firstStream = streamIx;
799 }
800 assert(firstStream + streamCount == streamIx);
801 streamCount++;
802 }
803 }
804
805 auto index = nextIndex(wctx.meshes);
806 auto & mesh = wctx.meshes.emplace_back();
807 mesh.name = recordString(wctx, srcMesh->getName());
808 mesh.firstStream = firstStream;
809 mesh.streamCount = (uint8_t)streamCount;
810 mesh.boundingBox = recordBoundingBox(wctx, srcMesh->boundingBox);
811 mesh.flags =
812 (srcMesh->isCCW() ? CogsBin::MeshFlags::None : CogsBin::MeshFlags::Clockwise) |
813 (srcMesh->isMeshFlagSet(MeshFlags::Skinned) ? CogsBin::MeshFlags::Skinned : CogsBin::MeshFlags::None);
814
815 mesh.primitiveType = translatePrimitiveType(wctx, srcMesh->primitiveType);
816
817 wctx.meshByPointer[srcMesh] = index;
818
819 return index;
820 }
821
822 template<typename T>
823 uint32_t recordProperty(WriteContext& wctx, const Cogs::StringView& key, CogsBin::PropertyValueType valueType, const T & value)
824 {
825 auto keyIx = recordString(wctx, key);;
826
827 // TODO (chrisdy): Check if this caching is worthwhile in practice.
828 size_t hash = Cogs::hashSequence(keyIx, valueType, value);
829 if (auto it = wctx.propByHash.find(hash); it != wctx.propByHash.end()) {
830 auto & p = wctx.properties[it->second];
831 if (p.keyType == CogsBin::PropertyKeyType::String &&
832 p.key == keyIx &&
833 p.valueType == valueType)
834 {
835 if constexpr (sizeof(T) <= sizeof(uint32_t)) {
836 if (*(T*)(&p.value) == value) {
837 //LOG_DEBUG(logger, "Found duplicate property (small), recycling.");
838 return it->second;
839 }
840 }
841 else {
842 if (*(T*)(wctx.propertyData.data() + p.value) == value) {
843 //LOG_DEBUG(logger, "Found duplicate property (large), recycling.");
844 return it->second;
845 }
846 }
847 }
848 }
849
850 auto index = nextIndex(wctx.properties);
851 wctx.properties.emplace_back(CogsBin::Property{ CogsBin::PropertyKeyType::String, valueType, keyIx, ~0u });
852 auto & prop = wctx.properties.back();
853
854 if constexpr (sizeof(T) <= sizeof(uint32_t)) { // values that fit inside the index are stored inline
855 *(T*)(&prop.value) = value;
856 }
857 else {
858 constexpr auto align = alignof(T);
859 static_assert(align != 0 && (align & (align - 1)) == 0 && "alignment is not a power of two");
860 const auto mask = align - 1;
861 auto offset = (wctx.propertyData.size() + mask) & ~mask; // pad to natural alignment
862 assert(offset < std::numeric_limits<uint32_t>::max());
863 prop.value = uint32_t(offset);
864 wctx.propertyData.resize(wctx.propertyData.size() + sizeof(value));
865 *(T*)(wctx.propertyData.data() + offset) = value;
866 }
867 return index;
868 }
869
870 uint32_t recordStringBasedProperty(WriteContext& wctx, const Cogs::StringView& key, CogsBin::PropertyValueType valueType, const Cogs::StringView& value)
871 {
872 auto index = nextIndex(wctx.properties);
873 wctx.properties.emplace_back();
874 auto & prop = wctx.properties.back();
875 prop.keyType = CogsBin::PropertyKeyType::String;
876 prop.key = recordString(wctx, key);
877 prop.valueType = valueType;
878 prop.value = recordString(wctx, value);
879 return index;
880 }
881
882 uint32_t recordSamplerState(WriteContext& wctx, const TextureWithSampler& srcSampler) {
883 auto index = nextIndex(wctx.samplerStates);
884 wctx.samplerStates.emplace_back();
885 auto& samplerState = wctx.samplerStates.back();
886 samplerState.addressModeS = translateTextureAddressMode(srcSampler.sMode);
887 samplerState.addressModeT = translateTextureAddressMode(srcSampler.tMode);
888 samplerState.addressModeW = translateTextureAddressMode(srcSampler.uMode);
889 samplerState.filter = translateTextureFilterMode(srcSampler.filterMode);
890
891 // Fill in default properties that we don't currently have in TextureWithSampler
893 samplerState.comparisonFunction = translateTextureComparisonFunc(defaultState.comparisonFunction);
894 samplerState.maxAnisotropy = defaultState.maxAnisotropy;
895 std::copy(std::begin(defaultState.borderColor), std::end(defaultState.borderColor), samplerState.borderColor);
896
897 return index;
898 }
899
900 uint32_t recordEmbeddedTexture(WriteContext& wctx, const Texture* srcTex, uint32_t samplerIx, uint16_t sectionIx)
901 {
902 assert(srcTex);
903 assert(srcTex->storage.data.size() != 0);
904
905 if (auto it = wctx.knownTexturesByPointer.find(srcTex); it != wctx.knownTexturesByPointer.end()) {
906 return it->second;
907 }
908 auto texIx = nextIndex(wctx.embeddedTextures);
909 wctx.knownTexturesByPointer[srcTex] = texIx;
910 auto & dstTex = wctx.embeddedTextures.emplace_back();;
911
912 // Store image data
913 assert(srcTex->storage.data.size() <= std::numeric_limits<uint32_t>::max());
914 const auto size = uint32_t(srcTex->storage.data.size());
915 dstTex.buffer = recordBuffer(wctx, size, CogsBin::BufferContentFlags::TextureData, 4, sectionIx);
916 std::memcpy(getBufferPointer(wctx, dstTex.buffer, 0, size), srcTex->storage.data.data(), size);
917
918 dstTex.offset = 0;
919 dstTex.size = size;
920 dstTex.encoding = CogsBin::TextureEncoding::None;
921 dstTex.name = recordString(wctx, srcTex->getName());
922 dstTex.width = srcTex->description.width;
923 dstTex.height = srcTex->description.height;
924 dstTex.depth = srcTex->description.depth;
925 dstTex.format = translateFormat(srcTex->description.format);
926 dstTex.flags = CogsBin::TextureFlags::None;
927 dstTex.dimension = translateTextureDimensions(srcTex->description.target);
928 dstTex.samplerState = samplerIx;
929
930 return texIx;
931 }
932
933 uint32_t recordMaterialOptions(WriteContext& wctx, const MaterialOptions& options) {
934 uint32_t propCount = 0;
935 Cogs::StringView optionVal;
936 switch(options.cullMode) {
937 case CullMode::None: optionVal = "None"; break;
938 case CullMode::Back: optionVal = "Back"; break;
939 case CullMode::Front: optionVal = "Front"; break;
940 default: assert(false && "Invalid cull mode"); break;
941 }
942 recordStringBasedProperty(wctx, "CullMode", CogsBin::PropertyValueType::Option, optionVal);
943 propCount++;
944
945 const Cogs::Reflection::Enumerator* blendMode = Cogs::Reflection::TypeDatabase::getType<BlendMode>().getEnumerator(size_t(options.blendMode));
946 assert(blendMode && "Invalid blend mode");
947 optionVal = blendMode->getName().c_str();
948
949 recordStringBasedProperty(wctx, "BlendMode", CogsBin::PropertyValueType::Option, optionVal);
950 propCount++;
951
952 switch(options.transparencyMode) {
953 case TransparencyMode::On: optionVal = "On"; break;
954 case TransparencyMode::Off: optionVal = "Off"; break;
955 case TransparencyMode::Auto: optionVal = "Auto"; break;
956 case TransparencyMode::Alpha: optionVal = "Alpha"; break;
957 default: assert(false && "Invalid transparency mode"); break;
958 }
959 recordStringBasedProperty(wctx, "Transparency", CogsBin::PropertyValueType::Option, optionVal);
960 propCount++;
961
962 recordStringBasedProperty(wctx, "DepthWriteEnabled", CogsBin::PropertyValueType::Option, options.depthWriteEnabled ? "true" : "false");
963 recordStringBasedProperty(wctx, "DepthTestEnabled", CogsBin::PropertyValueType::Option, options.depthTestEnabled ? "true" : "false");
964
965 switch (options.depthFunc) {
966 case DepthFunc::Less: optionVal = "Less"; break;
967 case DepthFunc::Always: optionVal = "Always"; break;
968 case DepthFunc::LessOrEqual: optionVal = "LessOrEqual"; break;
969 case DepthFunc::NotEqual: optionVal = "NotEqual"; break;
970 case DepthFunc::Equal: optionVal = "Equal"; break;
971 default: assert(false && "Invalid depthFunc"); break;
972 }
973 recordStringBasedProperty(wctx, "DepthFunc", CogsBin::PropertyValueType::Option, optionVal);
974
975 recordStringBasedProperty(wctx, "DepthBiasEnabled", CogsBin::PropertyValueType::Option, options.depthBiasEnabled ? "true" : "false");
976 propCount += 4;
977 if (options.depthBiasEnabled) {
978 recordStringBasedProperty(wctx, "DepthBiasConstant", CogsBin::PropertyValueType::Option, std::to_string(options.depthBias.constant));
979 recordStringBasedProperty(wctx, "DepthBiasSlope", CogsBin::PropertyValueType::Option, std::to_string(options.depthBias.slope));
980 recordStringBasedProperty(wctx, "DepthBiasClamp", CogsBin::PropertyValueType::Option, std::to_string(options.depthBias.clamp));
981 propCount += 3;
982 }
983 return propCount;
984 }
985
986 uint32_t recordMaterialInstance(WriteContext& wctx, MaterialInstance* srcMatInst, uint16_t sectionIx)
987 {
988 if (srcMatInst == nullptr) return ~0u;
989
990 if (auto it = wctx.knownMaterialInstancesByPointer.find(srcMatInst); it != wctx.knownMaterialInstancesByPointer.end()) {
991 return it->second;
992 }
993
994 auto hash = getMaterialInstanceHash(srcMatInst);
995 if (auto it = wctx.knownMaterialInstancesByHash.find(hash); it != wctx.knownMaterialInstancesByHash.end()) {
996 return it->second;
997 }
998
999 auto index = nextIndex(wctx.materialInstances);
1000 wctx.materialInstances.emplace_back();
1001 auto & dstMatInst = wctx.materialInstances.back();
1002
1003 wctx.knownMaterialInstancesByPointer[srcMatInst] = index;
1004 wctx.knownMaterialInstancesByHash[hash] = index;
1005
1006 dstMatInst.material = recordString(wctx, srcMatInst->material->getName());
1007 dstMatInst.permutation = recordString(wctx, srcMatInst->getPermutation());
1008 dstMatInst.propertyFirst = nextIndex(wctx.properties);
1009 dstMatInst.propertyCount = 0;
1010
1011 for (auto & prop : srcMatInst->material->constantBuffers.variables) {
1012 switch (prop.type) {
1013 case MaterialDataType::Float:
1014 if (auto val = srcMatInst->getFloatProperty(prop.key); val != prop.defaultFloat()) {
1015 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::Float, val);
1016 dstMatInst.propertyCount++;
1017 }
1018 break;
1019 case MaterialDataType::Float2:
1020 if (auto val = srcMatInst->getVec2Property(prop.key); val != prop.defaultVec2()) {
1021 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::Float2, val);
1022 dstMatInst.propertyCount++;
1023 }
1024 break;
1025 case MaterialDataType::Float3:
1026 if (auto val = srcMatInst->getVec3Property(prop.key); val != prop.defaultVec3()) {
1027 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::Float3, val);
1028 dstMatInst.propertyCount++;
1029 }
1030 break;
1031 case MaterialDataType::Float4:
1032 if (auto val = srcMatInst->getVec4Property(prop.key); val != prop.defaultVec4()) {
1033 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::Float4, val);
1034 dstMatInst.propertyCount++;
1035 }
1036 break;
1037 case MaterialDataType::Bool:
1038 if (auto val = srcMatInst->getBoolProperty(prop.key); val != prop.defaultBool()) {
1039 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::Bool, (uint32_t)val);
1040 dstMatInst.propertyCount++;
1041 }
1042 break;
1043 case MaterialDataType::UInt:
1044 if (auto val = srcMatInst->getProperty<uint32_t>(prop.key); val != prop.defaultUInt()) {
1045 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::UInt, (uint32_t)val);
1046 dstMatInst.propertyCount++;
1047 }
1048 break;
1049 default:
1050 LOG_WARNING(logger, "Skipped serialization of unsupported material property %s of type %u.", prop.name.c_str(), unsigned(prop.type));
1051 break;
1052 }
1053 }
1054
1055
1056 if ((wctx.flags & WriteModelFlags::EMBED_TEXTURES) != 0) {
1057 for (auto & prop : srcMatInst->material->textureProperties) {
1058 auto value = srcMatInst->getTextureProperty(prop.key);
1059 if (value.texture.handle && value.texture.handle != prop.texture.handle) { // different texture on instance than on material.
1060 auto * tex = value.texture.handle.resolve();
1061 if (tex->storage.data.size() == 0) {
1062 LOG_WARNING(logger, "Empty texture, skipping.");
1063 continue;
1064 }
1065 uint32_t samplerIx = recordSamplerState(wctx, value.texture);
1066 uint32_t texIx = recordEmbeddedTexture(wctx, tex, samplerIx, sectionIx);
1067 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::EmbeddedTexture, texIx);
1068 dstMatInst.propertyCount++;
1069 }
1070 }
1071 }
1072
1073 else {
1074 for (uint32_t texI = 0; texI < srcMatInst->material->textureProperties.size(); texI++) {
1075 const TextureProperty& prop = srcMatInst->material->textureProperties[texI];
1076 auto value = srcMatInst->getTextureProperty(prop.key);
1077 if (value.texture.handle && value.texture.handle != prop.texture.handle) {
1078 std::string source = value.texture.handle->getSource().to_string();
1079 const std::string dir = Cogs::IO::parentPath(wctx.fileName);
1080 if (source.empty()) {
1081 // If the source is empty, try to write the texture data to a png and use that as source.
1082 const Texture* tex = value.texture.handle.resolve();
1083 if (tex->storage.data.size() == 0) {
1084 LOG_WARNING(logger, "Skipping texture without source or data.");
1085 continue;
1086 }
1087 const std::string textureName = Cogs::IO::stem(wctx.fileName) + "_texture_" + std::to_string(index) + std::to_string(texI) + ".png";
1088 source = Cogs::IO::combine(dir, textureName);
1089 const Cogs::FormatInfo* formatInfo = getFormatInfo(tex->description.format);
1090 if (!stbi_write_png(source.c_str(), tex->description.width, tex->description.height, formatInfo->elements, tex->storage.data.data(), 0)) {
1091 LOG_ERROR(logger, "Failed to write texture %s.", source.c_str());
1092 return ~0u;
1093 }
1094 }
1095 uint32_t pathIx = recordString(wctx, Cogs::IO::relativePath(source, dir));
1096 uint32_t samplerIx = recordSamplerState(wctx, value.texture);
1097 auto texIx = nextIndex(wctx.externalTextures);
1098 wctx.externalTextures.push_back({ pathIx, samplerIx });
1099 recordProperty(wctx, prop.name, CogsBin::PropertyValueType::ExternalTexture, texIx);
1100 dstMatInst.propertyCount++;
1101 }
1102 }
1103 }
1104
1105 const ShaderVariants& variants = srcMatInst->material->definition.variants;
1106 for (const ShaderVariantSelector& selector : srcMatInst->variantSelectors) {
1107 const ShaderVariantDefinition& variant = variants[selector.index];
1108 if (variant.isShared) continue; // Do not store shared variants.
1109 if (selector.value != variant.defaultValue) {
1110 if (variant.type == ShaderVariantType::Enum) {
1111 recordStringBasedProperty(wctx, variant.name, CogsBin::PropertyValueType::Variant, variant.values[selector.value].key);
1112 dstMatInst.propertyCount++;
1113 }
1114 else {
1115 recordStringBasedProperty(wctx, variant.name, CogsBin::PropertyValueType::Variant, selector.value ? "true" : "false");
1116 dstMatInst.propertyCount++;
1117 }
1118 }
1119 }
1120
1121 dstMatInst.propertyCount += recordMaterialOptions(wctx, srcMatInst->options);
1122
1123 assert(dstMatInst.propertyFirst + dstMatInst.propertyCount == nextIndex(wctx.properties));
1124 return index;
1125 }
1126
1127 uint32_t recordSkeleton(WriteContext& wctx, const Skeleton& srcSkeleton)
1128 {
1129 if (srcSkeleton.bones.empty()) return ~0u;
1130
1131 auto index = nextIndex(wctx.skeletons);
1132 wctx.skeletons.emplace_back(CogsBin::Skeleton{});
1133 auto & dstSkeleton = wctx.skeletons.back();
1134 dstSkeleton.bindPose = srcSkeleton.bindPose;
1135 dstSkeleton.firstBone = nextIndex(wctx.bones);
1136 dstSkeleton.boneCount = uint32_t(srcSkeleton.bones.size());
1137 for (const auto & srcBone : srcSkeleton.bones) {
1138 wctx.bones.emplace_back(CogsBin::Bone{});
1139 auto & dstBone = wctx.bones.back();
1140 dstBone.inverseBindPose = srcBone.inverseBindPose;
1141 dstBone.relative = srcBone.relative;
1142 dstBone.rot = srcBone.rot;
1143 dstBone.pos = srcBone.pos;
1144 dstBone.scale = srcBone.scale;
1145 dstBone.name = recordString(wctx, srcBone.name);
1146 dstBone.parentBone = uint16_t(srcBone.parentBone);
1147 dstBone.flags =
1148 (srcBone.hasOffset ? CogsBin::BoneFlags::HasOffset : CogsBin::BoneFlags::None) |
1149 (srcBone.used ? CogsBin::BoneFlags::Used : CogsBin::BoneFlags::None) |
1150 (srcBone.animated ? CogsBin::BoneFlags::Animated : CogsBin::BoneFlags::None);
1151 }
1152 return index;
1153 }
1154
1155 uint32_t recordPart(WriteContext& wctx, const Model& model, const ModelPart& part, uint16_t section)
1156 {
1157 auto nodeIndex = newNode(wctx);
1158 auto & node = wctx.nodes[nodeIndex];
1159
1160 node.parent = part.parentIndex;
1161 if (part.nameIndex != -1) {
1162 node.name = recordString(wctx, model.getPartName(part));
1163 }
1164 if (part.transformIndex != -1) {
1165 node.transform = recordTransform(wctx, model.getPartTransform(part));
1166 }
1167
1168 if (part.meshIndex != -1) {
1169 auto * mesh = model.meshes[part.meshIndex].resolve();
1170 node.mesh = recordMesh(wctx, mesh, 0);
1171 node.boundingBox = recordBoundingBox(wctx, mesh->boundingBox);
1172 node.primitiveType = translatePrimitiveType(wctx, mesh->primitiveType); // FIXME: currently just use the primitive type of mesh since part has primtitive type.
1173 node.vertexFirst = part.startIndex;
1174 node.vertexCount = part.vertexCount;
1175 if (part.materialIndex != -1) {
1176 node.materialInstance = recordMaterialInstance(wctx, model.materials[part.materialIndex].resolve(), section);
1177 }
1178 }
1179
1180 return nodeIndex;
1181 }
1182
1183 void recordAnimTrack(WriteContext& wctx, const AnimationTrack& srcTrack, uint16_t sectionIx)
1184 {
1185 wctx.animTracks.emplace_back(CogsBin::AnimTrack{});
1186 auto & dstTrack = wctx.animTracks.back();
1187
1188 const auto translationCount = uint32_t(srcTrack.translations.size());
1189 const auto rotationCount = uint32_t(srcTrack.rotations.size());
1190 const auto scaleCount = uint32_t(srcTrack.scales.size());
1191
1192 dstTrack.boneIndex = uint32_t(srcTrack.boneIndex);
1193 dstTrack.buffer = recordBuffer(wctx,
1194 sizeof(float)*(4 * (size_t)translationCount + 5 * (size_t)rotationCount + 4 * (size_t)scaleCount),
1195 CogsBin::BufferContentFlags::AnimationData, 4, sectionIx);
1196
1197 auto * base = (float*)wctx.bufferContexts[dstTrack.buffer].contents.data();
1198 auto * ptr = base;
1199
1200 dstTrack.translationOffset = uint32_t((uint8_t*)ptr - (uint8_t*)base);
1201 for(auto & translation : srcTrack.translations) {
1202 *ptr++ = translation.t;
1203 *ptr++ = translation.value[0];
1204 *ptr++ = translation.value[1];
1205 *ptr++ = translation.value[2];
1206 }
1207 dstTrack.translationCount = translationCount;
1208 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) - dstTrack.translationOffset == sizeof(float) * 4 * translationCount);
1209
1210 dstTrack.rotationOffset = uint32_t((uint8_t*)ptr - (uint8_t*)base);
1211 for (auto & rotation : srcTrack.rotations) {
1212 *ptr++ = rotation.t;
1213 *ptr++ = rotation.value[0];
1214 *ptr++ = rotation.value[1];
1215 *ptr++ = rotation.value[2];
1216 *ptr++ = rotation.value[3];
1217 }
1218 dstTrack.rotationCount = rotationCount;
1219 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) - dstTrack.rotationOffset == sizeof(float) * 5 * rotationCount);
1220
1221 dstTrack.scaleOffset = uint32_t((uint8_t*)ptr - (uint8_t*)base);
1222 for (auto & scale : srcTrack.scales) {
1223 *ptr++ = scale.t;
1224 *ptr++ = scale.value[0];
1225 *ptr++ = scale.value[1];
1226 *ptr++ = scale.value[2];
1227 }
1228 dstTrack.scaleCount = scaleCount;
1229 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) - dstTrack.scaleOffset == sizeof(float) * 4 * scaleCount);
1230 assert(uint32_t((uint8_t*)ptr - (uint8_t*)base) == sizeof(float) * (4 * translationCount + 5 * rotationCount + 4 * scaleCount));
1231 }
1232
1233 void recordAnimClip(WriteContext& wctx, const AnimationClip& srcClip, uint16_t sectionIx)
1234 {
1235 wctx.animClips.emplace_back(CogsBin::AnimClip{});
1236 auto & dstClip = wctx.animClips.back();
1237 dstClip.name = recordString(wctx, srcClip.name);
1238 dstClip.duration = srcClip.duration;
1239 dstClip.resolution = srcClip.resolution;
1240 if (!srcClip.tracks.empty()) {
1241 dstClip.trackFirst = nextIndex(wctx.animTracks);
1242 dstClip.trackCount = uint32_t(srcClip.tracks.size());
1243 for (auto const & track : srcClip.tracks) recordAnimTrack(wctx, track, sectionIx);
1244 assert(dstClip.trackFirst + dstClip.trackCount == wctx.animTracks.size());
1245 }
1246 else {
1247 dstClip.trackFirst = ~0u;
1248 dstClip.trackCount = 0;
1249 }
1250 }
1251
1252 void recordModel(WriteContext& wctx, const Model * srcModel, WriteModelFlags /*flags*/)
1253 {
1254 if (srcModel->parts.empty()) return; // Now we know that it is atleast one part.
1255
1256 uint16_t section = 0;
1257
1258 wctx.models.emplace_back(CogsBin::Model{});
1259 auto & dstModel = wctx.models.back();
1260 dstModel.skeleton = (uint32_t)-1;
1261 dstModel.animClipFirst = (uint32_t)-1;
1262 dstModel.animClipCount = 0;
1263 if (auto * animation = srcModel->animation.resolve(); animation) {
1264 dstModel.skeleton = recordSkeleton(wctx, animation->skeleton);
1265 if (!animation->clips.empty()) {
1266 dstModel.animClipFirst = nextIndex(wctx.animClips);
1267 dstModel.animClipCount = uint32_t(animation->clips.size());
1268 for (const auto & clip : animation->clips) recordAnimClip(wctx, clip, section);
1269 assert(dstModel.animClipFirst + dstModel.animClipCount == wctx.animClips.size());
1270 }
1271 }
1272
1273 dstModel.firstNode = recordPart(wctx, *srcModel, srcModel->parts[0], section);
1274 for (size_t i = 1; i < srcModel->parts.size(); i++) {
1275 auto ix = recordPart(wctx, *srcModel, srcModel->parts[i], 0);
1276 assert(dstModel.firstNode + i == ix);
1277 }
1278 dstModel.nodeCount = uint32_t(srcModel->parts.size());
1279 }
1280
1281 uint32_t recordEntity(Context* context, WriteContext& wctx, uint32_t parent, Cogs::ComponentModel::Entity* entity, uint16_t section)
1282 {
1283 auto nodeIndex = newNode(wctx);
1284 auto & node = wctx.nodes[nodeIndex];
1285
1286 node.parent = parent;
1287 node.name = recordString(wctx, entity->getName());
1288
1289 if (const auto * trComp = entity->getComponent<TransformComponent>(); trComp) {
1290 node.transform = recordTransform(wctx, context->transformSystem->getLocalTransform(trComp));
1291 }
1292
1293 if (auto meshComponent = entity->getComponent<MeshComponent>(); meshComponent && meshComponent->meshHandle) {
1294 auto mesh = meshComponent->meshHandle;
1295 if (mesh && mesh->streams.size()) {
1296 auto renderComponent = entity->getComponent<MeshRenderComponent>();
1297 if (renderComponent) {
1298 node.mesh = recordMesh(wctx, mesh.resolve(), section);
1299 if (renderComponent->primitiveType != (Cogs::PrimitiveType::EPrimitiveType)-1) {
1300 node.primitiveType = translatePrimitiveType(wctx, renderComponent->primitiveType);
1301 } else {
1302 node.primitiveType = CogsBin::PrimitiveType::Unknown;
1303 }
1304 node.vertexFirst = renderComponent->startIndex;
1305 node.vertexCount = renderComponent->vertexCount;
1306 node.boundingBox = recordBoundingBox(wctx, context->renderSystem->getLocalBounds(renderComponent));
1307 if (renderComponent->material) {
1308 node.materialInstance = recordMaterialInstance(wctx, renderComponent->material.resolve(), section);
1309 }
1310 }
1311 else {
1312 LOG_ERROR(logger, "MeshComponent must be accompanied by a MeshRenderComponent for export.");
1313 }
1314 }
1315 }
1316
1317 if (auto * sceneComponent = entity->getComponent<SceneComponent>(); sceneComponent) {
1318 for (auto & child : sceneComponent->children) {
1319 recordEntity(context, wctx, nodeIndex, child.get(), section);
1320 }
1321 }
1322
1323 if (auto * animComp = entity->getComponent<AnimationComponent>(); animComp) {
1324
1325 auto * anim = animComp->animation.resolve();
1326 if (animComp->master) {
1327 auto * master = animComp->master.resolveComponent<AnimationComponent>();
1328 anim = master->animation.resolve();
1329 }
1330 if (wctx.currentAnimation == nullptr) {
1331 wctx.currentAnimation = anim;
1332 }
1333 else if (wctx.currentAnimation != anim) {
1334 LOG_ERROR(logger, "Multiple animations within a single model's hierarchy");
1335 }
1336 }
1337
1338 return nodeIndex;
1339 }
1340
1341 void layoutSections(WriteContext& wctx)
1342 {
1343 for (size_t i = 0; i < wctx.sections.size(); i++) {
1344 auto & section = *wctx.sections[i].get();
1345
1346 assert(section.contents.empty() && "Sections already laid out");
1347
1348 // lay out subsections at start
1349 section.writePos = sizeof(CogsBin::SubSectionInfo)*section.subSections.size();
1350 for (auto & subsection : section.subSections) {
1351 section.writePos = align(section.writePos);
1352 assert(section.writePos < std::numeric_limits<uint32_t>::max());
1353 subsection.offset = section.writePos;
1354 section.writePos += subsection.size;
1355 }
1356 section.writePos = align(section.writePos);
1357
1358 // Add data size and create buffer for contents.
1359 section.contents.resize(section.writePos + section.dataSize);
1360 LOG_TRACE(logger, "Section %zu allocated %zu bytes, data offset=%zu", i, section.contents.size(), section.writePos);
1361
1362 // write subsection directory
1363 auto * ptr = (CogsBin::SubSectionInfo*)section.contents.data();
1364 for (auto & subsection : section.subSections) {
1365 LOG_TRACE(logger, " Subsection offset=%zu, size=%u, type=%u", subsection.offset, subsection.size, unsigned(subsection.type));
1366 ptr->offset = subsection.offset;
1367 ptr->size = subsection.size;
1368 ptr->type = subsection.type;
1369 ptr++;
1370 }
1371 }
1372 }
1373
1374
1375 template<typename T>
1376 void fillSubsection(WSection& section, uint32_t index, const std::vector<T>& source)
1377 {
1378 if (index == ~0u) return;
1379 auto & subSection = section.subSections[index];
1380 assert(sizeof(T)*source.size() == subSection.size);
1381
1382 std::memcpy(section.contents.data() + subSection.offset, source.data(), subSection.size);
1383 }
1384
1385 bool pack(Context* /*context*/, WriteContext& wctx)
1386 {
1387 // First section has meta data, and only this one can have subsections.
1388 auto & metaSection = getSection(wctx, CogsBin::SectionType::META, 0);
1389
1390 uint32_t subSections[size_t(CogsBin::SubSectionType::EnumSize)];
1391 for (auto & subSection : subSections) subSection = (uint32_t)-1;
1392
1393#define REGISTER(T,A) if(!(A).empty()) subSections[size_t(T)] = addMetaSubsection(wctx, metaSection, T, sizeof((A)[0])*((A).size()))
1394 REGISTER(CogsBin::SubSectionType::Buffers, wctx.buffers);
1395 REGISTER(CogsBin::SubSectionType::ExternalTextures, wctx.externalTextures);
1396 REGISTER(CogsBin::SubSectionType::EmbeddedTextures, wctx.embeddedTextures);
1397 REGISTER(CogsBin::SubSectionType::SamplerStates, wctx.samplerStates);
1398 REGISTER(CogsBin::SubSectionType::Strings, wctx.strings);
1399 REGISTER(CogsBin::SubSectionType::StringData, wctx.stringData);
1400 REGISTER(CogsBin::SubSectionType::Nodes, wctx.nodes);
1401 REGISTER(CogsBin::SubSectionType::Transforms, wctx.transforms);
1402 REGISTER(CogsBin::SubSectionType::BoundingBoxes, wctx.boundingBoxes);
1403 REGISTER(CogsBin::SubSectionType::MaterialInstances, wctx.materialInstances);
1404 REGISTER(CogsBin::SubSectionType::Properties, wctx.properties);
1405 REGISTER(CogsBin::SubSectionType::PropertyData, wctx.propertyData);
1406 REGISTER(CogsBin::SubSectionType::Meshes, wctx.meshes);
1407 REGISTER(CogsBin::SubSectionType::VertexStreams, wctx.vertexStreams);
1408 REGISTER(CogsBin::SubSectionType::VertexAttributes, wctx.vertexAttributes);
1409 REGISTER(CogsBin::SubSectionType::Bones, wctx.bones);
1410 REGISTER(CogsBin::SubSectionType::Skeletons, wctx.skeletons);
1411 REGISTER(CogsBin::SubSectionType::AnimTracks, wctx.animTracks);
1412 REGISTER(CogsBin::SubSectionType::AnimClips, wctx.animClips);
1413 REGISTER(CogsBin::SubSectionType::Models, wctx.models);
1414#undef REGISTER
1415
1416 layoutSections(wctx);
1417
1418 // copy contents of buffers
1419 for (size_t i = 0; i < wctx.buffers.size(); i++) {
1420 auto & buffer = wctx.buffers[i];
1421 const auto & bufCtx = wctx.bufferContexts[i];
1422 const auto lowerMask = uint64_t(bufCtx.alignment - 1);
1423
1424 const auto sectionIx = buffer.sectionOffset;
1425 auto & section = getSection(wctx, CogsBin::SectionType::DATA, uint16_t(sectionIx), false);
1426 auto offset = (section.writePos + lowerMask) & (~lowerMask);
1427 buffer.sectionOffset = offset | (sectionIx << 48);
1428
1429 assert(offset + buffer.size <= section.contents.size());
1430 std::memcpy(section.contents.data() + offset, bufCtx.contents.data(), buffer.size);
1431 section.writePos = offset + buffer.size;
1432 }
1433
1434 // copy contents of meta subsection
1435 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Buffers)], wctx.buffers);
1436 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::ExternalTextures)], wctx.externalTextures);
1437 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::EmbeddedTextures)], wctx.embeddedTextures);
1438 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::SamplerStates)], wctx.samplerStates);
1439 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Strings)], wctx.strings);
1440 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::StringData)], wctx.stringData);
1441 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Nodes)], wctx.nodes);
1442 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Transforms)], wctx.transforms);
1443 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::BoundingBoxes)], wctx.boundingBoxes);
1444 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::MaterialInstances)], wctx.materialInstances);
1445 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Properties)], wctx.properties);
1446 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::PropertyData)], wctx.propertyData);
1447 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Meshes)], wctx.meshes);
1448 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::VertexStreams)], wctx.vertexStreams);
1449 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::VertexAttributes)], wctx.vertexAttributes);
1450 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Bones)], wctx.bones);
1451 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Skeletons)], wctx.skeletons);
1452 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::AnimTracks)], wctx.animTracks);
1453 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::AnimClips)], wctx.animClips);
1454 fillSubsection(metaSection, subSections[size_t(CogsBin::SubSectionType::Models)], wctx.models);
1455
1456 return true;
1457 }
1458
1459#if defined(_WIN32) && defined(MAKE_DEBUGABLE)
1460#pragma optimize( "", on )
1461#endif
1462 bool compressSections(Context* /*context*/, WriteContext& wctx)
1463 {
1464 wctx.sectionInfos.resize(wctx.sections.size());
1465 for (auto & section : wctx.sections) {
1466 auto & info = wctx.sectionInfos[section->index]; // Initialize section infos
1467 info.fileSize = section->contents.size();
1468 info.uncompressedSize = info.fileSize;
1469 info.compression = CogsBin::Compression::None;
1470 info.type = section->type;
1471 info.subSectionCount = uint16_t(section->subSections.size());
1472 }
1473
1474
1475 if ((wctx.flags & WriteModelFlags::COMPRESS_ZSTD) != 0) {
1476
1477 int compressionLevel = ZSTD_CLEVEL_DEFAULT;
1478 if ((wctx.flags & WriteModelFlags::COMPRESS_MAX) != 0) {
1479 compressionLevel = ZSTD_maxCLevel();
1480 } else {
1481 compressionLevel = wctx.settings.compressionLevel;
1482 }
1483
1484 // Recycle context between sections. There is an opportunity for multi-threading with multiple sections.
1485 ZSTD_CCtx* zstd_ctx = ZSTD_createCCtx();
1486 if (zstd_ctx == nullptr) {
1487 LOG_ERROR(logger, "Failed to create zstd context.");
1488 return false;
1489 }
1490
1491 std::vector<uint8_t> temp;
1492 for (auto & section : wctx.sections) {
1493 auto & info = wctx.sectionInfos[section->index];
1494 auto maxSize = ZSTD_compressBound(info.uncompressedSize);
1495 temp.resize(maxSize);
1496
1497 // Run compression and check result
1498 auto result = ZSTD_compressCCtx(zstd_ctx, temp.data(), maxSize, section->contents.data(), info.uncompressedSize, compressionLevel);
1499 if (ZSTD_isError(result)) {
1500 LOG_ERROR(logger, "zstd compression failed: %s", ZSTD_getErrorName(result));
1501 continue;
1502 }
1503 else if (info.uncompressedSize <= result) {
1504 //LOG_DEBUG(logger, "compressed size is greater or equal to uncompressed size.");
1505 continue;
1506 }
1507
1508 // Success, note compressed size etc.
1509 temp.resize(result);
1510 info.compression = CogsBin::Compression::ZSTD;
1511 info.fileSize = result;
1512 section->contents.swap(temp);
1513 }
1514
1515 ZSTD_freeCCtx(zstd_ctx);
1516 return true;
1517 }
1518 else {
1519 return true; // No compression
1520 }
1521 }
1522#if defined(_WIN32) && defined(MAKE_DEBUGABLE)
1523#pragma optimize( "", off )
1524#endif
1525
1526 bool compressBlob(Context* /*context*/, WriteContext& wctx, Cogs::Memory::MemoryBuffer& compressed, const Cogs::Memory::MemoryBuffer& uncompressed)
1527 {
1528 int compressionLevel = ZSTD_CLEVEL_DEFAULT;
1529 if ((wctx.flags & WriteModelFlags::COMPRESS_MAX) != 0) {
1530 compressionLevel = ZSTD_maxCLevel();
1531 }
1532 else {
1533 compressionLevel = wctx.settings.compressionLevel;
1534 }
1535
1536 ZSTD_CCtx* zstd_ctx = ZSTD_createCCtx();
1537 if (zstd_ctx == nullptr) {
1538 LOG_ERROR(logger, "Failed to create zstd context.");
1539 return false;
1540 }
1541
1542 size_t maxSize = ZSTD_compressBound(uncompressed.size());
1543 compressed.resize(maxSize, false);
1544
1545 size_t result = ZSTD_compressCCtx(zstd_ctx, compressed.data(), maxSize, uncompressed.data(), uncompressed.size(), compressionLevel);
1546 if (ZSTD_isError(result)) {
1547 ZSTD_freeCCtx(zstd_ctx);
1548 LOG_ERROR(logger, "zstd compression failed: %s", ZSTD_getErrorName(result));
1549 return false;
1550 }
1551 else if (uncompressed.size() <= result) {
1552 //LOG_DEBUG(logger, "compressed size is greater or equal to uncompressed size.");
1553 }
1554
1555 compressed.resize(result);
1556 ZSTD_freeCCtx(zstd_ctx);
1557 return true;
1558 }
1559
1560 bool write(Context* context, WriteContext& wctx)
1561 {
1562 pack(context, wctx);
1563
1564 compressSections(context, wctx);
1565
1566 // layout file and populate header
1567 uint64_t uncompressedSize = sizeof(CogsBin::Header) + sizeof(CogsBin::SectionInfo)*wctx.sectionInfos.size();
1568
1569 CogsBin::Header header{ };
1570 header.magic = CogsBin::HeaderMagic;
1571 header.version = CogsBin::Version;
1572 header.fileLength = sizeof(CogsBin::Header) + sizeof(CogsBin::SectionInfo) * wctx.sectionInfos.size();
1573 header.sectionCount = nextIndex(wctx.sectionInfos);
1574 for (auto & info : wctx.sectionInfos) {
1575 if (info.compression == CogsBin::Compression::None) {
1576 // if no compression, align
1577 header.fileLength = (header.fileLength + (CogsBin::CogsSectionAlignment - 1)) & ~(CogsBin::CogsSectionAlignment - 1);
1578 }
1579 info.fileOffset = header.fileLength;
1580 header.fileLength += info.fileSize;
1581 uncompressedSize += info.uncompressedSize;
1582 }
1583
1584 // write file.
1585 FILE * file = fopen(wctx.fileName.c_str(), "wb");
1586 if (file == nullptr) {
1587 LOG_ERROR(logger, "Failed to open \"%s\" for writing.", wctx.fileName.c_str());
1588 return false;
1589 }
1590
1591 if ((wctx.flags & WriteModelFlags::COMPRESS_ZSTD_AS_FILE) != 0) {
1592
1593 // Pack everything into a single buffer
1594 Cogs::Memory::MemoryBuffer uncompressed;
1595 uncompressed.write(&header, sizeof(CogsBin::Header));
1596 uncompressed.write(wctx.sectionInfos.data(), sizeof(CogsBin::SectionInfo) * header.sectionCount);
1597 for (size_t i = 0; i < header.sectionCount; i++) {
1598 const CogsBin::SectionInfo& info = wctx.sectionInfos[i];
1599 const ::WSection& section = *wctx.sections[i].get();
1600 assert(uncompressed.pos() <= info.fileOffset);
1601 while (uncompressed.pos() < info.fileOffset) {
1602 uncompressed.write8(0); // Padding
1603 }
1604 uncompressed.write(section.contents.data(), info.fileSize);
1605 }
1606 assert(uncompressedSize == uncompressed.pos());
1607
1608 Cogs::Memory::MemoryBuffer compressed;
1609 if (!compressBlob(context, wctx, compressed, uncompressed)) {
1610 return false;
1611 }
1612
1613 size_t r = fwrite(compressed.data(), compressed.size(), 1, file);
1614 if (r != 1) {
1615 fclose(file);
1616 LOG_ERROR(logger, "Error writing to %s", wctx.fileName.c_str());
1617 return false;
1618 }
1619 }
1620 else {
1621 // write header
1622 auto r = fwrite(&header, sizeof(CogsBin::Header), 1, file);
1623 if (r != 1) {
1624 fclose(file);
1625 LOG_ERROR(logger, "Error writing to %s", wctx.fileName.c_str());
1626 return false;
1627 }
1628
1629 // write section infos
1630 r = fwrite(wctx.sectionInfos.data(), sizeof(CogsBin::SectionInfo) * header.sectionCount, 1, file);
1631 if (r != 1) {
1632 fclose(file);
1633 LOG_ERROR(logger, "Error writing to %s", wctx.fileName.c_str());
1634 return false;
1635 }
1636
1637 // write section contents
1638 for (uint32_t i = 0; i < header.sectionCount; i++) {
1639 auto& info = wctx.sectionInfos[i];
1640 auto& section = *wctx.sections[i].get();
1641
1642#ifdef _WIN32
1643 auto pos = uint64_t(_ftelli64(file));
1644#else
1645 auto pos = uint64_t(ftell(file));
1646#endif
1647 assert(pos <= info.fileOffset);
1648 if (pos != info.fileOffset) {
1649#ifdef _WIN32
1650 _fseeki64(file, info.fileOffset, SEEK_SET);
1651#else
1652 fseek(file, info.fileOffset, SEEK_SET);
1653#endif
1654 }
1655
1656 r = fwrite(section.contents.data(), info.fileSize, 1, file);
1657 if (r != 1) {
1658 fclose(file);
1659 LOG_ERROR(logger, "Error writing to %s", wctx.fileName.c_str());
1660 return false;
1661 }
1662 }
1663 }
1664
1665#ifdef _WIN32
1666 auto fileLength = _ftelli64(file);
1667#else
1668 auto fileLength = ftell(file);
1669#endif
1670 if ((wctx.flags & WriteModelFlags::COMPRESS_ZSTD_AS_FILE) == 0) {
1671 assert(fileLength == (long long)header.fileLength);
1672 }
1673 LOG_TRACE(logger, "Wrote %zu bytes (=%zu uncompressed, ratio=%.1f%%) (%u sections)",
1674 size_t(fileLength),
1675 size_t(uncompressedSize),
1676 (100.f*float(fileLength))/float(uncompressedSize),
1677 unsigned(header.sectionCount));
1678 fclose(file);
1679 return true;
1680 }
1681
1682}
1683
1684bool Cogs::Core::writeModel(Context * context, uint32_t & numVertes, uint32_t & numIndexes, const StringView & fileName, const Model * model, WriteModelSettings * settings)
1685{
1686 auto flags = settings->flags;
1687
1688 if (!model) return false;
1689
1690 if (context->variables->get("cogsbin.writeVersion", 3) == 3) {
1691 return writeCogsBin3Model(context, numVertes, numIndexes, fileName, model, settings);
1692 }
1693 else {
1694 WriteContext wctx{};
1695 wctx.fileName = fileName.to_string();
1696 wctx.flags = WriteModelFlags(flags);
1697 if (settings) {
1698 wctx.settings = *settings;
1699 }
1700 recordModel(wctx, model, flags);
1701 write(context, wctx);
1702 numVertes = wctx.numVertes;
1703 numIndexes = wctx.numIndexes;
1704 }
1705 return true;
1706}
1707
1708bool Cogs::Core::writeModels(Context * context, uint32_t& numVertes, uint32_t& numIndexes, const StringView & fileName, ComponentModel::Entity ** entity, size_t N, WriteModelFlags flags)
1709{
1710 if (!entity || N == 0) return false;
1711
1712 if (context->variables->get("cogsbin.writeVersion", 3) == 3) {
1713 return writeCogsBin3Models(context, numVertes, numIndexes, fileName, entity, N, flags);
1714 }
1715 else {
1716 uint16_t section = 0;
1717 WriteContext wctx{};
1718 wctx.fileName = fileName.to_string();;
1719 wctx.flags = WriteModelFlags(flags);
1720 wctx.currentAnimation = nullptr;
1721 wctx.models.emplace_back(CogsBin::Model{});
1722 auto & dstModel = wctx.models.back();
1723
1724 dstModel.firstNode = recordEntity(context, wctx, ~0u, entity[0], section);
1725 for (size_t i = 1; i < N; i++) {
1726 recordEntity(context, wctx, ~0u, entity[i], section);
1727 }
1728 dstModel.nodeCount = uint32_t(wctx.nodes.size() - dstModel.firstNode);
1729
1730 if (wctx.currentAnimation) {
1731 dstModel.skeleton = recordSkeleton(wctx, wctx.currentAnimation->skeleton);
1732 dstModel.animClipFirst = nextIndex(wctx.animClips);
1733 dstModel.animClipCount = uint32_t(wctx.currentAnimation->clips.size());
1734 for (const auto & clip : wctx.currentAnimation->clips) recordAnimClip(wctx, clip, section);
1735 assert(dstModel.animClipFirst + dstModel.animClipCount == wctx.animClips.size());
1736 }
1737 else {
1738 dstModel.skeleton = ~0u;
1739 dstModel.animClipFirst = ~0u;
1740 dstModel.animClipCount = 0;
1741 }
1742
1743 write(context, wctx);
1744 numVertes = wctx.numVertes;
1745 numIndexes = wctx.numIndexes;
1746 }
1747 return true;
1748}
1749
1750bool Cogs::Core::writeModel(Context * context, const StringView & fileName, ComponentModel::Entity * entity, WriteModelFlags flags)
1751{
1752 uint32_t numVertes;
1753 uint32_t numIndexes;
1754 return writeModels(context, numVertes, numIndexes, fileName, &entity, 1, flags);
1755}
1756
1757bool Cogs::Core::writeModel(Context * context, uint32_t& numVertes, uint32_t& numIndexes, const StringView & fileName, ComponentModel::Entity * entity, WriteModelFlags flags)
1758{
1759 return writeModels(context, numVertes, numIndexes, fileName, &entity, 1, flags);
1760}
1761
1762
1763#if defined(_WIN32) && defined(MAKE_DEBUGABLE)
1764#pragma optimize( "", on )
1765#endif
1766
1767#else
1768// Emscripten
1769
1770bool Cogs::Core::writeModel(Context * /*context*/, uint32_t & /*numVertes*/, uint32_t & /*numIndexes*/, const StringView & /*fileName*/, const Model * /*model*/, WriteModelSettings * /*settings*/)
1771{
1772 return false;
1773}
1774
1775bool Cogs::Core::writeModels(Context * /*context*/, uint32_t& /*numVertes*/, uint32_t& /*numIndexes*/, const StringView & /*fileName*/, ComponentModel::Entity ** /*entity*/, size_t /*N*/, WriteModelFlags /*flags*/)
1776{
1777 return false;
1778}
1779
1780bool Cogs::Core::writeModel(Context * /*context*/, const StringView & /*fileName*/, ComponentModel::Entity * /*entity*/, WriteModelFlags /*flags*/)
1781{
1782 return false;
1783}
1784
1785bool Cogs::Core::writeModel(Context * /*context*/, uint32_t& /*numVertes*/, uint32_t& /*numIndexes*/, const StringView & /*fileName*/, ComponentModel::Entity * /*entity*/, WriteModelFlags /*flags*/)
1786{
1787 return false;
1788}
1789
1790#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
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
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: CogsBin.h:515
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.
MaterialOptions options
Material rendering options used by this instance.
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.
Defines options for rendering using a material instance.
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.
SamplerState::FilterMode filterMode
Filter mode to use when rendering with this texture.
SamplerState::AddressMode sMode
Address mode to use when rendering with this texture.
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
Single integral constant enumerator.
Definition: Type.h:58
const Name & getName() const
Get the name of the enumerator.
Definition: Type.h:68
const char * c_str() const
Gets the name as a null-terminated string.
Definition: Name.h:116
Encapsulates state for texture sampling in a state object.
Definition: SamplerState.h:12
ComparisonFunction comparisonFunction
Specifies the comparison function to use when applying a comparison sampler.
Definition: SamplerState.h:73
unsigned int maxAnisotropy
Specifies the maximum number of anisotropic samples to use when sampling a texture.
Definition: SamplerState.h:76
static SamplerState & DefaultState()
Constructs a sampler state initialized with the default values.
Definition: SamplerState.h:85
ComparisonFunction
Comparison functions applied when sampling depth buffers using comparison filters.
Definition: SamplerState.h:48
AddressMode
Addressing modes to use when sampling textures.
Definition: SamplerState.h:15
@ Clamp
Texture coordinates are clamped to the [0, 1] range.
Definition: SamplerState.h:17
@ Border
Texture color is set to the border color when outside [0, 1] range.
Definition: SamplerState.h:23
@ Wrap
Texture coordinates automatically wrap around to [0, 1] range.
Definition: SamplerState.h:19
@ Mirror
Texture coordinates are mirrored when outside [0, 1] range.
Definition: SamplerState.h:21
float borderColor[4]
Definition: SamplerState.h:80
FilterMode
Filter modes to specify how texture data is treated when sampled.
Definition: SamplerState.h:31
@ ComparisonMinMagMipPoint
Comparison filter for depth sample comparisons using point sampling.
Definition: SamplerState.h:38
@ ComparisonMinMagMipLinear
Comparison filter for depth sample comparisons using linear interpolation sampling.
Definition: SamplerState.h:40
@ MinMagMipPoint
Point sampling for both minification and magnification.
Definition: SamplerState.h:33
@ MinMagMipLinear
Linear sampling for both minification and magnification.
Definition: SamplerState.h:35
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