diff options
Diffstat (limited to '3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp')
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp | 389 |
1 files changed, 282 insertions, 107 deletions
diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp index aa6c49bb0d9..e939f3f1362 100644 --- a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp @@ -84,6 +84,8 @@ struct Settings bool simplify_aggressive; bool compress; + bool fallback; + int verbose; }; @@ -180,6 +182,30 @@ cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_ return 0; } +void readAccessor(std::vector<float>& data, const cgltf_accessor* accessor) +{ + assert(accessor->type == cgltf_type_scalar); + + data.resize(accessor->count); + cgltf_accessor_unpack_floats(accessor, &data[0], data.size()); +} + +void readAccessor(std::vector<Attr>& data, const cgltf_accessor* accessor) +{ + size_t components = cgltf_num_components(accessor->type); + + std::vector<float> temp(accessor->count * components); + cgltf_accessor_unpack_floats(accessor, &temp[0], temp.size()); + + data.resize(accessor->count); + + for (size_t i = 0; i < accessor->count; ++i) + { + for (size_t k = 0; k < components && k < 4; ++k) + data[i].f[k] = temp[i * components + k]; + } +} + void transformPosition(float* ptr, const float* transform) { float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12]; @@ -291,10 +317,7 @@ void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes) } Stream s = {attr.type, attr.index}; - s.data.resize(attr.data->count); - - for (size_t i = 0; i < attr.data->count; ++i) - cgltf_accessor_read_float(attr.data, i, s.data[i].f, 4); + readAccessor(s.data, attr.data); result.streams.push_back(s); } @@ -314,10 +337,7 @@ void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes) } Stream s = {attr.type, attr.index, int(ti + 1)}; - s.data.resize(attr.data->count); - - for (size_t i = 0; i < attr.data->count; ++i) - cgltf_accessor_read_float(attr.data, i, s.data[i].f, 4); + readAccessor(s.data, attr.data); result.streams.push_back(s); } @@ -821,10 +841,10 @@ void reindexMesh(Mesh& mesh) } } -const Stream* getPositionStream(const Mesh& mesh) +Stream* getStream(Mesh& mesh, cgltf_attribute_type type) { for (size_t i = 0; i < mesh.streams.size(); ++i) - if (mesh.streams[i].type == cgltf_attribute_type_position) + if (mesh.streams[i].type == type) return &mesh.streams[i]; return 0; @@ -835,7 +855,7 @@ void simplifyMesh(Mesh& mesh, float threshold, bool aggressive) if (threshold >= 1) return; - const Stream* positions = getPositionStream(mesh); + const Stream* positions = getStream(mesh, cgltf_attribute_type_position); if (!positions) return; @@ -883,12 +903,59 @@ void optimizeMesh(Mesh& mesh) } } +struct BoneInfluence +{ + float i; + float w; + + bool operator<(const BoneInfluence& other) const + { + return i < other.i; + } +}; + +void sortBoneInfluences(Mesh& mesh) +{ + Stream* joints = getStream(mesh, cgltf_attribute_type_joints); + Stream* weights = getStream(mesh, cgltf_attribute_type_weights); + + if (!joints || !weights) + return; + + size_t vertex_count = mesh.streams[0].data.size(); + + for (size_t i = 0; i < vertex_count; ++i) + { + Attr& ja = joints->data[i]; + Attr& wa = weights->data[i]; + + BoneInfluence inf[4] = {}; + int count = 0; + + for (int k = 0; k < 4; ++k) + if (wa.f[k] > 0.f) + { + inf[count].i = ja.f[k]; + inf[count].w = wa.f[k]; + count++; + } + + std::sort(inf, inf + count); + + for (int k = 0; k < 4; ++k) + { + ja.f[k] = inf[k].i; + wa.f[k] = inf[k].w; + } + } +} + void simplifyPointMesh(Mesh& mesh, float threshold) { if (threshold >= 1) return; - const Stream* positions = getPositionStream(mesh); + const Stream* positions = getStream(mesh, cgltf_attribute_type_position); if (!positions) return; @@ -919,7 +986,7 @@ void simplifyPointMesh(Mesh& mesh, float threshold) void sortPointMesh(Mesh& mesh) { - const Stream* positions = getPositionStream(mesh); + const Stream* positions = getStream(mesh, cgltf_attribute_type_position); if (!positions) return; @@ -1870,13 +1937,20 @@ size_t getBufferView(std::vector<BufferView>& views, BufferView::Kind kind, int return views.size() - 1; } -void writeBufferView(std::string& json, BufferView::Kind kind, size_t count, size_t stride, size_t bin_offset, size_t bin_size, int compression) +void writeBufferView(std::string& json, BufferView::Kind kind, size_t count, size_t stride, size_t bin_offset, size_t bin_size, int compression, size_t compressed_offset, size_t compressed_size) { - append(json, "{\"buffer\":0"); - append(json, ",\"byteLength\":"); - append(json, bin_size); + assert(bin_size == count * stride); + + // when compression is enabled, we store uncompressed data in buffer 1 and compressed data in buffer 0 + // when compression is disabled, we store uncompressed data in buffer 0 + size_t buffer = compression >= 0 ? 1 : 0; + + append(json, "{\"buffer\":"); + append(json, buffer); append(json, ",\"byteOffset\":"); append(json, bin_offset); + append(json, ",\"byteLength\":"); + append(json, bin_size); if (kind == BufferView::Kind_Vertex) { append(json, ",\"byteStride\":"); @@ -1891,12 +1965,17 @@ void writeBufferView(std::string& json, BufferView::Kind kind, size_t count, siz { append(json, ",\"extensions\":{"); append(json, "\"MESHOPT_compression\":{"); - append(json, "\"mode\":"); + append(json, "\"buffer\":0"); + append(json, ",\"byteOffset\":"); + append(json, size_t(compressed_offset)); + append(json, ",\"byteLength\":"); + append(json, size_t(compressed_size)); + append(json, ",\"byteStride\":"); + append(json, stride); + append(json, ",\"mode\":"); append(json, size_t(compression)); append(json, ",\"count\":"); append(json, count); - append(json, ",\"byteStride\":"); - append(json, stride); append(json, "}}"); } append(json, "}"); @@ -1961,7 +2040,7 @@ float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type) } } -bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node) +bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node, Attr* out_first = 0) { const float tolerance = 1e-3f; @@ -1972,15 +2051,16 @@ bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_pat assert(sampler.input->count * value_stride * components == sampler.output->count); + std::vector<Attr> output; + readAccessor(output, sampler.output); + for (size_t j = 0; j < components; ++j) { - Attr first = {}; - cgltf_accessor_read_float(sampler.output, j * value_stride + value_offset, first.f, 4); + Attr first = output[j * value_stride + value_offset]; for (size_t i = 1; i < sampler.input->count; ++i) { - Attr attr = {}; - cgltf_accessor_read_float(sampler.output, (i * components + j) * value_stride + value_offset, attr.f, 4); + const Attr& attr = output[(i * components + j) * value_stride + value_offset]; if (getDelta(first, attr, type) > tolerance) return false; @@ -1992,8 +2072,7 @@ bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_pat { for (int k = 0; k < 2; ++k) { - Attr t = {}; - cgltf_accessor_read_float(sampler.output, (i * components + j) * 3 + k * 2, t.f, 4); + const Attr& t = output[(i * components + j) * 3 + k * 2]; float error = fabsf(t.f[0]) + fabsf(t.f[1]) + fabsf(t.f[2]) + fabsf(t.f[3]); @@ -2004,6 +2083,9 @@ bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_pat } } + if (out_first) + *out_first = output[value_offset]; + return true; } @@ -2093,6 +2175,11 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s { size_t components = (type == cgltf_animation_path_type_weights) ? target_node->mesh->primitives[0].targets_count : 1; + std::vector<float> input; + readAccessor(input, sampler.input); + std::vector<Attr> output; + readAccessor(output, sampler.output); + size_t cursor = 0; for (int i = 0; i < frames; ++i) @@ -2101,8 +2188,7 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s while (cursor + 1 < sampler.input->count) { - float next_time = 0; - cgltf_accessor_read_float(sampler.input, cursor + 1, &next_time, 1); + float next_time = input[cursor + 1]; if (next_time > time) break; @@ -2112,10 +2198,8 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s if (cursor + 1 < sampler.input->count) { - float cursor_time = 0; - float next_time = 0; - cgltf_accessor_read_float(sampler.input, cursor + 0, &cursor_time, 1); - cgltf_accessor_read_float(sampler.input, cursor + 1, &next_time, 1); + float cursor_time = input[cursor + 0]; + float next_time = input[cursor + 1]; float range = next_time - cursor_time; float inv_range = (range == 0.f) ? 0.f : 1.f / (next_time - cursor_time); @@ -2127,32 +2211,25 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s { case cgltf_interpolation_type_linear: { - Attr v0 = {}; - Attr v1 = {}; - cgltf_accessor_read_float(sampler.output, (cursor + 0) * components + j, v0.f, 4); - cgltf_accessor_read_float(sampler.output, (cursor + 1) * components + j, v1.f, 4); + const Attr& v0 = output[(cursor + 0) * components + j]; + const Attr& v1 = output[(cursor + 1) * components + j]; data.push_back(interpolateLinear(v0, v1, t, type)); } break; case cgltf_interpolation_type_step: { - Attr v = {}; - cgltf_accessor_read_float(sampler.output, cursor * components + j, v.f, 4); + const Attr& v = output[cursor * components + j]; data.push_back(v); } break; case cgltf_interpolation_type_cubic_spline: { - Attr v0 = {}; - Attr b0 = {}; - Attr a1 = {}; - Attr v1 = {}; - cgltf_accessor_read_float(sampler.output, (cursor * 3 + 1) * components + j, v0.f, 4); - cgltf_accessor_read_float(sampler.output, (cursor * 3 + 2) * components + j, b0.f, 4); - cgltf_accessor_read_float(sampler.output, (cursor * 3 + 3) * components + j, a1.f, 4); - cgltf_accessor_read_float(sampler.output, (cursor * 3 + 4) * components + j, v1.f, 4); + const Attr& v0 = output[(cursor * 3 + 1) * components + j]; + const Attr& b0 = output[(cursor * 3 + 2) * components + j]; + const Attr& a1 = output[(cursor * 3 + 3) * components + j]; + const Attr& v1 = output[(cursor * 3 + 4) * components + j]; data.push_back(interpolateHermite(v0, b0, v1, a1, t, range, type)); } break; @@ -2168,8 +2245,7 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s for (size_t j = 0; j < components; ++j) { - Attr v = {}; - cgltf_accessor_read_float(sampler.output, offset * components + j, v.f, 4); + const Attr& v = output[offset * components + j]; data.push_back(v); } } @@ -2193,8 +2269,15 @@ void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes) NodeInfo& ni = nodes[channel.target_node - data->nodes]; // mark nodes that have animation tracks that change their base transform as animated - if (!isTrackConstant(sampler, channel.target_path, channel.target_node)) + Attr first = {}; + if (!isTrackConstant(sampler, channel.target_path, channel.target_node, &first)) + { + ni.animated_paths |= (1 << channel.target_path); + } + else if (channel.target_path == cgltf_animation_path_type_weights) { + // we currently preserve constant weight tracks because the usecase is very rare and + // isTrackConstant doesn't return the full set of weights to compare against ni.animated_paths |= (1 << channel.target_path); } else @@ -2212,12 +2295,10 @@ void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes) case cgltf_animation_path_type_scale: memcpy(base.f, channel.target_node->scale, 3 * sizeof(float)); break; - default:; + default: + assert(!"Unknown target path"); } - Attr first = {}; - cgltf_accessor_read_float(sampler.output, 0, first.f, 4); - const float tolerance = 1e-3f; if (getDelta(base, first, channel.target_path) > tolerance) @@ -2896,6 +2977,53 @@ void writeLight(std::string& json, const cgltf_light& light) append(json, "}"); } +void finalizeBufferViews(std::string& json, std::vector<BufferView>& views, std::string& bin, std::string& fallback) +{ + for (size_t i = 0; i < views.size(); ++i) + { + BufferView& view = views[i]; + + size_t bin_offset = bin.size(); + size_t fallback_offset = fallback.size(); + + size_t count = view.data.size() / view.stride; + + int compression = -1; + + if (view.compressed) + { + if (view.kind == BufferView::Kind_Index) + { + compressIndexStream(bin, view.data, count, view.stride); + compression = 1; + } + else + { + compressVertexStream(bin, view.data, count, view.stride); + compression = 0; + } + + fallback += view.data; + } + else + { + bin += view.data; + } + + size_t raw_offset = (compression >= 0) ? fallback_offset : bin_offset; + + comma(json); + writeBufferView(json, view.kind, count, view.stride, raw_offset, view.data.size(), compression, bin_offset, bin.size() - bin_offset); + + // record written bytes for statistics + view.bytes = bin.size() - bin_offset; + + // align each bufferView by 4 bytes + bin.resize((bin.size() + 3) & ~3); + fallback.resize((fallback.size() + 3) & ~3); + } +} + void printMeshStats(const std::vector<Mesh>& meshes, const char* name) { size_t triangles = 0; @@ -2952,7 +3080,7 @@ void printAttributeStats(const std::vector<BufferView>& views, BufferView::Kind } } -void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings, std::string& json, std::string& bin) +void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings, std::string& json, std::string& bin, std::string& fallback) { if (settings.verbose) { @@ -3017,6 +3145,7 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin reindexMesh(mesh); simplifyMesh(mesh, settings.simplify_threshold, settings.simplify_aggressive); optimizeMesh(mesh); + sortBoneInfluences(mesh); break; default: @@ -3342,7 +3471,7 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin append(json, "\"KHR_lights_punctual\""); } append(json, "]"); - if (settings.compress) + if (settings.compress && !settings.fallback) { append(json, ",\"extensionsRequired\":["); // Note: ideally we should include MESHOPT_quantized_geometry in the required extension list (regardless of compression) @@ -3353,47 +3482,13 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin append(json, "]"); } - size_t bytes[BufferView::Kind_Count] = {}; - if (!views.empty()) { - append(json, ",\"bufferViews\":["); - for (size_t i = 0; i < views.size(); ++i) - { - BufferView& view = views[i]; - - size_t offset = bin.size(); - size_t count = view.data.size() / view.stride; - - int compression = -1; - - if (view.compressed) - { - if (view.kind == BufferView::Kind_Index) - { - compressIndexStream(bin, view.data, count, view.stride); - compression = 1; - } - else - { - compressVertexStream(bin, view.data, count, view.stride); - compression = 0; - } - } - else - { - bin += view.data; - } + std::string json_views; + finalizeBufferViews(json_views, views, bin, fallback); - comma(json); - writeBufferView(json, view.kind, count, view.stride, offset, bin.size() - offset, compression); - - view.bytes = bin.size() - offset; - bytes[view.kind] += view.bytes; - - // align each bufferView by 4 bytes - bin.resize((bin.size() + 3) & ~3); - } + append(json, ",\"bufferViews\":["); + append(json, json_views); append(json, "]"); } if (!json_accessors.empty()) @@ -3466,6 +3561,14 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin if (settings.verbose) { + size_t bytes[BufferView::Kind_Count] = {}; + + for (size_t i = 0; i < views.size(); ++i) + { + BufferView& view = views[i]; + bytes[view.kind] += view.bytes; + } + printf("output: %d nodes, %d meshes (%d primitives), %d materials\n", int(node_offset), int(mesh_offset), int(meshes.size()), int(material_offset)); printf("output: JSON %d bytes, buffers %d bytes\n", int(json.size()), int(bin.size())); printf("output: buffers: vertex %d bytes, index %d bytes, skin %d bytes, time %d bytes, keyframe %d bytes, image %d bytes\n", @@ -3495,6 +3598,56 @@ bool requiresExtension(cgltf_data* data, const char* name) return false; } +const char* getBaseName(const char* path) +{ + const char* slash = strrchr(path, '/'); + const char* backslash = strrchr(path, '\\'); + + const char* rs = slash ? slash + 1 : path; + const char* bs = backslash ? backslash + 1 : path; + + return std::max(rs, bs); +} + +std::string getBufferSpec(const char* bin_path, size_t bin_size, const char* fallback_path, size_t fallback_size, bool fallback_ref) +{ + std::string json; + append(json, "\"buffers\":["); + append(json, "{"); + if (bin_path) + { + append(json, "\"uri\":\""); + append(json, bin_path); + append(json, "\""); + } + comma(json); + append(json, "\"byteLength\":"); + append(json, bin_size); + append(json, "}"); + if (fallback_ref) + { + comma(json); + append(json, "{"); + if (fallback_path) + { + append(json, "\"uri\":\""); + append(json, fallback_path); + append(json, "\""); + } + comma(json); + append(json, "\"byteLength\":"); + append(json, fallback_size); + append(json, ",\"extensions\":{"); + append(json, "\"MESHOPT_compression\":{"); + append(json, "\"fallback\":true"); + append(json, "}}"); + append(json, "}"); + } + append(json, "]"); + + return json; +} + int gltfpack(const char* input, const char* output, const Settings& settings) { cgltf_data* data = 0; @@ -3549,8 +3702,8 @@ int gltfpack(const char* input, const char* output, const Settings& settings) return 2; } - std::string json, bin; - process(data, meshes, settings, json, bin); + std::string json, bin, fallback; + process(data, meshes, settings, json, bin, fallback); cgltf_free(data); @@ -3566,41 +3719,52 @@ int gltfpack(const char* input, const char* output, const Settings& settings) std::string binpath = output; binpath.replace(binpath.size() - 5, 5, ".bin"); - std::string binname = binpath; - std::string::size_type slash = binname.find_last_of("/\\"); - if (slash != std::string::npos) - binname.erase(0, slash + 1); + std::string fbpath = output; + fbpath.replace(fbpath.size() - 5, 5, ".fallback.bin"); FILE* outjson = fopen(output, "wb"); FILE* outbin = fopen(binpath.c_str(), "wb"); - if (!outjson || !outbin) + FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL; + if (!outjson || !outbin || (!outfb && settings.fallback)) { fprintf(stderr, "Error saving %s\n", output); return 4; } - fprintf(outjson, "{\"buffers\":[{\"uri\":\"%s\",\"byteLength\":%zu}],", binname.c_str(), bin.size()); + std::string bufferspec = getBufferSpec(getBaseName(binpath.c_str()), bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback.size(), settings.compress); + + fprintf(outjson, "{"); + fwrite(bufferspec.c_str(), bufferspec.size(), 1, outjson); + fprintf(outjson, ","); fwrite(json.c_str(), json.size(), 1, outjson); fprintf(outjson, "}"); fwrite(bin.c_str(), bin.size(), 1, outbin); + if (settings.fallback) + fwrite(fallback.c_str(), fallback.size(), 1, outfb); + fclose(outjson); fclose(outbin); + if (outfb) + fclose(outfb); } else if (oext && (strcmp(oext, ".glb") == 0 || strcmp(oext, ".GLB") == 0)) { + std::string fbpath = output; + fbpath.replace(fbpath.size() - 4, 4, ".fallback.bin"); + FILE* out = fopen(output, "wb"); - if (!out) + FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL; + if (!out || (!outfb && settings.fallback)) { fprintf(stderr, "Error saving %s\n", output); return 4; } - char bufferspec[64]; - sprintf(bufferspec, "{\"buffers\":[{\"byteLength\":%zu}],", bin.size()); + std::string bufferspec = getBufferSpec(NULL, bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback.size(), settings.compress); - json.insert(0, bufferspec); + json.insert(0, "{" + bufferspec + ","); json.push_back('}'); while (json.size() % 4) @@ -3621,7 +3785,12 @@ int gltfpack(const char* input, const char* output, const Settings& settings) writeU32(out, 0x004E4942); fwrite(bin.c_str(), bin.size(), 1, out); + if (settings.fallback) + fwrite(fallback.c_str(), fallback.size(), 1, outfb); + fclose(out); + if (outfb) + fclose(outfb); } else { @@ -3698,6 +3867,11 @@ int main(int argc, char** argv) { settings.compress = true; } + else if (strcmp(arg, "-cf") == 0) + { + settings.compress = true; + settings.fallback = true; + } else if (strcmp(arg, "-v") == 0) { settings.verbose = 1; @@ -3749,7 +3923,8 @@ int main(int argc, char** argv) fprintf(stderr, "-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n"); fprintf(stderr, "-si R: simplify meshes to achieve the ratio R (default: 1; R should be between 0 and 1)\n"); fprintf(stderr, "-sa: aggressively simplify to the target ratio disregarding quality\n"); - fprintf(stderr, "-c: produce compressed glb files\n"); + fprintf(stderr, "-c: produce compressed gltf/glb files\n"); + fprintf(stderr, "-cf: produce compressed gltf/glb files with fallback for loaders that don't support compression\n"); fprintf(stderr, "-v: verbose output\n"); fprintf(stderr, "-h: display this help and exit\n"); |