Бранимир Караџић 6 роки тому
батько
коміт
5f8326f20a

+ 101 - 1
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<unsigned char> 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<unsigned char> 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<unsigned char> 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
+}

+ 1 - 0
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*))

+ 1 - 0
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 <assert.h>

+ 34 - 3
3rdparty/meshoptimizer/src/vertexcodec.cpp

@@ -23,6 +23,14 @@
 #include <intrin.h> // __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.h> // __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 <tmmintrin.h>
 #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<const __m128i*>(&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

+ 89 - 25
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 <typename T>
 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<uint32_t> 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;
 }
 

+ 19 - 3
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)

+ 82 - 22
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<Mesh>& meshes, const Settings& settings)
 {
-	size_t write = 0;
-
 	for (size_t i = 0; i < meshes.size(); ++i)
 	{
-		if (meshes[i].streams.empty())
-			continue;
-
-		Mesh& target = meshes[write];
+		Mesh& target = meshes[i];
 
-		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();
-		}
+		if (target.streams.empty())
+			continue;
 
 		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<Mesh>& 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<Mesh>& 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<Mesh>& meshes, const Settings& settings)
 
 		assert(target.streams[0].data.size() == target_vertices);
 		assert(target.indices.size() == target_indices);
+	}
+}
+
+void filterEmptyMeshes(std::vector<Mesh>& 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<Stream> streams;
+			streams.swap(mesh.streams);
+
+			std::vector<unsigned int> 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<BufferView>& 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)
 		{

+ 0 - 125
3rdparty/meshoptimizer/tools/ktx2_format.h

@@ -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 <stdint.h>
-
-#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;