From db8c732ed99278b8eb4ab88183486a4bfc68fb75 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 16 Jan 2026 09:49:31 -0800 Subject: [PATCH 1/4] gltfpack: Implement support for half-precision positions/UVs When -vph or -vth is specified, similarly to -vpf or -vtf, we use half-precision floating-point attribute storage. This results in the same scene or material structure without extra transform nodes, but unlike -vpf/-vtf the resulting asset uses the same amount of memory as it does by default when using integer quantization. Half-precision floats have ~11 bits of relative precision (10 + implied 1); this is lower precision than either position or texture coordinates use by default. As such, some assets might see excessive deformation - however, unlike integer quantization, individual meshes do not have to fit the same coordinate grid, so in practice it may be fine. --- gltf/gltfpack.cpp | 13 +++++++++++++ gltf/gltfpack.h | 4 ++++ gltf/stream.cpp | 36 +++++++++++++++++++++++++++++++++++- gltf/write.cpp | 2 ++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/gltf/gltfpack.cpp b/gltf/gltfpack.cpp index 01dde94b3..2507be6c2 100644 --- a/gltf/gltfpack.cpp +++ b/gltf/gltfpack.cpp @@ -849,6 +849,7 @@ static size_t process(cgltf_data* data, const char* input_path, const char* outp const ExtensionInfo extensions[] = { {"KHR_mesh_quantization", settings.quantize, true}, + {"KHR_accessor_float16", settings.quantize && (settings.pos_half || settings.tex_half), true}, {meshopt_ext, settings.compress, !settings.fallback}, {"KHR_texture_transform", (settings.quantize && !settings.tex_float && !json_textures.empty()) || ext_texture_transform, false}, {"KHR_materials_pbrSpecularGlossiness", ext_pbr_specular_glossiness, false}, @@ -1338,10 +1339,20 @@ int main(int argc, char** argv) { settings.pos_float = true; } + else if (strcmp(arg, "-vph") == 0) + { + settings.pos_float = true; + settings.pos_half = true; + } else if (strcmp(arg, "-vtf") == 0) { settings.tex_float = true; } + else if (strcmp(arg, "-vth") == 0) + { + settings.tex_float = true; + settings.tex_half = true; + } else if (strcmp(arg, "-vnf") == 0) { settings.nrm_float = true; @@ -1667,8 +1678,10 @@ int main(int argc, char** argv) fprintf(stderr, "\t-vpi: use integer attributes for positions (default)\n"); fprintf(stderr, "\t-vpn: use normalized attributes for positions\n"); fprintf(stderr, "\t-vpf: use floating point attributes for positions\n"); + fprintf(stderr, "\t-vph: use half precision attributes for positions\n"); fprintf(stderr, "\nVertex attributes:\n"); fprintf(stderr, "\t-vtf: use floating point attributes for texture coordinates\n"); + fprintf(stderr, "\t-vth: use half precision attributes for texture coordinates\n"); fprintf(stderr, "\t-vnf: use floating point attributes for normals\n"); fprintf(stderr, "\t-vi: use interleaved vertex attributes (reduces compression efficiency)\n"); fprintf(stderr, "\t-kv: keep source vertex attributes even if they aren't used\n"); diff --git a/gltf/gltfpack.h b/gltf/gltfpack.h index e1cf03b3c..4e4b49f52 100644 --- a/gltf/gltfpack.h +++ b/gltf/gltfpack.h @@ -23,6 +23,8 @@ #include #include +static const cgltf_component_type cgltf_component_type_r_16f = (cgltf_component_type)5131; + struct Attr { float f[4]; @@ -126,7 +128,9 @@ struct Settings bool pos_normalized; bool pos_float; + bool pos_half; bool tex_float; + bool tex_half; bool nrm_float; int trn_bits; diff --git a/gltf/stream.cpp b/gltf/stream.cpp index be338314b..23be90de7 100644 --- a/gltf/stream.cpp +++ b/gltf/stream.cpp @@ -299,7 +299,15 @@ void getPositionBounds(float min[3], float max[3], const Stream& stream, const Q if (settings.quantize) { - if (settings.pos_float) + if (settings.pos_half) + { + for (int k = 0; k < 3; ++k) + { + min[k] = meshopt_dequantizeHalf(meshopt_quantizeHalf(min[k])); + max[k] = meshopt_dequantizeHalf(meshopt_quantizeHalf(max[k])); + } + } + else if (settings.pos_float) { for (int k = 0; k < 3; ++k) { @@ -466,6 +474,26 @@ static StreamFormat writeVertexStreamFloat(std::string& bin, const Stream& strea return format; } +static StreamFormat writeVertexStreamHalf(std::string& bin, const Stream& stream, cgltf_type type, int components, size_t stride) +{ + assert(components >= 1 && components <= 4); + assert(stride >= sizeof(uint16_t) * components); + + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + uint16_t v[4] = {}; + for (int k = 0; k < components; ++k) + v[k] = meshopt_quantizeHalf(a.f[k]); + + bin.append(reinterpret_cast(v), stride); + } + + StreamFormat format = {type, cgltf_component_type_r_16f, false, stride}; + return format; +} + StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings, bool filters) { if (stream.type == cgltf_attribute_type_position) @@ -473,6 +501,9 @@ StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const Qua if (!settings.quantize) return writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3); + if (settings.pos_half) + return writeVertexStreamHalf(bin, stream, cgltf_type_vec3, 3, sizeof(uint16_t) * 4); + if (settings.pos_float) return writeVertexStreamFloat(bin, stream, cgltf_type_vec3, 3, settings.compress && filters, qp.bits, settings.compressmore ? meshopt_EncodeExpSharedComponent : meshopt_EncodeExpSeparate); @@ -552,6 +583,9 @@ StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const Qua if (!settings.quantize) return writeVertexStreamRaw(bin, stream, cgltf_type_vec2, 2); + if (settings.tex_half) + return writeVertexStreamHalf(bin, stream, cgltf_type_vec2, 2, sizeof(uint16_t) * 2); + // expand the encoded range to ensure it covers [0..1) interval // this can slightly reduce precision but we should not need more precision inside 0..1, and this significantly improves compressed size when using encodeExpOne if (settings.tex_float) diff --git a/gltf/write.cpp b/gltf/write.cpp index b61718f56..a2d25abea 100644 --- a/gltf/write.cpp +++ b/gltf/write.cpp @@ -22,6 +22,8 @@ static const char* componentType(cgltf_component_type type) return "5125"; case cgltf_component_type_r_32f: return "5126"; + case cgltf_component_type_r_16f: + return "5131"; default: return "0"; } From 217fc7e921240d542fdfb31bf207fc0e5010dcfc Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 16 Jan 2026 09:56:32 -0800 Subject: [PATCH 2/4] gltfpack: Warn when -vph is used on meshes too large to encode Half-precision encoding will use infinity to represent values above 65504; we now detect this and warn, suggesting -vpf which has a much larger range. --- gltf/stream.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/gltf/stream.cpp b/gltf/stream.cpp index 23be90de7..779d647cc 100644 --- a/gltf/stream.cpp +++ b/gltf/stream.cpp @@ -164,6 +164,19 @@ QuantizationPosition prepareQuantizationPosition(const std::vector& meshes fprintf(stderr, "Warning: position data has significant error (%.0f%%); consider using floating-point quantization (-vpf) or more bits (-vp N)\n", max_rel_error * 100); } + if (b.isValid() && settings.quantize && settings.pos_half) + { + float amax = 0; + for (int k = 0; k < 3; ++k) + { + amax = std::max(amax, fabsf(b.min.f[0])); + amax = std::max(amax, fabsf(b.max.f[0])); + } + + if (amax > 65500) + fprintf(stderr, "Warning: position data can't be represented by half-precision floating point; consider using floating-point quantization (-vpf)\n"); + } + result.node_scale = result.scale / float((1 << result.bits) - 1) * (result.normalized ? 65535.f : 1.f); return result; From 2fcb879bea26b733c980cd8e80e1d0bd23f97488 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 16 Jan 2026 09:58:34 -0800 Subject: [PATCH 3/4] gltfpack: Update README.md Mention KHR_accessor_float16 support. --- gltf/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gltf/README.md b/gltf/README.md index d75863924..7e609829f 100644 --- a/gltf/README.md +++ b/gltf/README.md @@ -83,6 +83,7 @@ gltfpack supports most Khronos extensions and some multi-vendor extensions in th - KHR_materials_variants - KHR_materials_volume - KHR_mesh_quantization +- KHR_accessor_float16 - KHR_meshopt_compression - KHR_texture_basisu - KHR_texture_transform @@ -93,6 +94,7 @@ gltfpack supports most Khronos extensions and some multi-vendor extensions in th Even if the source file does not use extensions, gltfpack may use some extensions in the output file either by default or when certain options are used: - KHR_mesh_quantization (used by default unless disabled via `-noq`) +- KHR_accessor_float16 (used when requested via `-vph` or `-vth`) - KHR_meshopt_compression (used when requested via `-ce khr` or `-cz`) - KHR_texture_transform (used by default when textures are present, unless disabled via `-noq` or `-vtf`) - KHR_texture_basisu (used when requested via `-tc` or `-tu`) From 38be0142851e718fd0be74e33cc75aa06b2fcba3 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 16 Jan 2026 10:12:29 -0800 Subject: [PATCH 4/4] gltfpack: Move cgltf_component_type_r_16f to cgltf.h This avoids GCC warning for enum switches; this does mean we'll need to carry a cgltf.h patch around across version updates, but maybe this can be upstreamed in the future. --- extern/cgltf.h | 3 ++- gltf/gltfpack.h | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/extern/cgltf.h b/extern/cgltf.h index 316a11dfd..94424ecec 100644 --- a/extern/cgltf.h +++ b/extern/cgltf.h @@ -184,6 +184,7 @@ typedef enum cgltf_component_type cgltf_component_type_r_16u, /* UNSIGNED_SHORT */ cgltf_component_type_r_32u, /* UNSIGNED_INT */ cgltf_component_type_r_32f, /* FLOAT */ + cgltf_component_type_r_16f, /* HALF_FLOAT */ cgltf_component_type_max_enum } cgltf_component_type; @@ -4382,7 +4383,7 @@ static int cgltf_parse_json_diffuse_transmission(cgltf_options* options, jsmntok // Defaults cgltf_fill_float_array(out_diff_transmission->diffuse_transmission_color_factor, 3, 1.0f); out_diff_transmission->diffuse_transmission_factor = 0.f; - + for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); diff --git a/gltf/gltfpack.h b/gltf/gltfpack.h index 4e4b49f52..0adf6c14a 100644 --- a/gltf/gltfpack.h +++ b/gltf/gltfpack.h @@ -23,8 +23,6 @@ #include #include -static const cgltf_component_type cgltf_component_type_r_16f = (cgltf_component_type)5131; - struct Attr { float f[4];