Cogs.Core
MeshoptDecompressor.cpp
1#include "MeshoptDecompressor.h"
2#include "GltfLoader.h"
3
4#include "Foundation/Logging/Logger.h"
5#include "Resources/MeshManager.h"
6
7#include <span>
8
9using namespace Cogs::Core;
10
11#define DEBUG_MESHOPT 0
12
13namespace {
14 Cogs::Logging::Log logger = Cogs::Logging::getLogger("GltfLoader::MeshoptDecompress");
15}
16
17bool
18MeshoptDecompressor::registerBufferViewCompression(uint32_t bufferViewIdx, const GltfLoader::Object& properties)
19{
21 bvd.originalBufferViewIdx = bufferViewIdx;
22
23 //
24 // Required properties
25 //
26 if (!properties.HasMember("buffer")) {
27 LOG_ERROR(logger, "EXT_meshopt_compression does not contain a 'buffer' property");
28 return false;
29 }
30 bvd.bufferIdx = properties["buffer"].GetInt();
31
32 if (!properties.HasMember("byteLength")) {
33 LOG_ERROR(logger, "EXT_meshopt_compression does not contain a 'byteLength' property");
34 return false;
35 }
36 bvd.byteLength = properties["byteLength"].GetInt();
37
38 if (!properties.HasMember("byteStride")) {
39 LOG_ERROR(logger, "EXT_meshopt_compression does not contain a 'byteStride' property");
40 return false;
41 }
42 bvd.byteStride = properties["byteStride"].GetInt();
43
44 if (!properties.HasMember("count")) {
45 LOG_ERROR(logger, "EXT_meshopt_compression does not contain a 'count' property");
46 return false;
47 }
48 bvd.count = properties["count"].GetInt();
49
50 if (!properties.HasMember("mode")) {
51 LOG_ERROR(logger, "EXT_meshopt_compression does not contain a 'mode' property");
52 return false;
53 }
54 std::string m = properties["mode"].GetString();
55 if (m == "ATTRIBUTES") { bvd.mode = Mode::ATTRIBUTES; }
56 else if (m == "TRIANGLES") { bvd.mode = Mode::TRIANGLES; }
57 else if (m == "INDICES") { bvd.mode = Mode::INDICES; }
58 else {
59 LOG_ERROR(logger, "Unknown 'mode' in glTF: '%s'", m.c_str());
60 return false;
61 }
62
63 //
64 // Optional properties
65 //
66 if (properties.HasMember("byteOffset")) {
67 bvd.byteOffset = properties["byteOffset"].GetInt();
68 }
69
70 if (properties.HasMember("filter")) {
71 std::string f = properties["filter"].GetString();
72 if (f == "OCTAHEDRAL") { bvd.filter = Filter::OCTAHEDRAL; }
73 else if (f == "QUATERNION") { bvd.filter = Filter::QUATERNION; }
74 else if (f == "EXPONENTIAL") { bvd.filter = Filter::EXPONENTIAL; }
75 else {
76 LOG_ERROR(logger, "Unknown 'filter' in glTF: '%s'", f.c_str());
77 return false;
78 }
79 }
80
81 //
82 // Validate according to spec rules
83 // FIXME: Validate that the parent bufferview also follows the spec.
84 //
85 if (bvd.mode == Mode::ATTRIBUTES && (bvd.byteStride % 4 != 0 || bvd.byteStride > 255)) {
86 LOG_ERROR(logger, "When mode is ATTRIBUTES then byteStride must divisible by 4 and less than 256");
87 return false;
88 }
89
90 if (bvd.mode == Mode::TRIANGLES && (bvd.count % 3 != 0)) {
91 LOG_ERROR(logger, "When mode is TRIANGLES then count must divisible by 3");
92 return false;
93 }
94
95 if (bvd.mode == Mode::INDICES || bvd.mode == Mode::TRIANGLES) {
96 if (bvd.byteStride != 2 && bvd.byteStride != 4) {
97 LOG_ERROR(logger, "When mode is INDICES or TRIANGLES then byteStride must be 2 or 4 (is %d)", bvd.byteStride);
98 return false;
99 }
100 if (bvd.filter != Filter::NONE) {
101 LOG_ERROR(logger, "When mode is INDICES or TRIANGLES then FITLER must be NONE or omitted");
102 return false;
103 }
104 }
105
106 if (bvd.filter == Filter::OCTAHEDRAL && (bvd.byteStride != 4 && bvd.byteStride != 8)) {
107 LOG_ERROR(logger, "When FILTER is OCTAHEDRAL then byteStride must be 4 or 8 (is %d)", bvd.byteStride);
108 return false;
109 }
110
111 if (bvd.filter == Filter::QUATERNION && bvd.byteStride != 8) {
112 LOG_ERROR(logger, "When FILTER is QUATERNION then byteStride must be 8 (is %d)", bvd.byteStride);
113 return false;
114 }
115
116 if (bvd.filter == Filter::EXPONENTIAL && (bvd.byteStride % 4 != 0)) {
117 LOG_ERROR(logger, "When FILTER is EXPONENTIAL then byteStride must divisible by 4");
118 return false;
119 }
120
121#if DEBUG_MESHOPT
122 LOG_DEBUG(logger, "EXT_meshopt_decompressor registered for BufferView %d", bufferViewIdx);
123 LOG_DEBUG(logger, " SrcBuffer=%d, len=%d, stride=%d, count=%d, offset=%d, filter=%d",
124 bvd.bufferIdx, bvd.byteLength, bvd.byteStride, bvd.count, bvd.byteOffset, bvd.filter);
125#endif
126
127 assert(!this->bufferViewDecomps.contains(bufferViewIdx) && "Bufferview already registered");
128 this->bufferViewDecomps[bufferViewIdx] = bvd;
129 return true;
130}
131
132bool
133MeshoptDecompressor::isCompressed(GltfLoader::GltfModelDefinition& loadData, int accessorIdx) const
134{
135 uint32_t bufferViewIdx = getBufferViewIdxFromAccessorIdx(loadData, accessorIdx);
136 return this->bufferViewDecomps.contains(bufferViewIdx);
137}
138
139bool
140MeshoptDecompressor::decompress(GltfLoader::GltfModelDefinition& loadData, uint32_t accessorIdx)
141{
142 uint32_t bufferViewIdx = getBufferViewIdxFromAccessorIdx(loadData, accessorIdx);
143 assert(this->bufferViewDecomps.contains(bufferViewIdx) && "BufferView not registered as Meshopt compressed");
144 BufferViewDecomp & bvd = this->bufferViewDecomps[bufferViewIdx];
145
146 switch (bvd.mode) {
147 case Mode::ATTRIBUTES: return this->processAttributes(bvd, loadData, accessorIdx);
148 case Mode::INDICES: return this->processIndices(bvd, loadData, accessorIdx);
149 case Mode::TRIANGLES: return this->processTriangles(bvd, loadData, accessorIdx);
150 default:
151 LOG_ERROR(logger, "Unknown 'EXT_meshopt_compression' mode: %d", int(bvd.mode));
152 break;
153 }
154
155 return false;
156}
157
158int
159MeshoptDecompressor::getBufferViewIdxFromAccessorIdx(GltfLoader::GltfModelDefinition& loadData, int accessorIdx) const
160{
161 const GltfLoader::GltfAccessor& accessor = loadData.accessors[accessorIdx];
162 return accessor.bufferView;
163}
164
165void
166MeshoptDecompressor::normalizeBBoxValue(Cogs::Core::GltfLoader::GltfAccessor& accessor, int idx) const
167{
168 switch (accessor.componentType) {
169 case GltfLoader::AccessorComponentType::UnsignedByte:
170 accessor.min.s_float[idx] = accessor.min.s_float[idx] / 255.0f;
171 accessor.max.s_float[idx] = accessor.max.s_float[idx] / 255.0f;
172 break;
173 case GltfLoader::AccessorComponentType::Byte:
174 accessor.min.s_float[idx] = std::fmax(accessor.min.s_float[idx] / 127.0f, -1.0f);
175 accessor.max.s_float[idx] = std::fmax(accessor.max.s_float[idx] / 127.0f, -1.0f);
176 break;
177 case GltfLoader::AccessorComponentType::UnsignedShort:
178 accessor.min.s_float[idx] = accessor.min.s_float[idx] / 65335.0f;
179 accessor.max.s_float[idx] = accessor.max.s_float[idx] / 65335.0f;
180 break;
181 case GltfLoader::AccessorComponentType::Short:
182 accessor.min.s_float[idx] = std::fmax(accessor.min.s_float[idx] / 32767.0f, -1.0f);
183 accessor.max.s_float[idx] = std::fmax(accessor.max.s_float[idx] / 32767.0f, -1.0f);
184 break;
185 case GltfLoader::AccessorComponentType::UnsignedInt:
186 accessor.min.s_float[idx] = accessor.min.s_float[idx] / float(0xFFFFFFFF);
187 accessor.max.s_float[idx] = accessor.max.s_float[idx] / float(0xFFFFFFFF);
188 break;
189 default:
190 LOG_ERROR(logger, "Unhandled accessor type: %s", GltfLoader::accessorTypeName[int(accessor.componentType)]);
191 break;
192 }
193}
194
195void
196MeshoptDecompressor::convertScalarAccessorBBoxToFloat(GltfLoader::GltfAccessor& accessor) const
197{
198 if (accessor.minCount == 0 || accessor.maxCount == 0 || accessor.componentType == GltfLoader::AccessorComponentType::Float) {
199 return;
200 }
201
202 if (accessor.componentType == GltfLoader::AccessorComponentType::UnsignedByte ||
203 accessor.componentType == GltfLoader::AccessorComponentType::UnsignedShort ||
204 accessor.componentType == GltfLoader::AccessorComponentType::UnsignedInt) {
205 accessor.min.s_float[0] = float(accessor.min.s_uint[0]);
206 accessor.max.s_float[0] = float(accessor.max.s_uint[0]);
207 }
208 else if (accessor.componentType == GltfLoader::AccessorComponentType::Byte ||
209 accessor.componentType == GltfLoader::AccessorComponentType::Short) {
210 accessor.min.s_float[0] = float(accessor.min.s_int[0]);
211 accessor.max.s_float[0] = float(accessor.max.s_int[0]);
212 }
213 else {
214 assert(false && "Unsupported component type");
215 }
216
217 if (accessor.normalized) {
218 normalizeBBoxValue(accessor, 0);
219 }
220}
221
222
223bool
224MeshoptDecompressor::processAttributes(BufferViewDecomp& bvd, GltfLoader::GltfModelDefinition& loadData, int accessorIdx)
225{
226 GltfLoader::GltfAccessor& accessor = loadData.accessors[accessorIdx];
227
228#if DEBUG_MESHOPT
229 LOG_DEBUG(logger, " accessor(%d): bufferView=%d, byteOffset=%d, cType=%s, count=%d, normalized=%s",
230 accessorIdx, accessor.bufferView, accessor.byteOffset, GltfLoader::accessorComponentTypeName[int(accessor.componentType)], accessor.count, accessor.normalized ? "TRUE" : "FALSE");
231 LOG_DEBUG(logger, " Already decompressed: %s", this->decompressedBufferViews.contains(accessor.bufferView) ? "TRUE" : "FALSE");
232#endif
233
234 // Only decompress buffer once. Reuse result if needed.
235 if (!this->decompressedBufferViews.contains(accessor.bufferView)) {
236 this->memoryBuffers.push_back(Memory::MemoryBuffer());
237 Memory::MemoryBuffer* membuf = &this->memoryBuffers.back();
238
239 membuf->resize(bvd.count * bvd.byteStride);
240 uint8_t * targetBuffer = static_cast<uint8_t*>(membuf->data());
241
242 const unsigned char* vbuf = &(loadData.buffer_data[bvd.bufferIdx].data())[bvd.byteOffset];
243 int err = meshopt_decodeVertexBuffer(targetBuffer, bvd.count, bvd.byteStride, vbuf, bvd.byteLength);
244 if (err != 0) {
245 LOG_ERROR(logger, "The meshopt lib failed decoding the vertex buffer. Return value: %d", err);
246 switch (err) {
247 case -1: LOG_ERROR(logger, " -> error(-1): Illegal header data"); break;
248 case -2: LOG_ERROR(logger, " -> error(-2): Index out of bounds"); break;
249 case -3: LOG_ERROR(logger, " -> error(-3): Could not process all data"); break;
250 default: break;
251 }
252 return false;
253 }
254
255 if (bvd.filter != Filter::NONE) {
256 int filterSize = bvd.count;
257 bool ok = this->transformData(targetBuffer, filterSize, bvd.byteStride, bvd.filter);
258 if (!ok) {
259 LOG_ERROR(logger, "Error filtering vertices");
260 return false;
261 }
262 }
263
265 Memory::MemoryBuffer* convertedMemBuf = membuf;
266 int newStride = bvd.byteStride;
267
268 if (accessor.componentType != GltfLoader::AccessorComponentType::Float) {
269 GltfLoader::BufferDataView tmpBufferDataView = GltfLoader::BufferDataView{ targetBuffer, bvd.byteStride, bvd.count };
270 this->memoryBuffers.push_back(Memory::MemoryBuffer());
271 convertedMemBuf = &this->memoryBuffers.back();
272
273 if (accessor.type == GltfLoader::AccessorType::Vec3) {
274 newBDV = GltfLoader::convertVecBufferToFloats<glm::vec3>(tmpBufferDataView, accessor.componentType, convertedMemBuf, accessor.normalized);
275 }
276 else if (accessor.type == GltfLoader::AccessorType::Vec2) {
277 newBDV = GltfLoader::convertVecBufferToFloats<glm::vec2>(tmpBufferDataView, accessor.componentType, convertedMemBuf, accessor.normalized);
278 }
279 else if (accessor.type == GltfLoader::AccessorType::Vec4) {
280 newBDV = GltfLoader::convertVecBufferToFloats<glm::vec4>(tmpBufferDataView, accessor.componentType, convertedMemBuf, accessor.normalized);
281 }
282
283 newStride = newBDV.stride;
284 }
285
286 // Mark bufferview as decompressed
287 this->decompressedBufferViews.insert(accessor.bufferView);
288
289 // Add a new buffer to the buffer-list and update the original BufferView to point to this buffer instead of the original
290 assert(convertedMemBuf->size() > 0);
291 size_t newBufferIdx = loadData.buffer_data.size();
292 loadData.buffer_data.push_back(std::span<const uint8_t>(static_cast<uint8_t*>(convertedMemBuf->data()), static_cast<uint8_t*>(convertedMemBuf->data()) + convertedMemBuf->size()));
293 loadData.buffer_views[accessor.bufferView].buffer = uint32_t(newBufferIdx);
294 loadData.buffer_views[accessor.bufferView].byteOffset = 0;
295 loadData.buffer_views[accessor.bufferView].byteLength = uint32_t(convertedMemBuf->size());
296 loadData.buffer_views[accessor.bufferView].byteStride = newStride;
297 }
298
299 // We need to convert the accessor's BBOX to floats as the component type is changed to float.
300 if (accessor.type == GltfLoader::AccessorType::Vec3) {
301 this->convertVecAccessorBBoxToFloat<glm::vec3>(accessor);
302 }
303 else if (accessor.type == GltfLoader::AccessorType::Vec2) {
304 this->convertVecAccessorBBoxToFloat<glm::vec2>(accessor);
305 }
306 else if (accessor.type == GltfLoader::AccessorType::Vec4) {
307 this->convertVecAccessorBBoxToFloat<glm::vec4>(accessor);
308 }
309 else if (accessor.type == GltfLoader::AccessorType::Scalar) {
310 this->convertScalarAccessorBBoxToFloat(accessor);
311 }
312 else {
313 LOG_WARNING(logger, "Unknown bbox type: %s", GltfLoader::accessorTypeName[int(accessor.type)]);
314 }
315
316 // Update the Accessor with a correct "byteOffset" which matches the decompressed data.
317 const int newOffset = (accessor.byteOffset / bvd.byteStride) * loadData.buffer_views[accessor.bufferView].byteStride;
318 loadData.accessors[accessorIdx].byteOffset = newOffset;
319 loadData.accessors[accessorIdx].componentType = GltfLoader::AccessorComponentType::Float;
320
321 return true;
322}
323
324bool
325MeshoptDecompressor::processIndices(BufferViewDecomp& bvd, GltfLoader::GltfModelDefinition& loadData, int accessorIdx)
326{
327 GltfLoader::GltfAccessor& accessor = loadData.accessors[accessorIdx];
328
329#if DEBUG_MESHOPT
330 LOG_DEBUG(logger, "processIndices (bufferIdx=%d, count=%d, stride=%d, offset=%d, byteLen=%d, filter=%d)", bvd.bufferIdx, bvd.count, bvd.byteStride, bvd.byteOffset, bvd.byteLength, bvd.filter);
331 LOG_DEBUG(logger, " accessor: bufferView=%d, byteOffset=%d, cType=%s, count=%d, normalized=%s",
332 accessor.bufferView, accessor.byteOffset, GltfLoader::accessorComponentTypeName[int(accessor.componentType)], accessor.count, accessor.normalized ? "TRUE" : "FALSE");
333 LOG_DEBUG(logger, " Already decompressed: %s", this->decompressedBufferViews.contains(accessor.bufferView) ? "TRUE" : "FALSE");
334#endif
335
336 // Only decompress buffer once. Reuse result if needed.
337 if (!this->decompressedBufferViews.contains(accessor.bufferView)) {
338
339 const unsigned char* ibuf = &(loadData.buffer_data[bvd.bufferIdx].data())[bvd.byteOffset];
340
341 this->memoryBuffers.push_back(Memory::MemoryBuffer());
342 Memory::MemoryBuffer* membuf = &this->memoryBuffers.back();
343
344 membuf->resize(bvd.count * bvd.byteStride);
345 uint8_t* targetBuffer = static_cast<uint8_t*>(membuf->data());
346
347 int err = meshopt_decodeIndexSequence(reinterpret_cast<uint32_t*>(targetBuffer), bvd.count, ibuf, bvd.byteLength);
348 if (err != 0) {
349 LOG_ERROR(logger, "The meshopt lib failed decoding the index sequence. Return value: %d", err);
350 switch (err) {
351 case -1: LOG_ERROR(logger, " -> error(-1): Illegal header data"); break;
352 case -2: LOG_ERROR(logger, " -> error(-2): Index out of bounds"); break;
353 case -3: LOG_ERROR(logger, " -> error(-3): Could not process all the data"); break;
354 default: break;
355 }
356 return false;
357 }
358
359 // Mark bufferview as decompressed
360 this->decompressedBufferViews.insert(accessor.bufferView);
361
362 // Add a new buffer to the buffer-list and update the original BufferView to point to this buffer instead of the original
363 size_t newBufferIdx = loadData.buffer_data.size();
364 loadData.buffer_data.push_back(std::span<const uint8_t>(static_cast<uint8_t*>(membuf->data()), static_cast<uint8_t*>(membuf->data()) + membuf->size()));
365 loadData.buffer_views[accessor.bufferView].buffer = uint32_t(newBufferIdx);
366 loadData.buffer_views[accessor.bufferView].byteOffset = 0;
367 loadData.buffer_views[accessor.bufferView].byteLength = uint32_t(membuf->size());
368 loadData.buffer_views[accessor.bufferView].byteStride = bvd.byteStride;
369 }
370
371 // Update the Accessor with a correct "byteOffset" which matches the decompressed data.
372 const int newOffset = (accessor.byteOffset / bvd.byteStride) * loadData.buffer_views[accessor.bufferView].byteStride;
373 loadData.accessors[accessorIdx].byteOffset = newOffset;
374
375 return true;
376}
377
378bool
379MeshoptDecompressor::processTriangles(BufferViewDecomp& bvd, GltfLoader::GltfModelDefinition& loadData, int accessorIdx)
380{
381 GltfLoader::GltfAccessor& accessor = loadData.accessors[accessorIdx];
382
383#if DEBUG_MESHOPT
384 LOG_DEBUG(logger, "processTriangles (bufferIdx=%d, count=%d, stride=%d, offset=%d, byteLen=%d, filter=%d). accessorIdx=%d",
385 bvd.bufferIdx, bvd.count, bvd.byteStride, bvd.byteOffset, bvd.byteLength, bvd.filter, accessorIdx);
386 LOG_DEBUG(logger, " accessor: bufferView=%d, byteOffset=%d, cType=%s, count=%d, normalized=%s",
387 accessor.bufferView, accessor.byteOffset, GltfLoader::accessorComponentTypeName[(int)accessor.componentType], accessor.count, accessor.normalized ? "TRUE" : "FALSE");
388 LOG_DEBUG(logger, " Already decompressed: %s", this->decompressedBufferViews.contains(accessor.bufferView) ? "TRUE" : "FALSE");
389#endif
390
391 // Only decompress buffer once. Reuse result if needed.
392 if (!this->decompressedBufferViews.contains(accessor.bufferView)) {
393 const unsigned char* ibuf = &(loadData.buffer_data[bvd.bufferIdx].data())[bvd.byteOffset];
394
395 this->memoryBuffers.push_back(Memory::MemoryBuffer());
396 Memory::MemoryBuffer* membuf = &this->memoryBuffers.back();
397 membuf->resize(bvd.count * bvd.byteStride);
398 uint8_t * targetBuffer = static_cast<uint8_t*>(membuf->data());
399
400 int err = 0;
401 switch (bvd.byteStride) {
402 case 2:
403 err = meshopt_decodeIndexBuffer(reinterpret_cast<uint16_t*>(targetBuffer), bvd.count, ibuf, bvd.byteLength);
404 break;
405 case 4:
406 err = meshopt_decodeIndexBuffer(reinterpret_cast<uint32_t*>(targetBuffer), bvd.count, ibuf, bvd.byteLength);
407 break;
408 default:
409 LOG_ERROR(logger, "Illegal byte-stride for compressed indices: %d. Must be 2 or 4.", bvd.byteStride);
410 return false;
411 };
412
413 if (err != 0) {
414 LOG_ERROR(logger, "The meshopt lib failed decoding the triangles/index buffer. Error code: %d", err);
415 switch (err) {
416 case -1: LOG_ERROR(logger, " error(-1): Illegal header data"); break;
417 case -2: LOG_ERROR(logger, " error(-2): Index out of bounds"); break;
418 case -3: LOG_ERROR(logger, " error(-3): Could not process all the data"); break;
419 default: break;
420 }
421 return false;
422 }
423
424 // Mark bufferview as decompressed
425 this->decompressedBufferViews.insert(accessor.bufferView);
426
427 // Add a new buffer to the buffer-list and update the original BufferView to point to this buffer instead of the original
428 size_t newBufferIdx = loadData.buffer_data.size();
429 loadData.buffer_data.push_back(std::span<const uint8_t>(static_cast<uint8_t*>(membuf->data()), static_cast<uint8_t*>(membuf->data()) + membuf->size()));
430 loadData.buffer_views[accessor.bufferView].buffer = uint32_t(newBufferIdx);
431 loadData.buffer_views[accessor.bufferView].byteOffset = 0;
432 loadData.buffer_views[accessor.bufferView].byteLength = uint32_t(membuf->size());
433 loadData.buffer_views[accessor.bufferView].byteStride = bvd.byteStride;
434 }
435
436 // Update the Accessor with a correct "byteOffset" which matches the decompressed data.
437 const int newOffset = (accessor.byteOffset / bvd.byteStride) * loadData.buffer_views[accessor.bufferView].byteStride;
438 loadData.accessors[accessorIdx].byteOffset = newOffset;
439
440 return true;
441}
442
443#undef DEBUG_MESHOPT
bool registerBufferViewCompression(uint32_t bufferViewIdx, const GltfLoader::Object &properties)
Returns false if something failed.
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180