diff --git a/3rdparty/meshoptimizer/demo/tests.cpp b/3rdparty/meshoptimizer/demo/tests.cpp index ffab4c023..e0b9751b7 100644 --- a/3rdparty/meshoptimizer/demo/tests.cpp +++ b/3rdparty/meshoptimizer/demo/tests.cpp @@ -229,6 +229,79 @@ static void decodeVertexRejectMalformedHeaders() assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &brokenbuffer[0], brokenbuffer.size()) < 0); } +static void decodeVertexBitGroups() +{ + unsigned char data[16 * 4]; + + // this tests 0/2/4/8 bit groups in one stream + for (size_t i = 0; i < 16; ++i) + { + data[i * 4 + 0] = 0; + data[i * 4 + 1] = (unsigned char)(i * 1); + data[i * 4 + 2] = (unsigned char)(i * 2); + data[i * 4 + 3] = (unsigned char)(i * 8); + } + + std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); + + unsigned char decoded[16 * 4]; + assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, data, sizeof(data)) == 0); +} + +static void decodeVertexBitGroupSentinels() +{ + unsigned char data[16 * 4]; + + // this tests 0/2/4/8 bit groups and sentinels in one stream + for (size_t i = 0; i < 16; ++i) + { + if (i == 7 || i == 13) + { + data[i * 4 + 0] = 42; + data[i * 4 + 1] = 42; + data[i * 4 + 2] = 42; + data[i * 4 + 3] = 42; + } + else + { + data[i * 4 + 0] = 0; + data[i * 4 + 1] = (unsigned char)(i * 1); + data[i * 4 + 2] = (unsigned char)(i * 2); + data[i * 4 + 3] = (unsigned char)(i * 8); + } + } + + std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); + + unsigned char decoded[16 * 4]; + assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, data, sizeof(data)) == 0); +} + +static void decodeVertexLarge() +{ + unsigned char data[128 * 4]; + + // this tests 0/2/4/8 bit groups in one stream + for (size_t i = 0; i < 128; ++i) + { + data[i * 4 + 0] = 0; + data[i * 4 + 1] = (unsigned char)(i * 1); + data[i * 4 + 2] = (unsigned char)(i * 2); + data[i * 4 + 3] = (unsigned char)(i * 8); + } + + std::vector buffer(meshopt_encodeVertexBufferBound(128, 4)); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 128, 4)); + + unsigned char decoded[128 * 4]; + assert(meshopt_decodeVertexBuffer(decoded, 128, 4, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, data, sizeof(data)) == 0); +} + static void clusterBoundsDegenerate() { const float vbd[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; @@ -310,6 +383,8 @@ static void customAllocator() // customAlloc & customFree should not get called anymore meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12); assert(allocCount == 6 && freeCount == 6); + + allocCount = freeCount = 0; } static void emptyMesh() @@ -356,7 +431,7 @@ static void simplifyPointsStuck() assert(meshopt_simplifyPoints(0, vb, 3, 12, 0) == 0); } -void runTests() +static void runTestsOnce() { decodeIndexV0(); decodeIndex16(); @@ -370,6 +445,9 @@ void runTests() decodeVertexMemorySafe(); decodeVertexRejectExtraBytes(); decodeVertexRejectMalformedHeaders(); + decodeVertexBitGroups(); + decodeVertexBitGroupSentinels(); + decodeVertexLarge(); clusterBoundsDegenerate(); @@ -381,3 +459,25 @@ void runTests() simplifySloppyStuck(); simplifyPointsStuck(); } + +namespace meshopt +{ +extern unsigned int cpuid; +} + +void runTests() +{ + runTestsOnce(); + +#if !(defined(__AVX__) || defined(__SSSE3__)) && (defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__)) + // When SSSE3/AVX support isn't enabled unconditionally, we use a cpuid-based fallback + // It's useful to be able to test scalar code in this case, so we temporarily fake the feature bits + // and restore them later + unsigned int cpuid = meshopt::cpuid; + meshopt::cpuid = 0; + + runTestsOnce(); + + meshopt::cpuid = cpuid; +#endif +} diff --git a/3rdparty/meshoptimizer/src/allocator.cpp b/3rdparty/meshoptimizer/src/allocator.cpp index 8136b5842..da7cc540b 100644 --- a/3rdparty/meshoptimizer/src/allocator.cpp +++ b/3rdparty/meshoptimizer/src/allocator.cpp @@ -1,3 +1,4 @@ +// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details #include "meshoptimizer.h" void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*)) diff --git a/3rdparty/meshoptimizer/src/stripifier.cpp b/3rdparty/meshoptimizer/src/stripifier.cpp index 85ccf48fb..8ce17ef3d 100644 --- a/3rdparty/meshoptimizer/src/stripifier.cpp +++ b/3rdparty/meshoptimizer/src/stripifier.cpp @@ -1,3 +1,4 @@ +// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details #include "meshoptimizer.h" #include diff --git a/3rdparty/meshoptimizer/src/vertexcodec.cpp b/3rdparty/meshoptimizer/src/vertexcodec.cpp index 31b8a4de2..c26f1ae9d 100644 --- a/3rdparty/meshoptimizer/src/vertexcodec.cpp +++ b/3rdparty/meshoptimizer/src/vertexcodec.cpp @@ -23,6 +23,14 @@ #include // __cpuid #endif +// GCC 4.9+ and clang 3.8+ support targeting SIMD instruction sets from individual functions +#if !defined(SIMD_SSE) && !defined(SIMD_AVX) && ((defined(__clang__) && __clang_major__ * 100 + __clang_minor__ >= 308) || (defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ >= 409)) && (defined(__i386__) || defined(__x86_64__)) +#define SIMD_SSE +#define SIMD_FALLBACK +#define SIMD_TARGET __attribute__((target("ssse3"))) +#include // __cpuid +#endif + #if !defined(SIMD_NEON) && defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) #define SIMD_NEON #endif @@ -32,6 +40,10 @@ #define SIMD_WASM #endif +#ifndef SIMD_TARGET +#define SIMD_TARGET +#endif + #ifdef SIMD_SSE #include #endif @@ -446,6 +458,7 @@ static bool gDecodeBytesGroupInitialized = decodeBytesGroupBuildTables(); #endif #ifdef SIMD_SSE +SIMD_TARGET static __m128i decodeShuffleMask(unsigned char mask0, unsigned char mask1) { __m128i sm0 = _mm_loadl_epi64(reinterpret_cast(&kDecodeBytesGroupShuffle[mask0])); @@ -457,6 +470,7 @@ static __m128i decodeShuffleMask(unsigned char mask0, unsigned char mask1) return _mm_unpacklo_epi64(sm0, sm1r); } +SIMD_TARGET static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int bitslog2) { switch (bitslog2) @@ -814,6 +828,7 @@ static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi #endif #if defined(SIMD_SSE) || defined(SIMD_AVX) +SIMD_TARGET static void transpose8(__m128i& x0, __m128i& x1, __m128i& x2, __m128i& x3) { __m128i t0 = _mm_unpacklo_epi8(x0, x1); @@ -827,6 +842,7 @@ static void transpose8(__m128i& x0, __m128i& x1, __m128i& x2, __m128i& x3) x3 = _mm_unpackhi_epi16(t1, t3); } +SIMD_TARGET static __m128i unzigzag8(__m128i v) { __m128i xl = _mm_sub_epi8(_mm_setzero_si128(), _mm_and_si128(v, _mm_set1_epi8(1))); @@ -884,6 +900,7 @@ static v128_t unzigzag8(v128_t v) #endif #if defined(SIMD_SSE) || defined(SIMD_AVX) || defined(SIMD_NEON) || defined(SIMD_WASM) +SIMD_TARGET static const unsigned char* decodeBytesSimd(const unsigned char* data, const unsigned char* data_end, unsigned char* buffer, size_t buffer_size) { assert(buffer_size % kByteGroupSize == 0); @@ -929,6 +946,7 @@ static const unsigned char* decodeBytesSimd(const unsigned char* data, const uns return data; } +SIMD_TARGET static const unsigned char* decodeVertexBlockSimd(const unsigned char* data, const unsigned char* data_end, unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, unsigned char last_vertex[256]) { assert(vertex_count > 0 && vertex_count <= kVertexBlockMaxSize); @@ -1027,6 +1045,21 @@ static const unsigned char* decodeVertexBlockSimd(const unsigned char* data, con } #endif +#if defined(SIMD_SSE) && defined(SIMD_FALLBACK) +static unsigned int getCpuFeatures() +{ + int cpuinfo[4] = {}; +#if defined(_MSC_VER) && !defined(__clang__) + __cpuid(cpuinfo, 1); +#else + __cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); +#endif + return cpuinfo[2]; +} + +unsigned int cpuid = getCpuFeatures(); +#endif + } // namespace meshopt size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size) @@ -1140,9 +1173,7 @@ int meshopt_decodeVertexBuffer(void* destination, size_t vertex_count, size_t ve const unsigned char* (*decode)(const unsigned char*, const unsigned char*, unsigned char*, size_t, size_t, unsigned char[256]) = 0; #if defined(SIMD_SSE) && defined(SIMD_FALLBACK) - int cpuinfo[4] = {}; - __cpuid(cpuinfo, 1); - decode = (cpuinfo[2] & (1 << 9)) ? decodeVertexBlockSimd : decodeVertexBlock; + decode = (cpuid & (1 << 9)) ? decodeVertexBlockSimd : decodeVertexBlock; #elif defined(SIMD_SSE) || defined(SIMD_AVX) || defined(SIMD_NEON) || defined(SIMD_WASM) decode = decodeVertexBlockSimd; #else diff --git a/3rdparty/meshoptimizer/tools/basistoktx.cpp b/3rdparty/meshoptimizer/tools/basistoktx.cpp index b8add3550..884a0704f 100644 --- a/3rdparty/meshoptimizer/tools/basistoktx.cpp +++ b/3rdparty/meshoptimizer/tools/basistoktx.cpp @@ -9,7 +9,66 @@ #include "basisu_format.h" #include "khr_df.h" -#include "ktx2_format.h" + +// KTX Specification: 2. File Structure +struct Ktx2Header +{ + uint8_t identifier[12]; + uint32_t vkFormat; + uint32_t typeSize; + uint32_t pixelWidth; + uint32_t pixelHeight; + uint32_t pixelDepth; + uint32_t layerCount; + uint32_t faceCount; + uint32_t levelCount; + uint32_t supercompressionScheme; + + uint32_t dfdByteOffset; + uint32_t dfdByteLength; + uint32_t kvdByteOffset; + uint32_t kvdByteLength; + uint64_t sgdByteOffset; + uint64_t sgdByteLength; +}; + +struct Ktx2LevelIndex +{ + uint64_t byteOffset; + uint64_t byteLength; + uint64_t uncompressedByteLength; +}; + +enum +{ + Ktx2SupercompressionSchemeBasis = 1, +}; + +// KTX Specification: 3.1. identifier +static const uint8_t Ktx2FileIdentifier[12] = { + 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A, +}; + +// KTX Specification: 3.12.2. Basis Universal Global Data +struct Ktx2BasisGlobalHeader +{ + uint32_t globalFlags; + uint16_t endpointCount; + uint16_t selectorCount; + uint32_t endpointsByteLength; + uint32_t selectorsByteLength; + uint32_t tablesByteLength; + uint32_t extendedByteLength; +}; + +struct Ktx2BasisImageDesc +{ + uint32_t imageFlags; + uint32_t rgbSliceByteOffset; + uint32_t rgbSliceByteLength; + uint32_t alphaSliceByteOffset; + uint32_t alphaSliceByteLength; +}; template static void read(const std::string& data, size_t offset, T& result) @@ -99,16 +158,17 @@ std::string basisToKtx(const std::string& basis, bool srgb) uint32_t height = slices[0].m_orig_height; uint32_t levels = has_alpha ? uint32_t(slices.size()) / 2 : uint32_t(slices.size()); - KTX_header2 ktx_header = {KTX2_IDENTIFIER_REF}; + Ktx2Header ktx_header = {}; + memcpy(ktx_header.identifier, Ktx2FileIdentifier, sizeof(Ktx2FileIdentifier)); ktx_header.typeSize = 1; ktx_header.pixelWidth = width; ktx_header.pixelHeight = height; ktx_header.layerCount = 0; ktx_header.faceCount = 1; ktx_header.levelCount = levels; - ktx_header.supercompressionScheme = KTX_SUPERCOMPRESSION_BASIS; + ktx_header.supercompressionScheme = Ktx2SupercompressionSchemeBasis; - size_t header_size = sizeof(KTX_header2) + levels * sizeof(ktxLevelIndexEntry); + size_t header_size = sizeof(Ktx2Header) + levels * sizeof(Ktx2LevelIndex); std::vector dfd; createDfd(dfd, has_alpha ? 4 : 3, srgb); @@ -138,17 +198,17 @@ std::string basisToKtx(const std::string& basis, bool srgb) size_t dfd_size = dfd.size() * sizeof(uint32_t); size_t bgd_size = - sizeof(ktxBasisGlobalHeader) + sizeof(ktxBasisSliceDesc) * levels + + sizeof(Ktx2BasisGlobalHeader) + sizeof(Ktx2BasisImageDesc) * levels + basis_header.m_endpoint_cb_file_size + basis_header.m_selector_cb_file_size + basis_header.m_tables_file_size; - ktx_header.dataFormatDescriptor.byteOffset = uint32_t(header_size); - ktx_header.dataFormatDescriptor.byteLength = uint32_t(dfd_size); + ktx_header.dfdByteOffset = uint32_t(header_size); + ktx_header.dfdByteLength = uint32_t(dfd_size); - ktx_header.keyValueData.byteOffset = uint32_t(header_size + dfd_size); - ktx_header.keyValueData.byteLength = uint32_t(kvp_size); + ktx_header.kvdByteOffset = uint32_t(header_size + dfd_size); + ktx_header.kvdByteLength = uint32_t(kvp_size); - ktx_header.supercompressionGlobalData.byteOffset = (header_size + dfd_size + kvp_size + 7) & ~7; - ktx_header.supercompressionGlobalData.byteLength = bgd_size; + ktx_header.sgdByteOffset = (header_size + dfd_size + kvp_size + 7) & ~7; + ktx_header.sgdByteLength = bgd_size; // KTX2 header write(ktx, ktx_header); @@ -157,7 +217,7 @@ std::string basisToKtx(const std::string& basis, bool srgb) for (size_t i = 0; i < levels; ++i) { - ktxLevelIndexEntry le = {}; // This will be patched later + Ktx2LevelIndex le = {}; // This will be patched later write(ktx, le); } @@ -170,7 +230,7 @@ std::string basisToKtx(const std::string& basis, bool srgb) ktx.resize((ktx.size() + 7) & ~7); // supercompression global data - ktxBasisGlobalHeader sgd_header = {}; + Ktx2BasisGlobalHeader sgd_header = {}; sgd_header.globalFlags = basis_header.m_flags; sgd_header.endpointCount = uint16_t(basis_header.m_total_endpoints); sgd_header.selectorCount = uint16_t(basis_header.m_total_selectors); @@ -185,8 +245,8 @@ std::string basisToKtx(const std::string& basis, bool srgb) for (size_t i = 0; i < levels; ++i) { - ktxBasisSliceDesc sgd_slice = {}; // This will be patched later - write(ktx, sgd_slice); + Ktx2BasisImageDesc sgd_image = {}; // This will be patched later + write(ktx, sgd_image); } ktx.append(basis.substr(basis_header.m_endpoint_cb_file_ofs, basis_header.m_endpoint_cb_file_size)); @@ -199,12 +259,14 @@ std::string basisToKtx(const std::string& basis, bool srgb) // mip levels for (size_t i = 0; i < levels; ++i) { - size_t slice_index = (levels - i - 1) * (has_alpha + 1); + size_t level_index = levels - i - 1; + size_t slice_index = level_index * (has_alpha + 1); + const basist::basis_slice_desc& slice = slices[slice_index]; const basist::basis_slice_desc* slice_alpha = has_alpha ? &slices[slice_index + 1] : 0; assert(slice.m_image_index == 0); - assert(slice.m_level_index == levels - i - 1); + assert(slice.m_level_index == level_index); size_t file_offset = ktx.size(); @@ -213,29 +275,31 @@ std::string basisToKtx(const std::string& basis, bool srgb) if (slice_alpha) ktx.append(basis.substr(slice_alpha->m_file_ofs, slice_alpha->m_file_size)); - ktxLevelIndexEntry le = {}; + Ktx2LevelIndex le = {}; le.byteOffset = file_offset; le.byteLength = ktx.size() - file_offset; le.uncompressedByteLength = 0; - write(ktx, ktx_level_offset + i * sizeof(ktxLevelIndexEntry), le); + write(ktx, ktx_level_offset + level_index * sizeof(Ktx2LevelIndex), le); - ktxBasisSliceDesc sgd_slice = {}; - sgd_slice.sliceByteOffset = 0; - sgd_slice.sliceByteLength = slice.m_file_size; + Ktx2BasisImageDesc sgd_image = {}; + sgd_image.rgbSliceByteOffset = 0; + sgd_image.rgbSliceByteLength = slice.m_file_size; if (slice_alpha) { - sgd_slice.alphaSliceByteOffset = slice.m_file_size; - sgd_slice.alphaSliceByteLength = slice_alpha->m_file_size; + sgd_image.alphaSliceByteOffset = slice.m_file_size; + sgd_image.alphaSliceByteLength = slice_alpha->m_file_size; } - write(ktx, sgd_level_offset + i * sizeof(ktxBasisSliceDesc), sgd_slice); + write(ktx, sgd_level_offset + level_index * sizeof(Ktx2BasisImageDesc), sgd_image); if (i + 1 != levels) ktx.resize((ktx.size() + 7) & ~7); } + ktx.resize((ktx.size() + 7) & ~7); + return ktx; } diff --git a/3rdparty/meshoptimizer/tools/cgltf.h b/3rdparty/meshoptimizer/tools/cgltf.h index 67d310efc..63ecb91fc 100644 --- a/3rdparty/meshoptimizer/tools/cgltf.h +++ b/3rdparty/meshoptimizer/tools/cgltf.h @@ -125,6 +125,7 @@ typedef enum cgltf_result cgltf_result_file_not_found, cgltf_result_io_error, cgltf_result_out_of_memory, + cgltf_result_legacy_gltf, } cgltf_result; typedef enum cgltf_buffer_view_type @@ -763,7 +764,7 @@ cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_s uint32_t version = tmp; if (version != GlbVersion) { - return cgltf_result_unknown_format; + return version < GlbVersion ? cgltf_result_legacy_gltf : cgltf_result_unknown_format; } // Total length @@ -1707,6 +1708,7 @@ cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size #define CGLTF_ERROR_JSON -1 #define CGLTF_ERROR_NOMEM -2 +#define CGLTF_ERROR_LEGACY -3 #define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; } #define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */ @@ -1826,7 +1828,10 @@ static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* toke static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size) { (void)json_chunk; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (tokens[i].type != JSMN_ARRAY) + { + return tokens[i].type == JSMN_OBJECT ? CGLTF_ERROR_LEGACY : CGLTF_ERROR_JSON; + } if (*out_array) { return CGLTF_ERROR_JSON; @@ -4049,6 +4054,11 @@ static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* token } } + if (out_asset->version && atof(out_asset->version) < 2) + { + return CGLTF_ERROR_LEGACY; + } + return i; } @@ -4315,7 +4325,13 @@ cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, if (i < 0) { cgltf_free(data); - return (i == CGLTF_ERROR_NOMEM) ? cgltf_result_out_of_memory : cgltf_result_invalid_gltf; + + switch (i) + { + case CGLTF_ERROR_NOMEM: return cgltf_result_out_of_memory; + case CGLTF_ERROR_LEGACY: return cgltf_result_legacy_gltf; + default: return cgltf_result_invalid_gltf; + } } if (cgltf_fixup_pointers(data) < 0) diff --git a/3rdparty/meshoptimizer/tools/gltfpack.cpp b/3rdparty/meshoptimizer/tools/gltfpack.cpp index 755de4389..d95edcc8f 100644 --- a/3rdparty/meshoptimizer/tools/gltfpack.cpp +++ b/3rdparty/meshoptimizer/tools/gltfpack.cpp @@ -177,12 +177,12 @@ struct BufferView size_t bytes; }; -const char* getError(cgltf_result result) +const char* getError(cgltf_result result, cgltf_data* data) { switch (result) { case cgltf_result_file_not_found: - return "file not found"; + return data ? "resource not found" : "file not found"; case cgltf_result_io_error: return "I/O error"; @@ -196,6 +196,15 @@ const char* getError(cgltf_result result) case cgltf_result_out_of_memory: return "out of memory"; + case cgltf_result_legacy_gltf: + return "legacy GLTF"; + + case cgltf_result_data_too_short: + return data ? "buffer too short" : "not a GLTF file"; + + case cgltf_result_unknown_format: + return data ? "unknown resource format" : "not a GLTF file"; + default: return "unknown error"; } @@ -776,29 +785,18 @@ void mergeMeshes(Mesh& target, const Mesh& mesh) void mergeMeshes(std::vector& meshes, const Settings& settings) { - size_t write = 0; - for (size_t i = 0; i < meshes.size(); ++i) { - if (meshes[i].streams.empty()) + Mesh& target = meshes[i]; + + if (target.streams.empty()) continue; - Mesh& target = meshes[write]; - - if (i != write) - { - Mesh& mesh = meshes[i]; - - // note: this copy is expensive; we could use move in C++11 or swap manually which is a bit painful... - target = mesh; - - mesh.streams.clear(); - mesh.indices.clear(); - } - size_t target_vertices = target.streams[0].data.size(); size_t target_indices = target.indices.size(); + size_t last_merged = i; + for (size_t j = i + 1; j < meshes.size(); ++j) { Mesh& mesh = meshes[j]; @@ -807,6 +805,7 @@ void mergeMeshes(std::vector& meshes, const Settings& settings) { target_vertices += mesh.streams[0].data.size(); target_indices += mesh.indices.size(); + last_merged = j; } } @@ -815,7 +814,7 @@ void mergeMeshes(std::vector& meshes, const Settings& settings) target.indices.reserve(target_indices); - for (size_t j = i + 1; j < meshes.size(); ++j) + for (size_t j = i + 1; j <= last_merged; ++j) { Mesh& mesh = meshes[j]; @@ -830,6 +829,39 @@ void mergeMeshes(std::vector& meshes, const Settings& settings) assert(target.streams[0].data.size() == target_vertices); assert(target.indices.size() == target_indices); + } +} + +void filterEmptyMeshes(std::vector& meshes) +{ + size_t write = 0; + + for (size_t i = 0; i < meshes.size(); ++i) + { + Mesh& mesh = meshes[i]; + + if (mesh.streams.empty()) + continue; + + if (mesh.streams[0].data.empty()) + continue; + + if (mesh.type == cgltf_primitive_type_triangles && mesh.indices.empty()) + continue; + + if (i != write) + { + // the following code is roughly equivalent to meshes[write] = std::move(mesh) + std::vector streams; + streams.swap(mesh.streams); + + std::vector indices; + indices.swap(mesh.indices); + + meshes[write] = mesh; + meshes[write].streams.swap(streams); + meshes[write].indices.swap(indices); + } write++; } @@ -2940,7 +2972,7 @@ void writeImage(std::string& json, std::vector& views, const cgltf_i if (settings.texture_basis) { std::string full_path = getFullPath(image.uri, input_path); - std::string basis_path = getFileName(image.uri) + (settings.texture_ktx2 ? ".ktx" : ".basis"); + std::string basis_path = getFileName(image.uri) + (settings.texture_ktx2 ? ".ktx2" : ".basis"); std::string basis_full_path = getFullPath(basis_path.c_str(), output_path); if (readFile(full_path.c_str(), img_data)) @@ -3693,6 +3725,7 @@ void process(cgltf_data* data, const char* input_path, const char* output_path, mergeMeshMaterials(data, meshes); mergeMeshes(meshes, settings); + filterEmptyMeshes(meshes); markNeededNodes(data, nodes, meshes, settings); @@ -3710,6 +3743,8 @@ void process(cgltf_data* data, const char* input_path, const char* output_path, processMesh(meshes[i], settings); } + filterEmptyMeshes(meshes); // some meshes may become empty after processing + if (settings.verbose) { printMeshStats(meshes, "output"); @@ -4106,6 +4141,29 @@ std::string getBufferSpec(const char* bin_path, size_t bin_size, const char* fal return json; } +bool needsDummyBuffers(cgltf_data* data) +{ + for (size_t i = 0; i < data->accessors_count; ++i) + { + cgltf_accessor* accessor = &data->accessors[i]; + + if (accessor->buffer_view && accessor->buffer_view->buffer->data == NULL) + return true; + + if (accessor->is_sparse) + { + cgltf_accessor_sparse* sparse = &accessor->sparse; + + if (sparse->indices_buffer_view->buffer->data == NULL) + return true; + if (sparse->values_buffer_view->buffer->data == NULL) + return true; + } + } + + return false; +} + int gltfpack(const char* input, const char* output, const Settings& settings) { cgltf_data* data = 0; @@ -4117,17 +4175,19 @@ int gltfpack(const char* input, const char* output, const Settings& settings) { cgltf_options options = {}; cgltf_result result = cgltf_parse_file(&options, input, &data); - result = (result == cgltf_result_success) ? cgltf_validate(data) : result; result = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, input) : result; + result = (result == cgltf_result_success) ? cgltf_validate(data) : result; const char* error = NULL; if (result != cgltf_result_success) - error = getError(result); + error = getError(result, data); else if (requiresExtension(data, "KHR_draco_mesh_compression")) error = "file requires Draco mesh compression support"; else if (requiresExtension(data, "MESHOPT_compression")) error = "file has already been compressed using gltfpack"; + else if (needsDummyBuffers(data)) + error = "buffer has no data"; if (error) { diff --git a/3rdparty/meshoptimizer/tools/ktx2_format.h b/3rdparty/meshoptimizer/tools/ktx2_format.h deleted file mode 100644 index e97f8c4fa..000000000 --- a/3rdparty/meshoptimizer/tools/ktx2_format.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2010-2018 The Khronos Group Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -/* - * Author: Mark Callow from original code by Georg Kolling - */ - -/* - * Converted from ktxint.h + basis_sgd.h by extracting meaningful structures for gltfpack - */ - -#pragma once - -#include - -#define KTX2_IDENTIFIER_REF { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A } -#define KTX2_HEADER_SIZE (80) - -typedef enum ktxSupercmpScheme { - KTX_SUPERCOMPRESSION_NONE = 0, /*!< No supercompression. */ - KTX_SUPERCOMPRESSION_BASIS = 1, /*!< Basis Universal supercompression. */ - KTX_SUPERCOMPRESSION_LZMA = 2, /*!< LZMA supercompression. */ - KTX_SUPERCOMPRESSION_ZLIB = 3, /*!< Zlib supercompression. */ - KTX_SUPERCOMPRESSION_ZSTD = 4, /*!< ZStd supercompression. */ - KTX_SUPERCOMPRESSION_BEGIN_RANGE = KTX_SUPERCOMPRESSION_NONE, - KTX_SUPERCOMPRESSION_END_RANGE = KTX_SUPERCOMPRESSION_ZSTD, - KTX_SUPERCOMPRESSION_BEGIN_VENDOR_RANGE = 0x10000, - KTX_SUPERCOMPRESSION_END_VENDOR_RANGE = 0x1ffff, - KTX_SUPERCOMPRESSION_BEGIN_RESERVED = 0x20000, -} ktxSupercmpScheme; - -/** - * @internal - * @~English - * @brief 32-bit KTX 2 index entry. - */ -typedef struct ktxIndexEntry32 { - uint32_t byteOffset; /*!< Offset of item from start of file. */ - uint32_t byteLength; /*!< Number of bytes of data in the item. */ -} ktxIndexEntry32; -/** - * @internal - * @~English - * @brief 64-bit KTX 2 index entry. - */ -typedef struct ktxIndexEntry64 { - uint64_t byteOffset; /*!< Offset of item from start of file. */ - uint64_t byteLength; /*!< Number of bytes of data in the item. */ -} ktxIndexEntry64; - -/** - * @internal - * @~English - * @brief KTX 2 file header. - * - * See the KTX 2 specification for descriptions. - */ -typedef struct KTX_header2 { - uint8_t identifier[12]; - uint32_t vkFormat; - uint32_t typeSize; - uint32_t pixelWidth; - uint32_t pixelHeight; - uint32_t pixelDepth; - uint32_t layerCount; - uint32_t faceCount; - uint32_t levelCount; - uint32_t supercompressionScheme; - ktxIndexEntry32 dataFormatDescriptor; - ktxIndexEntry32 keyValueData; - ktxIndexEntry64 supercompressionGlobalData; -} KTX_header2; - -/* This will cause compilation to fail if the struct size doesn't match */ -typedef int KTX_header2_SIZE_ASSERT [sizeof(KTX_header2) == KTX2_HEADER_SIZE]; - -/** - * @internal - * @~English - * @brief KTX 2 level index entry. - */ -typedef struct ktxLevelIndexEntry { - uint64_t byteOffset; /*!< Offset of level from start of file. */ - uint64_t byteLength; - /*!< Number of bytes of compressed image data in the level. */ - uint64_t uncompressedByteLength; - /*!< Number of bytes of uncompressed image data in the level. */ -} ktxLevelIndexEntry; - -typedef struct ktxBasisGlobalHeader { - uint32_t globalFlags; - uint16_t endpointCount; - uint16_t selectorCount; - uint32_t endpointsByteLength; - uint32_t selectorsByteLength; - uint32_t tablesByteLength; - uint32_t extendedByteLength; -} ktxBasisGlobalHeader; - -// This header is followed by imageCount "slice" descriptions. - -// 1, or 2 slices per image (i.e. layer, face & slice). -// These offsets are relative to start of a mip level as given by the -// main levelIndex. -typedef struct ktxBasisSliceDesc { - uint32_t sliceFlags; - uint32_t sliceByteOffset; - uint32_t sliceByteLength; - uint32_t alphaSliceByteOffset; - uint32_t alphaSliceByteLength; -} ktxBasisSliceDesc; \ No newline at end of file