feat: LOD 网格分级——meshopt 解析期简化 + 按距离/级联选级

vendored meshoptimizer 1.2(submodule 环境故障改直接 vendor)。Mesh
多 LOD:各级索引连续拼进同一 index buffer(共享顶点,LodRange 区间
选择),解析期 meshopt_simplify 生成链(LOD0=原始,每级目标减半,
相对误差 1e-2,小网格复用上级);都在 worker 线程,加载零额外卡顿。

SceneRenderer 按 (mesh, LOD) 聚合 batch(lookup key = 指针<<8|lod):
主视图按屏占比近似 radius/dist 选级(阈值 10/20 倍半径);阴影级联
按级联序降级(越远越简)。Draw 按 batch.lod 选索引区间。

A/B 对比(100 背包 10×10,RTX 5060 Laptop,vsync off):
- 纯背包:LOD 开 93-95 fps / 关 90-107 fps —— 无显著差异
- 加 SunTemple:LOD 开 52-56 / 关 51-55 —— 无显著差异
结论:当前场景瓶颈不在顶点数(5060 顶点吞吐富余),LOD 基础设施
保留(几何复杂资产/低端 GPU/更大场景生效),帧率问题转向 Tracy
GPU 时间线定位真实瓶颈。

坑:LodRange 记录"LOD 不足复用最后一级"时 first 不得继续累加
(buffer 无重复数据,累加必越界——VUID-08798 抓的)。
This commit is contained in:
2026-09-24 20:53:19 +08:00
parent 34b593f734
commit 5bdfb53566
125 changed files with 67608 additions and 29 deletions
+133
View File
@@ -0,0 +1,133 @@
# 📦 gltfpack
gltfpack is a tool that can automatically optimize glTF files to reduce the download size and improve loading and rendering speed.
## Installation
You can download a pre-built binary for gltfpack on [Releases page](https://github.com/zeux/meshoptimizer/releases), or install [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended over npm since they can work with larger files, run faster, and support texture compression.
## Usage
To convert a glTF file using gltfpack, run the command-line binary like this on an input `.gltf`/`.glb`/`.obj` file (run it without arguments for a list of options):
```
gltfpack -i scene.gltf -o scene.glb
```
gltfpack substantially changes the glTF data by optimizing the meshes for vertex fetch and transform cache, quantizing the geometry to reduce the memory consumption and size, merging meshes to reduce the draw call count, quantizing and resampling animations to reduce animation size and simplify playback, and pruning the node tree by removing or collapsing redundant nodes. It will also simplify the meshes when requested to do so.
By default gltfpack outputs regular `.glb`/`.gltf` files that have been optimized for GPU consumption using various cache optimizers and quantization. These files can be loaded by GLTF loaders that support `KHR_mesh_quantization` extension such as [three.js](https://threejs.org/) (r111+) and [Babylon.js](https://www.babylonjs.com/) (4.1+).
When using `-c` option, gltfpack outputs compressed `.glb`/`.gltf` files that use meshoptimizer codecs to reduce the download size further. Loading these files requires extending GLTF loaders with support for [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md) extension; three.js supports it in r122+ (requires calling `GLTFLoader.setMeshoptDecoder`), Babylon.js supports it in 5.0+ without further setup.
For better compression, you can use `-cc` option which applies additional compression; additionally make sure that your content delivery method is configured to use deflate (gzip) - meshoptimizer codecs are designed to produce output that can be compressed further with general purpose compressors. For maximum compression, consider using [KHR_meshopt_compression](https://github.com/KhronosGroup/glTF/pull/2517) extension with a higher compression level (`-cz` option).
gltfpack can also compress textures using Basis Universal format stored in a KTX2 container (`-tc` flag, requires support for `KHR_texture_basisu`) or using WebP format (`-tw` flag, requires support for `EXT_texture_webp`).
When working with glTF files that contain point clouds, gltfpack automatically processes the point cloud data to reduce the download size to the extent possible. In addition to aforementioned compression options (either `-c` or `-cc` are recommended), gltfpack can also prune point clouds to provide a more uniform density when `-si` option is used.
## Decompression
When using compressed files, [js/meshopt_decoder.mjs](https://github.com/zeux/meshoptimizer/blob/master/js/meshopt_decoder.mjs) needs to be loaded to provide the WebAssembly decoder module like this:
```js
import { MeshoptDecoder } from './meshopt_decoder.mjs';
...
var loader = new GLTFLoader();
loader.setMeshoptDecoder(MeshoptDecoder);
loader.load('pirate.glb', function (gltf) { scene.add(gltf.scene); });
```
When using Three.js, this module can be imported from three.js repository from `examples/jsm/libs/meshopt_decoder.module.js`.
Note that `meshopt_decoder` assumes that WebAssembly is supported. This is the case for all modern browsers; if support for legacy browsers such as Internet Explorer 11 is desired, it's recommended to use `-cf` flag when creating the glTF content. This will create and load fallback uncompressed buffers, but only on browsers that don't support WebAssembly.
## Options
By default gltfpack makes certain assumptions when optimizing the scenes, for example meshes that belong to nodes that aren't animated can be merged together, and has some defaults that represent a tradeoff between precision and size that are picked to fit most use cases. However, in some cases the resulting `.gltf` file needs to retain some way for the application to manipulate individual scene elements, and in other cases precision or size are more important to optimize for. gltfpack has a rich set of command line options to control various aspects of its behavior, with the full list available via `gltfpack -h`.
The following settings are frequently used to reduce the resulting data size:
* `-cc`: produce compressed gltf/glb files (requires `EXT_meshopt_compression`)
* `-tc`: convert all textures to KTX2 with BasisU supercompression (requires `KHR_texture_basisu` and may require `-tp` flag for compatibility with WebGL 1)
* `-tw`: convert all textures to WebP (requires `EXT_texture_webp`)
* `-mi`: use mesh instancing when serializing references to the same meshes (requires `EXT_mesh_gpu_instancing`)
* `-si R`: simplify meshes targeting triangle/point count ratio R (default: 1; R should be between 0 and 1)
The following settings are frequently used to restrict some optimizations:
* `-kn`: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally
* `-km`: keep named materials and disable named material merging
* `-ke`: keep extras data
* `-vpf`: use floating-point position quantization instead of the default fixed-point (this results in larger position data, but does not insert new nodes with dequantization transforms; when using this option, `-cc` is recommended as well)
## Extensions
gltfpack supports most Khronos extensions and some multi-vendor extensions in the input scenes, with newer extensions added regularly. The following extensions are fully supported:
- KHR_lights_punctual
- KHR_materials_anisotropy
- KHR_materials_clearcoat
- KHR_materials_diffuse_transmission
- KHR_materials_dispersion
- KHR_materials_emissive_strength
- KHR_materials_ior
- KHR_materials_iridescence
- KHR_materials_pbrSpecularGlossiness
- KHR_materials_sheen
- KHR_materials_specular
- KHR_materials_transmission
- KHR_materials_unlit
- KHR_materials_variants
- KHR_materials_volume
- KHR_mesh_quantization
- KHR_meshopt_compression
- KHR_texture_basisu
- KHR_texture_transform
- EXT_mesh_gpu_instancing
- EXT_meshopt_compression
- EXT_texture_webp
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_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`)
- EXT_meshopt_compression (used when requested via `-c` or `-cc`)
- EXT_mesh_gpu_instancing (used when requested via `-mi`)
- EXT_texture_webp (used when requested via `-tw`)
gltfpack does not support vendor-specific extensions or custom extensions, including ones defined in [Khronos glTF repository](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor). Unknown extension nodes are discarded from the output.
Some Khronos extensions, notably Draco compression and extensions for interactive scenes (interactivity, animation pointer, node selectability/hoverability/visibility), are not supported.
## Custom data
glTF files may contain custom application-specific data stored outside of custom extensions. gltfpack has limited support for preserving this data.
Unknown vertex or instance attributes are generally discarded. While it would be possible to preserve them in theory, many scene transformations and analyses require a full understanding of the attribute semantics - some attributes need to be transformed with the mesh transformation matrix, some need to be adjusted when meshes are merged, some influence how meshes are rendered, etc. However, gltfpack supports some specific attributes that are commonly used in the ecosystem:
- Vertex attributes that store integer IDs with names `_ID`, `_BATCHID` or `_FEATURE_ID_n` are preserved as-is. These can be used to identify objects.
- Instance attribute `_COLOR_0` is preserved as-is. This can be used to provide per-instance color tinting.
Additional application-specific data can be stored via the glTF `extras` property. While extras (outside of `asset`) are discarded by default, using the `-ke` option preserves extras in materials, nodes, primitives and scenes.
In addition to `asset` extras, gltfpack unconditionally preserves morph target names specified via the `targetNames` property inside `extras` on meshes.
## Building
gltfpack can be built from source using CMake or Make. To build a full version of gltfpack that supports texture compression, CMake configuration needs to specify the path to https://github.com/zeux/basis_universal fork (branch gltfpack) via `MESHOPT_GLTFPACK_BASISU_PATH` variable, as well as libwebp path via `MESHOPT_GLTFPACK_LIBWEBP_PATH` variable:
```
git clone -b gltfpack https://github.com/zeux/basis_universal
git clone https://github.com/webmproject/libwebp
cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -DMESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp -DCMAKE_BUILD_TYPE=Release
cmake --build . --target gltfpack --config Release
```
## License
gltfpack is available to anybody free of charge, under the terms of MIT License (see LICENSE.md).
+356
View File
@@ -0,0 +1,356 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <algorithm>
#include <float.h>
#include <math.h>
#include <string.h>
static float getScaleDelta(float l, float r)
{
float d = fabsf(l - r);
float m = std::max(fabsf(l), fabsf(r));
return m == 0.f ? 0.f : d / m;
}
static float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type)
{
switch (type)
{
case cgltf_animation_path_type_translation:
return std::max(std::max(fabsf(l.f[0] - r.f[0]), fabsf(l.f[1] - r.f[1])), fabsf(l.f[2] - r.f[2]));
case cgltf_animation_path_type_rotation:
return 2 * acosf(std::min(1.f, fabsf(l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3])));
case cgltf_animation_path_type_scale:
return std::max(std::max(getScaleDelta(l.f[0], r.f[0]), getScaleDelta(l.f[1], r.f[1])), getScaleDelta(l.f[2], r.f[2]));
case cgltf_animation_path_type_weights:
return fabsf(l.f[0] - r.f[0]);
default:
assert(!"Unknown animation path");
return 0;
}
}
static float getDeltaTolerance(cgltf_animation_path_type type)
{
switch (type)
{
case cgltf_animation_path_type_translation:
return 0.0001f; // 0.1mm linear
case cgltf_animation_path_type_rotation:
return 0.1f * (3.1415926f / 180.f); // 0.1 degrees
case cgltf_animation_path_type_scale:
return 0.001f; // 0.1% ratio
case cgltf_animation_path_type_weights:
return 0.001f; // 0.1% linear
default:
assert(!"Unknown animation path");
return 0;
}
}
static Attr interpolateLinear(const Attr& l, const Attr& r, float t, cgltf_animation_path_type type)
{
if (type == cgltf_animation_path_type_rotation)
{
// Approximating slerp, https://zeux.io/2015/07/23/approximating-slerp/
// We also handle quaternion double-cover
float ca = l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3];
float d = fabsf(ca);
float A = 1.0904f + d * (-3.2452f + d * (3.55645f - d * 1.43519f));
float B = 0.848013f + d * (-1.06021f + d * 0.215638f);
float k = A * (t - 0.5f) * (t - 0.5f) + B;
float ot = t + t * (t - 0.5f) * (t - 1) * k;
float t0 = 1 - ot;
float t1 = ca > 0 ? ot : -ot;
Attr lerp = {{
l.f[0] * t0 + r.f[0] * t1,
l.f[1] * t0 + r.f[1] * t1,
l.f[2] * t0 + r.f[2] * t1,
l.f[3] * t0 + r.f[3] * t1,
}};
float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]);
if (len > 0.f)
{
lerp.f[0] /= len;
lerp.f[1] /= len;
lerp.f[2] /= len;
lerp.f[3] /= len;
}
return lerp;
}
else
{
Attr lerp = {{
l.f[0] * (1 - t) + r.f[0] * t,
l.f[1] * (1 - t) + r.f[1] * t,
l.f[2] * (1 - t) + r.f[2] * t,
l.f[3] * (1 - t) + r.f[3] * t,
}};
return lerp;
}
}
static Attr interpolateHermite(const Attr& v0, const Attr& t0, const Attr& v1, const Attr& t1, float t, float dt, cgltf_animation_path_type type)
{
float s0 = 1 + t * t * (2 * t - 3);
float s1 = t + t * t * (t - 2);
float s2 = 1 - s0;
float s3 = t * t * (t - 1);
float ts1 = dt * s1;
float ts3 = dt * s3;
Attr lerp = {{
s0 * v0.f[0] + ts1 * t0.f[0] + s2 * v1.f[0] + ts3 * t1.f[0],
s0 * v0.f[1] + ts1 * t0.f[1] + s2 * v1.f[1] + ts3 * t1.f[1],
s0 * v0.f[2] + ts1 * t0.f[2] + s2 * v1.f[2] + ts3 * t1.f[2],
s0 * v0.f[3] + ts1 * t0.f[3] + s2 * v1.f[3] + ts3 * t1.f[3],
}};
if (type == cgltf_animation_path_type_rotation)
{
float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]);
if (len > 0.f)
{
lerp.f[0] /= len;
lerp.f[1] /= len;
lerp.f[2] /= len;
lerp.f[3] /= len;
}
}
return lerp;
}
static void resampleKeyframes(std::vector<Attr>& data, const std::vector<float>& input, const std::vector<Attr>& output, cgltf_animation_path_type type, cgltf_interpolation_type interpolation, size_t components, int frames, float mint, int freq)
{
size_t cursor = 0;
for (int i = 0; i < frames; ++i)
{
float time = mint + float(i) / freq;
while (cursor + 1 < input.size())
{
float next_time = input[cursor + 1];
if (next_time > time)
break;
cursor++;
}
if (cursor + 1 < input.size())
{
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);
float t = std::max(0.f, std::min(1.f, (time - cursor_time) * inv_range));
for (size_t j = 0; j < components; ++j)
{
switch (interpolation)
{
case cgltf_interpolation_type_linear:
{
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:
{
const Attr& v = output[cursor * components + j];
data.push_back(v);
}
break;
case cgltf_interpolation_type_cubic_spline:
{
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;
default:
assert(!"Unknown interpolation type");
}
}
}
else
{
size_t offset = (interpolation == cgltf_interpolation_type_cubic_spline) ? cursor * 3 + 1 : cursor;
for (size_t j = 0; j < components; ++j)
{
const Attr& v = output[offset * components + j];
data.push_back(v);
}
}
}
}
static float getMaxDelta(const std::vector<Attr>& data, cgltf_animation_path_type type, const Attr* value, size_t components)
{
assert(data.size() % components == 0);
float result = 0;
for (size_t i = 0; i < data.size(); i += components)
{
for (size_t j = 0; j < components; ++j)
{
float delta = getDelta(value[j], data[i + j], type);
result = (result < delta) ? delta : result;
}
}
return result;
}
static void getBaseTransform(Attr* result, size_t components, cgltf_animation_path_type type, cgltf_node* node)
{
switch (type)
{
case cgltf_animation_path_type_translation:
memcpy(result->f, node->translation, 3 * sizeof(float));
break;
case cgltf_animation_path_type_rotation:
memcpy(result->f, node->rotation, 4 * sizeof(float));
break;
case cgltf_animation_path_type_scale:
memcpy(result->f, node->scale, 3 * sizeof(float));
break;
case cgltf_animation_path_type_weights:
if (node->weights_count)
{
assert(node->weights_count == components);
for (size_t k = 0; k < components; ++k)
result[k].f[0] = node->weights[k];
}
else if (node->mesh && node->mesh->weights_count)
{
assert(node->mesh->weights_count == components);
for (size_t k = 0; k < components; ++k)
result[k].f[0] = node->mesh->weights[k];
}
break;
default:
assert(!"Unknown animation path");
}
}
static float getWorldScale(cgltf_node* node)
{
float transform[16];
cgltf_node_transform_world(node, transform);
// 3x3 determinant computes scale^3
float a0 = transform[5] * transform[10] - transform[6] * transform[9];
float a1 = transform[4] * transform[10] - transform[6] * transform[8];
float a2 = transform[4] * transform[9] - transform[5] * transform[8];
float det = transform[0] * a0 - transform[1] * a1 + transform[2] * a2;
return powf(fabsf(det), 1.f / 3.f);
}
void processAnimation(Animation& animation, const Settings& settings)
{
float mint = FLT_MAX, maxt = 0;
for (size_t i = 0; i < animation.tracks.size(); ++i)
{
const Track& track = animation.tracks[i];
assert(!track.time.empty());
mint = std::min(mint, track.time.front());
maxt = std::max(maxt, track.time.back());
}
animation.start = mint = std::min(mint, maxt);
if (settings.anim_freq)
{
// round the number of frames to nearest but favor the "up" direction
// this means that at 100 Hz resampling, we will try to preserve the last frame <10ms
// but if the last frame is <2ms we favor just removing this data
int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f);
animation.frames = frames;
}
std::vector<Attr> base;
for (size_t i = 0; i < animation.tracks.size(); ++i)
{
Track& track = animation.tracks[i];
if (settings.anim_freq)
{
std::vector<Attr> result;
resampleKeyframes(result, track.time, track.data, track.path, track.interpolation, track.components, animation.frames, animation.start, settings.anim_freq);
track.time.clear();
track.data.swap(result);
track.interpolation = track.interpolation == cgltf_interpolation_type_cubic_spline ? cgltf_interpolation_type_linear : track.interpolation;
}
// getMaxDelta assumes linear/step interpolation for now
if (track.interpolation == cgltf_interpolation_type_cubic_spline)
continue;
float tolerance = getDeltaTolerance(track.path);
// translation tracks use world space tolerance; in the future, we should compute all errors as linear using hierarchy
if (track.node && track.node->parent && track.path == cgltf_animation_path_type_translation)
{
float scale = getWorldScale(track.node->parent);
tolerance /= scale == 0.f ? 1.f : scale;
}
float deviation = getMaxDelta(track.data, track.path, &track.data[0], track.components);
if (deviation <= tolerance)
{
// track is constant (equal to first keyframe), we only need the first keyframe
track.constant = true;
track.time.clear();
track.data.resize(track.components);
// track.dummy is true iff track redundantly sets up the value to be equal to default node transform
base.resize(track.components);
getBaseTransform(&base[0], track.components, track.path, track.node);
track.dummy = getMaxDelta(track.data, track.path, &base[0], track.components) <= tolerance;
}
}
}
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env node
// This file is part of gltfpack and is distributed under the terms of MIT License.
import { pack } from './library.js';
import fs from 'fs';
var args = process.argv.slice(2);
var iface = {
read: function (path) {
return fs.readFileSync(path);
},
write: function (path, data) {
fs.writeFileSync(path, data);
},
};
pack(args, iface)
.then(function (log) {
process.stdout.write(log);
process.exit(0);
})
.catch(function (err) {
process.stderr.write(err.message);
process.exit(1);
});
+214
View File
@@ -0,0 +1,214 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#ifdef WITH_BASISU
#include "gltfpack.h"
#define BASISU_NO_ITERATOR_DEBUG_LEVEL
#ifdef __clang__
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
#pragma GCC diagnostic ignored "-Warray-bounds"
#pragma GCC diagnostic ignored "-Wc++17-extensions"
#pragma GCC diagnostic ignored "-Wdeprecated-builtins"
#endif
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wclass-memaccess"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-value"
#endif
#if defined(__GNUC__) && __GNUC__ >= 12
#pragma GCC diagnostic ignored "-Wc++17-extensions"
#endif
#include "encoder/basisu_comp.h"
struct BasisSettings
{
int etc1s_l;
int etc1s_q;
int uastc_l;
float uastc_q;
};
static const BasisSettings kBasisSettings[10] = {
{1, 1, 0, 4.f},
{1, 32, 0, 3.f},
{1, 64, 1, 2.f},
{1, 96, 1, 1.5f},
{1, 128, 1, 1.f}, // quality arguments aligned with basisu defaults
{1, 150, 1, 0.8f},
{1, 170, 1, 0.6f},
{1, 192, 1, 0.4f}, // gltfpack defaults
{1, 224, 2, 0.2f},
{1, 255, 2, 0.f},
};
static void fillParams(basisu::basis_compressor_params& params, const char* input, const char* output, bool uastc, int width, int height, const BasisSettings& bs, const ImageInfo& info, const Settings& settings)
{
if (uastc)
{
static const uint32_t s_level_flags[basisu::TOTAL_PACK_UASTC_LEVELS] = {basisu::cPackUASTCLevelFastest, basisu::cPackUASTCLevelFaster, basisu::cPackUASTCLevelDefault, basisu::cPackUASTCLevelSlower, basisu::cPackUASTCLevelVerySlow};
params.m_uastc = true;
#if BASISU_LIB_VERSION >= 160
params.m_pack_uastc_ldr_4x4_flags &= ~basisu::cPackUASTCLevelMask;
params.m_pack_uastc_ldr_4x4_flags |= s_level_flags[bs.uastc_l];
params.m_rdo_uastc_ldr_4x4 = bs.uastc_q > 0;
params.m_rdo_uastc_ldr_4x4_quality_scalar = bs.uastc_q;
params.m_rdo_uastc_ldr_4x4_dict_size = 1024;
#else
params.m_pack_uastc_flags &= ~basisu::cPackUASTCLevelMask;
params.m_pack_uastc_flags |= s_level_flags[bs.uastc_l];
params.m_rdo_uastc = bs.uastc_q > 0;
params.m_rdo_uastc_quality_scalar = bs.uastc_q;
params.m_rdo_uastc_dict_size = 1024;
#endif
}
else
{
#if BASISU_LIB_VERSION >= 200
params.m_etc1s_compression_level = bs.etc1s_l;
params.m_quality_level = bs.etc1s_q;
params.m_etc1s_max_endpoint_clusters = 0;
params.m_etc1s_max_selector_clusters = 0;
#elif BASISU_LIB_VERSION >= 160
params.m_compression_level = bs.etc1s_l;
params.m_etc1s_quality_level = bs.etc1s_q;
params.m_etc1s_max_endpoint_clusters = 0;
params.m_etc1s_max_selector_clusters = 0;
#else
params.m_compression_level = bs.etc1s_l;
params.m_quality_level = bs.etc1s_q;
params.m_max_endpoint_clusters = 0;
params.m_max_selector_clusters = 0;
#endif
params.m_no_selector_rdo = info.normal_map;
params.m_no_endpoint_rdo = info.normal_map;
}
params.m_perceptual = info.srgb;
params.m_mip_gen = true;
params.m_mip_srgb = info.srgb;
params.m_resample_width = width;
params.m_resample_height = height;
params.m_y_flip = settings.texture_flipy;
params.m_create_ktx2_file = true;
#if BASISU_LIB_VERSION >= 200
params.m_ktx2_and_basis_srgb_transfer_function = info.srgb;
#else
params.m_ktx2_srgb_transfer_func = info.srgb;
#endif
if (uastc)
{
params.m_ktx2_uastc_supercompression = basist::KTX2_SS_ZSTANDARD;
params.m_ktx2_zstd_supercompression_level = 9;
}
params.m_read_source_images = true;
#if BASISU_LIB_VERSION >= 150
params.m_write_output_basis_or_ktx2_files = true;
#else
params.m_write_output_basis_files = true;
#endif
params.m_source_filenames.resize(1);
params.m_source_filenames[0] = input;
params.m_out_filename = output;
params.m_status_output = false;
}
static const char* prepareEncode(basisu::basis_compressor_params& params, const cgltf_image& image, const char* input_path, const ImageInfo& info, const Settings& settings, const std::string& temp_prefix, std::string& temp_input, std::string& temp_output)
{
std::string img_data;
std::string mime_type;
if (!readImage(image, input_path, img_data, mime_type))
return "error reading source file";
if (mime_type != "image/png" && mime_type != "image/jpeg")
return NULL;
int width = 0, height = 0;
if (!getDimensions(img_data, mime_type.c_str(), width, height))
return "error parsing image header";
adjustDimensions(width, height, settings.texture_scale[info.kind], settings.texture_limit[info.kind], settings.texture_pow2);
temp_input = temp_prefix + mimeExtension(mime_type.c_str());
temp_output = temp_prefix + ".ktx2";
if (!writeFile(temp_input.c_str(), img_data))
return "error writing temporary file";
int quality = settings.texture_quality[info.kind];
bool uastc = settings.texture_mode[info.kind] == TextureMode_UASTC;
const BasisSettings& bs = kBasisSettings[quality - 1];
fillParams(params, temp_input.c_str(), temp_output.c_str(), uastc, width, height, bs, info, settings);
return NULL;
}
void encodeImagesBasis(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings)
{
basisu::basisu_encoder_init();
basisu::vector<basisu::basis_compressor_params> params(data->images_count);
basisu::vector<basisu::parallel_results> results(data->images_count);
std::string temp_prefix = getTempPrefix();
std::vector<std::string> temp_inputs(data->images_count);
std::vector<std::string> temp_outputs(data->images_count);
for (size_t i = 0; i < data->images_count; ++i)
{
const cgltf_image& image = data->images[i];
ImageInfo info = images[i];
if (settings.texture_mode[info.kind] == TextureMode_ETC1S || settings.texture_mode[info.kind] == TextureMode_UASTC)
if (const char* error = prepareEncode(params[i], image, input_path, info, settings, temp_prefix + "-" + std::to_string(i), temp_inputs[i], temp_outputs[i]))
encoded[i] = error;
}
uint32_t num_threads = settings.texture_jobs == 0 ? std::thread::hardware_concurrency() : settings.texture_jobs;
basisu::basis_parallel_compress(num_threads, params, results);
for (size_t i = 0; i < data->images_count; ++i)
{
if (params[i].m_source_filenames.empty())
; // encoding was skipped or preparation resulted in an error
else if (results[i].m_error_code == basisu::basis_compressor::cECFailedReadingSourceImages)
encoded[i] = "error decoding source image";
else if (results[i].m_error_code != basisu::basis_compressor::cECSuccess)
encoded[i] = "error encoding image";
else if (!readFile(temp_outputs[i].c_str(), encoded[i]))
encoded[i] = "error reading temporary file";
}
for (size_t i = 0; i < data->images_count; ++i)
{
if (!temp_inputs[i].empty())
removeFile(temp_inputs[i].c_str());
if (!temp_outputs[i].empty())
removeFile(temp_outputs[i].c_str());
}
}
#endif
+169
View File
@@ -0,0 +1,169 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#ifdef WITH_LIBWEBP
#include "gltfpack.h"
#include "webp/decode.h"
#include "webp/encode.h"
#ifndef WITH_LIBWEBP_BASIS
#include "imageio/image_dec.h"
#endif
#include <atomic>
#include <memory>
#include <thread>
static int writeWebP(const uint8_t* data, size_t data_size, const WebPPicture* picture)
{
std::string* encoded = static_cast<std::string*>(picture->custom_ptr);
encoded->append(reinterpret_cast<const char*>(data), data_size);
return 1;
}
// when gltfpack is built with Basis Universal, we have easy access to its jpeg/png decoders
#ifdef WITH_LIBWEBP_BASIS
namespace pv_png
{
void* load_png(const void* pImage_buf, size_t buf_size, uint32_t desired_chans, uint32_t& width, uint32_t& height, uint32_t& num_chans);
} // namespace pv_png
namespace jpgd
{
static const uint32_t cFlagLinearChromaFiltering = 1;
unsigned char* decompress_jpeg_image_from_memory(const unsigned char* pSrc_data, int src_data_size, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags = 0);
} // namespace jpgd
typedef int (*WebPImageReader)(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata);
static int readPngBasis(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata)
{
(void)keep_alpha;
(void)metadata;
uint32_t width = 0, height = 0, channels = 0;
void* img = pv_png::load_png(data, data_size, 4, width, height, channels);
if (!img)
return 0;
pic->width = width;
pic->height = height;
int ok = WebPPictureImportRGBA(pic, static_cast<uint8_t*>(img), width * 4);
free(img);
return ok;
}
static int readJpegBasis(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata)
{
(void)keep_alpha;
(void)metadata;
int width = 0, height = 0, channels = 0;
unsigned char* img = jpgd::decompress_jpeg_image_from_memory(data, int(data_size), &width, &height, &channels, 4, jpgd::cFlagLinearChromaFiltering);
if (!img)
return 0;
pic->width = width;
pic->height = height;
int ok = WebPPictureImportRGBA(pic, img, width * 4);
free(img);
return ok;
}
#endif
static const char* encodeWebP(const cgltf_image& image, const char* input_path, const ImageInfo& info, const Settings& settings, std::string& encoded)
{
WebPConfig config;
if (!WebPConfigInit(&config))
return "error initializing WebP";
std::string img_data;
std::string mime_type;
if (!readImage(image, input_path, img_data, mime_type))
return "error reading source file";
if (mime_type != "image/png" && mime_type != "image/jpeg")
return NULL;
int width = 0, height = 0;
if (!getDimensions(img_data, mime_type.c_str(), width, height))
return "error parsing image header";
adjustDimensions(width, height, settings.texture_scale[info.kind], settings.texture_limit[info.kind], settings.texture_pow2);
#ifdef WITH_LIBWEBP_BASIS
WebPImageReader reader = (mime_type == "image/jpeg") ? readJpegBasis : readPngBasis;
#else
WebPInputFileFormat format = (mime_type == "image/jpeg") ? WEBP_JPEG_FORMAT : WEBP_PNG_FORMAT;
WebPImageReader reader = WebPGetImageReader(format);
if (!reader)
return "unsupported image format";
#endif
WebPPicture pic;
if (!WebPPictureInit(&pic))
return "error initializing picture";
std::unique_ptr<WebPPicture, void (*)(WebPPicture*)> pic_storage(&pic, WebPPictureFree);
if (!reader(reinterpret_cast<const uint8_t*>(img_data.data()), img_data.size(), &pic, 1, NULL))
return "error decoding source image";
if ((width != pic.width || height != pic.height) && !WebPPictureRescale(&pic, width, height))
return "error resizing image";
int quality = settings.texture_quality[info.kind];
if (info.normal_map)
config.quality = float(50 + quality * 5); // map 1-10 to 55-100
else
config.quality = float(20 + quality * 8); // map 1-10 to 28-100
config.emulate_jpeg_size = 1; // for flatter quality curve
pic.writer = writeWebP;
pic.custom_ptr = &encoded;
encoded.clear();
if (!WebPEncode(&config, &pic))
return "error encoding image";
return NULL;
}
void encodeImagesWebP(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings)
{
std::atomic<size_t> next_image{0};
auto encode = [&]()
{
for (;;)
{
size_t i = next_image++;
if (i >= data->images_count)
break;
const cgltf_image& image = data->images[i];
ImageInfo info = images[i];
if (settings.texture_mode[info.kind] == TextureMode_WebP)
if (const char* error = encodeWebP(image, input_path, info, settings, encoded[i]))
encoded[i] = error;
}
};
// we use main thread as a worker as well
size_t worker_count = settings.texture_jobs == 0 ? std::thread::hardware_concurrency() : settings.texture_jobs;
size_t thread_count = worker_count > 0 ? worker_count - 1 : 0;
std::vector<std::thread> threads;
for (size_t i = 0; i < thread_count; ++i)
threads.emplace_back(encode);
encode();
for (size_t i = 0; i < thread_count; ++i)
threads[i].join();
}
#endif
+115
View File
@@ -0,0 +1,115 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <process.h>
#else
#include <unistd.h>
#endif
std::string getTempPrefix()
{
#if defined(_WIN32)
const char* temp_dir = getenv("TEMP");
std::string path = temp_dir ? temp_dir : ".";
path += "\\gltfpack-temp";
path += std::to_string(_getpid());
return path;
#elif defined(__wasi__)
return "gltfpack-temp";
#else
std::string path = "/tmp/gltfpack-temp";
path += std::to_string(getpid());
return path;
#endif
}
std::string getFullPath(const char* path, const char* base_path)
{
std::string result = base_path;
std::string::size_type slash = result.find_last_of("/\\");
result.erase(slash == std::string::npos ? 0 : slash + 1);
result += path;
return result;
}
std::string getFileName(const char* path)
{
std::string result = path;
std::string::size_type slash = result.find_last_of("/\\");
if (slash != std::string::npos)
result.erase(0, slash + 1);
std::string::size_type dot = result.find_last_of('.');
if (dot != std::string::npos)
result.erase(dot);
return result;
}
std::string getExtension(const char* path)
{
std::string result = path;
std::string::size_type slash = result.find_last_of("/\\");
std::string::size_type dot = result.find_last_of('.');
if (slash != std::string::npos && dot != std::string::npos && dot < slash)
dot = std::string::npos;
result.erase(0, dot);
for (size_t i = 0; i < result.length(); ++i)
if (unsigned(result[i] - 'A') < 26)
result[i] = (result[i] - 'A') + 'a';
return result;
}
bool readFile(const char* path, std::string& data)
{
FILE* file = fopen(path, "rb");
if (!file)
return false;
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
if (length <= 0)
{
fclose(file);
return false;
}
data.resize(length);
size_t result = fread(&data[0], 1, data.size(), file);
int rc = fclose(file);
return rc == 0 && result == data.size();
}
bool writeFile(const char* path, const std::string& data)
{
FILE* file = fopen(path, "wb");
if (!file)
return false;
size_t result = fwrite(&data[0], 1, data.size(), file);
int rc = fclose(file);
return rc == 0 && result == data.size();
}
void removeFile(const char* path)
{
remove(path);
}
+303
View File
@@ -0,0 +1,303 @@
#
# AFL dictionary for JSON
# -----------------------
#
# Just the very basics.
#
# Inspired by a dictionary by Jakub Wilk <jwilk@jwilk.net>
#
"0"
",0"
":0"
"0:"
"-1.2e+3"
"true"
"false"
"null"
"\"\""
",\"\""
":\"\""
"\"\":"
"{}"
",{}"
":{}"
"{\"\":0}"
"{{}}"
"[]"
",[]"
":[]"
"[0]"
"[[]]"
"''"
"\\"
"\\b"
"\\f"
"\\n"
"\\r"
"\\t"
"\\u0000"
"\\x00"
"\\0"
"\\uD800\\uDC00"
"\\uDBFF\\uDFFF"
"\"\":0"
"//"
"/**/"
#
# AFL dictionary for GLTF core
# -----------------------
"5120"
"5121"
"5122"
"5123"
"5125"
"5126"
"\"BLEND\""
"\"CUBICSPLINE\""
"\"LINEAR\""
"\"MASK\""
"\"MAT2\""
"\"MAT3\""
"\"MAT4\""
"\"OPAQUE\""
"\"POSITION\""
"\"NORMAL\""
"\"TANGENT\""
"\"TEXCOORD\""
"\"JOINTS\""
"\"WEIGHTS\""
"\"SCALAR\""
"\"STEP\""
"\"VEC2\""
"\"VEC3\""
"\"VEC4\""
"\"accessor\""
"\"accessors\""
"\"alphaCutoff\""
"\"alphaMode\""
"\"animations\""
"\"aspectRatio\""
"\"asset\""
"\"attributes\""
"\"baseColorFactor\""
"\"baseColorTexture\""
"\"bufferView\""
"\"bufferViews\""
"\"buffer\""
"\"buffers\""
"\"byteLength\""
"\"byteOffset\""
"\"byteStride\""
"\"camera\""
"\"cameras\""
"\"channel\""
"\"channels\""
"\"children\""
"\"componentType\""
"\"copyright\""
"\"count\""
"\"doubleSided\""
"\"emissiveFactor\""
"\"emissiveTexture\""
"\"extensionsRequired\""
"\"extensionsUsed\""
"\"extensions\""
"\"extras\""
"\"generator\""
"\"image\""
"\"images\""
"\"index\""
"\"indices\""
"\"input\""
"\"interpolation\""
"\"inverseBindMatrices\""
"\"joints\""
"\"magFilter\""
"\"material\""
"\"materials\""
"\"matrix\""
"\"max\""
"\"mesh\""
"\"meshes\""
"\"metallicFactor\""
"\"metallicRoughnessTexture\""
"\"mimeType\""
"\"minFilter\""
"\"minVersion\""
"\"min\""
"\"mode\""
"\"name\""
"\"node\""
"\"nodes\""
"\"normalTextureInfo\""
"\"normalTexture\""
"\"normalized\""
"\"occlusionTextureInfo\""
"\"occlusionTexture\""
"\"orthographic\""
"\"output\""
"\"path\""
"\"pbrMetallicRoughness\""
"\"perspective\""
"\"primitive\""
"\"primitives\""
"\"rotation\""
"\"roughnessFactor\""
"\"sampler\""
"\"samplers\""
"\"scale\""
"\"scene\""
"\"scenes\""
"\"skeleton\""
"\"skin\""
"\"skins\""
"\"source\""
"\"sparse\""
"\"strength\""
"\"target\""
"\"targets\""
"\"texCoord\""
"\"textureInfo\""
"\"texture\""
"\"textures\""
"\"translation\""
"\"type\""
"\"uri\""
"\"values\""
"\"version\""
"\"weights\""
"\"wrapS\""
"\"wrapT\""
"\"xmag\""
"\"yfov\""
"\"ymag\""
"\"zfar\""
"\"znear\""
#
# AFL dictionary for GLTF extensions
# -----------------------
"\"KHR_materials_unlit\""
"\"KHR_draco_mesh_compression\""
"\"KHR_texture_basisu\""
"\"KHR_materials_pbrSpecularGlossiness\""
"\"diffuseFactor\""
"\"diffuseTexture\""
"\"specularFactor\""
"\"glossinessFactor\""
"\"specularGlossinessTexture\""
"\"KHR_texture_transform\""
"\"offset\""
"\"rotation\""
"\"scale\""
"\"texCoord\""
"\"KHR_lights_punctual\""
"\"color\""
"\"directional\""
"\"intensity\""
"\"light\""
"\"lights\""
"\"point\""
"\"spot\""
"\"type\""
"\"range\""
"\"innerConeAngle\""
"\"outerConeAngle\""
"\"KHR_materials_transmission\""
"\"transmissionFactor\""
"\"transmissionTexture\""
"\"KHR_materials_volume\""
"\"thicknessFactor\""
"\"thicknessTexture\""
"\"attenuationColor\""
"\"attenuationDistance\""
"\"KHR_materials_sheen\""
"\"sheenColorFactor\""
"\"sheenColorTexture\""
"\"sheenRoughnessFactor\""
"\"sheenRoughnessTexture\""
"\"KHR_materials_emissive_strength\""
"\"emissiveStrength\""
"\"KHR_materials_clearcoat\""
"\"clearcoatFactor\""
"\"clearcoatTexture\""
"\"clearcoatRoughnessFactor\""
"\"clearcoatRoughnessTexture\""
"\"clearcoatNormalTexture\""
"\"KHR_materials_ior\""
"\"ior\""
"\"KHR_materials_specular\""
"\"specularFactor\""
"\"specularTexture\""
"\"specularColorFactor\""
"\"specularColorTexture\""
"\"KHR_materials_iridescence\""
"\"iridescenceFactor\""
"\"iridescenceTexture\""
"\"iridescenceIor\""
"\"iridescenceThicknessMinimum\""
"\"iridescenceThicknessMaximum\""
"\"iridescenceThicknessTexture\""
"\"KHR_materials_anisotropy\""
"\"anisotropyStrength\""
"\"anisotropyRotation\""
"\"anisotropyTexture\""
"\"KHR_materials_dispersion\""
"\"dispersion\""
"\"KHR_materials_diffuse_transmission\""
"\"diffuseTransmissionFactor\""
"\"diffuseTransmissionTexture\""
"\"diffuseTransmissionColorFactor\""
"\"diffuseTransmissionColorTexture\""
"\"KHR_materials_variants\""
"\"mappings\""
"\"targetNames\""
"\"variants\""
"\"EXT_texture_webp\""
"\"EXT_mesh_gpu_instancing\""
"\"TRANSLATION\""
"\"ROTATION\""
"\"SCALE\""
"\"_COLOR_0\""
"\"EXT_meshopt_compression\""
"\"KHR_meshopt_compression\""
"\"mode\""
"\"filter\""
"\"fallback\""
"\"ATTRIBUTES\""
"\"TRIANGLES\""
"\"INDICES\""
"\"NONE\""
"\"OCTAHEDRAL\""
"\"QUATERNION\""
"\"EXPONENTIAL\""
"\"COLOR\""
"\"KHR_mesh_quantization\""
Binary file not shown.
File diff suppressed because it is too large Load Diff
+449
View File
@@ -0,0 +1,449 @@
/**
* gltfpack - version 1.2
*
* Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://github.com/zeux/meshoptimizer
*
* This application is distributed under the MIT License. See notice at the end of this file.
*/
#pragma once
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#ifndef _CRT_NONSTDC_NO_WARNINGS
#define _CRT_NONSTDC_NO_WARNINGS
#endif
#include "../extern/cgltf.h"
#include <assert.h>
#include <string>
#include <vector>
struct Attr
{
float f[4];
};
struct Stream
{
cgltf_attribute_type type;
int index;
int target; // 0 = base mesh, 1+ = morph target
const char* custom_name; // only valid for cgltf_attribute_type_custom
std::vector<Attr> data;
};
struct Instance
{
float transform[16];
float color[4];
};
struct Mesh
{
int scene;
std::vector<cgltf_node*> nodes;
std::vector<Instance> instances;
cgltf_material* material;
cgltf_skin* skin;
cgltf_extras extras;
cgltf_primitive_type type;
std::vector<Stream> streams;
std::vector<unsigned int> indices;
bool geometry_duplicate;
uint64_t geometry_hash[2];
size_t targets;
std::vector<float> target_weights;
std::vector<const char*> target_names;
std::vector<cgltf_material_mapping> variants;
float quality;
};
struct Track
{
cgltf_node* node;
cgltf_animation_path_type path;
bool constant;
bool dummy;
size_t components; // 1 unless path is cgltf_animation_path_type_weights
cgltf_interpolation_type interpolation;
std::vector<float> time; // empty for resampled or constant animations
std::vector<Attr> data;
};
struct Animation
{
const char* name;
float start;
int frames;
std::vector<Track> tracks;
};
enum TextureKind
{
TextureKind_Generic,
TextureKind_Color,
TextureKind_Normal,
TextureKind_Attrib,
TextureKind__Count
};
enum TextureMode
{
TextureMode_Raw,
TextureMode_ETC1S,
TextureMode_UASTC,
TextureMode_WebP,
};
struct Settings
{
int pos_bits;
int tex_bits;
int nrm_bits;
int col_bits;
bool pos_normalized;
bool pos_float;
bool tex_float;
bool nrm_float;
int trn_bits;
int rot_bits;
int scl_bits;
int anim_freq;
bool anim_const;
bool keep_nodes;
bool keep_materials;
bool keep_extras;
bool keep_attributes;
bool mesh_dedup;
bool mesh_merge;
bool mesh_instancing;
bool mesh_interleaved;
bool mesh_tangents;
bool mesh_normals;
float normals_crease;
float simplify_ratio;
float simplify_error;
bool simplify_aggressive;
bool simplify_lock_borders;
bool simplify_permissive;
bool simplify_update;
bool texture_ktx2;
bool texture_webp;
bool texture_embed;
bool texture_ref;
bool texture_pow2;
bool texture_flipy;
float texture_scale[TextureKind__Count];
int texture_limit[TextureKind__Count];
TextureMode texture_mode[TextureKind__Count];
int texture_quality[TextureKind__Count];
int texture_jobs;
bool quantize;
bool compress;
bool compressmore;
bool compresskhr;
bool fallback;
int verbose;
};
struct QuantizationPosition
{
float offset[3];
float scale;
int bits;
bool normalized;
float node_scale; // computed from scale/bits/normalized
};
struct QuantizationTexture
{
float offset[2];
float scale[2];
int bits;
bool normalized;
};
struct StreamFormat
{
enum Filter
{
Filter_None,
Filter_Oct,
Filter_Quat,
Filter_Exp,
Filter_Color,
};
cgltf_type type;
cgltf_component_type component_type;
bool normalized;
size_t stride;
Filter filter;
};
struct NodeInfo
{
int scene;
bool keep;
bool animated;
unsigned int animated_path_mask;
int remap;
std::vector<size_t> mesh_nodes;
bool has_mesh;
size_t mesh_index;
cgltf_skin* mesh_skin;
};
struct MaterialInfo
{
bool keep;
bool uses_texture_transform;
bool needs_tangents;
bool unlit;
bool mesh_alpha;
unsigned int texture_set_mask;
int remap;
};
struct TextureInfo
{
bool keep;
int remap;
};
struct ImageInfo
{
TextureKind kind;
bool normal_map;
bool srgb;
int channels;
};
struct ExtensionInfo
{
const char* name;
bool used;
bool required;
};
struct BufferView
{
enum Kind
{
Kind_Vertex,
Kind_Index,
Kind_Skin,
Kind_Time,
Kind_Keyframe,
Kind_Instance,
Kind_Image,
Kind_Count
};
enum Compression
{
Compression_None = -1,
Compression_Attribute,
Compression_Index,
Compression_IndexSequence,
};
Kind kind;
StreamFormat::Filter filter;
Compression compression;
size_t stride;
int variant;
std::string data;
size_t bytes;
};
std::string getTempPrefix();
std::string getFullPath(const char* path, const char* base_path);
std::string getFileName(const char* path);
std::string getExtension(const char* path);
bool readFile(const char* path, std::string& data);
bool writeFile(const char* path, const std::string& data);
void removeFile(const char* path);
cgltf_data* parseObj(const char* path, std::vector<Mesh>& meshes, const char** error);
cgltf_data* parseGltf(const char* path, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error);
cgltf_data* parseGlb(const void* buffer, size_t size, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error);
bool areExtrasEqual(const cgltf_extras& lhs, const cgltf_extras& rhs);
void processAnimation(Animation& animation, const Settings& settings);
void processMesh(Mesh& mesh, const Settings& settings);
bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs);
bool compareMeshVariants(const Mesh& lhs, const Mesh& rhs);
bool compareMeshNodes(const Mesh& lhs, const Mesh& rhs);
void hashMesh(Mesh& mesh);
void dedupMeshes(std::vector<Mesh>& meshes, const Settings& settings);
void mergeMeshInstances(Mesh& mesh);
void mergeMeshes(std::vector<Mesh>& meshes, const Settings& settings);
void filterEmptyMeshes(std::vector<Mesh>& meshes);
void filterStreams(Mesh& mesh, const MaterialInfo& mi);
void generateTangents(Mesh& mesh);
void generateNormals(Mesh& mesh, float crease_angle);
void mergeMeshMaterials(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings);
void markNeededMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, const std::vector<Mesh>& meshes, const Settings& settings);
void mergeTextures(cgltf_data* data, std::vector<TextureInfo>& textures);
bool isValidTransform(const cgltf_texture_transform& transform);
void analyzeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images);
void optimizeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<ImageInfo>& images, const char* input_path);
bool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type);
bool hasAlpha(const std::string& data, const char* mime_type);
bool getDimensions(const std::string& data, const char* mime_type, int& width, int& height);
void adjustDimensions(int& width, int& height, float scale, int limit, bool pow2);
const char* mimeExtension(const char* mime_type);
#ifdef WITH_BASISU
void encodeImagesBasis(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings);
#endif
#ifdef WITH_LIBWEBP
void encodeImagesWebP(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings);
#endif
void markScenes(cgltf_data* data, std::vector<NodeInfo>& nodes);
void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Animation>& animations);
void markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Mesh>& meshes, const std::vector<Animation>& animations, const Settings& settings);
void remapNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, size_t& node_offset);
void decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform);
void computeMeshQuality(std::vector<Mesh>& meshes);
bool hasVertexAlpha(const Mesh& mesh);
bool hasInstanceAlpha(const std::vector<Instance>& instances);
QuantizationPosition prepareQuantizationPosition(const std::vector<Mesh>& meshes, const Settings& settings);
void prepareQuantizationTexture(cgltf_data* data, std::vector<QuantizationTexture>& result, std::vector<size_t>& indices, const std::vector<Mesh>& meshes, const Settings& settings);
void getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition& qp, const Settings& settings);
StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings, bool filters = true);
StreamFormat writeIndexStream(std::string& bin, const std::vector<unsigned int>& stream);
StreamFormat writeTimeStream(std::string& bin, const std::vector<float>& data);
StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector<Attr>& data, const Settings& settings, bool has_tangents = false);
void compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride, int level);
void compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride);
void compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride);
size_t getBufferView(std::vector<BufferView>& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant = 0);
void comma(std::string& s);
void append(std::string& s, size_t v);
void append(std::string& s, float v);
void append(std::string& s, const char* v);
void append(std::string& s, const std::string& v);
void append(std::string& s, const float* data, size_t count);
void appendJson(std::string& s, const char* data);
const char* attributeType(cgltf_attribute_type type);
const char* animationPath(cgltf_animation_path_type type);
void writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt, std::vector<TextureInfo>& textures);
void writeBufferView(std::string& json, BufferView::Kind kind, StreamFormat::Filter filter, size_t count, size_t stride, size_t bin_offset, size_t bin_size, BufferView::Compression compression, size_t compressed_offset, size_t compressed_size, const char* meshopt_ext);
void writeSampler(std::string& json, const cgltf_sampler& sampler);
void writeImage(std::string& json, std::vector<BufferView>& views, const cgltf_image& image, const ImageInfo& info, const std::string* encoded, size_t index, const char* input_path, const char* output_path, const Settings& settings);
void writeTexture(std::string& json, const cgltf_texture& texture, const ImageInfo* info, cgltf_data* data, const Settings& settings);
void writeMeshAttributes(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings);
size_t writeMeshIndices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const std::vector<unsigned int>& indices, cgltf_primitive_type type, const Settings& settings);
void writeMeshGeometry(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings);
size_t writeJointBindMatrices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings);
size_t writeInstances(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const std::vector<Instance>& instances, const QuantizationPosition& qp, bool has_color, const Settings& settings);
void writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp);
void writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset, bool has_color);
void writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector<NodeInfo>& nodes, cgltf_data* data);
void writeNode(std::string& json, const cgltf_node& node, const std::vector<NodeInfo>& nodes, cgltf_data* data);
void writeAnimation(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector<NodeInfo>& nodes, const Settings& settings);
void writeCamera(std::string& json, const cgltf_camera& camera);
void writeLight(std::string& json, const cgltf_light& light);
void writeArray(std::string& json, const char* name, const std::string& contents);
void writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count);
void writeExtras(std::string& json, const cgltf_extras& extras);
void writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots, const Settings& settings);
/**
* Copyright (c) 2016-2026 Arseny Kapoulkine
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="..." version="6.0.0.0"/>
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>
+372
View File
@@ -0,0 +1,372 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <stdlib.h>
#include <string.h>
static const char* kMimeTypes[][2] = {
{"image/jpeg", ".jpg"},
{"image/jpeg", ".jpeg"},
{"image/png", ".png"},
{"image/ktx2", ".ktx2"},
{"image/webp", ".webp"},
};
static const char* inferMimeType(const char* path)
{
std::string ext = getExtension(path);
for (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i)
if (ext == kMimeTypes[i][1])
return kMimeTypes[i][0];
return "";
}
static bool parseDataUri(const char* uri, std::string& mime_type, std::string& result)
{
if (strncmp(uri, "data:", 5) == 0)
{
const char* comma = strchr(uri, ',');
if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0)
{
const char* base64 = comma + 1;
size_t base64_size = strlen(base64);
size_t size = base64_size - base64_size / 4;
if (base64_size >= 2)
{
size -= base64[base64_size - 2] == '=';
size -= base64[base64_size - 1] == '=';
}
void* data = NULL;
cgltf_options options = {};
cgltf_result res = cgltf_load_buffer_base64(&options, size, base64, &data);
if (res != cgltf_result_success)
return false;
mime_type = std::string(uri + 5, comma - 7);
result = std::string(static_cast<const char*>(data), size);
free(data);
return true;
}
}
return false;
}
static std::string fixupMimeType(const std::string& data, const std::string& mime_type)
{
if (mime_type == "image/jpeg" && data.compare(0, 8, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a") == 0)
return "image/png";
if (mime_type == "image/png" && data.compare(0, 3, "\xff\xd8\xff") == 0)
return "image/jpeg";
return mime_type;
}
bool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type)
{
if (image.uri && parseDataUri(image.uri, mime_type, data))
{
mime_type = fixupMimeType(data, mime_type);
return true;
}
else if (image.buffer_view && image.buffer_view->buffer->data && image.mime_type)
{
const cgltf_buffer_view* view = image.buffer_view;
data.assign(static_cast<const char*>(view->buffer->data) + view->offset, view->size);
mime_type = image.mime_type;
mime_type = fixupMimeType(data, image.mime_type);
return true;
}
else if (image.uri && *image.uri && input_path)
{
std::string path = image.uri;
cgltf_decode_uri(&path[0]);
path.resize(strlen(&path[0]));
if (!readFile(getFullPath(path.c_str(), input_path).c_str(), data))
return false;
mime_type = image.mime_type ? image.mime_type : inferMimeType(path.c_str());
mime_type = fixupMimeType(data, mime_type);
return true;
}
else
{
return false;
}
}
static int readInt16BE(const std::string& data, size_t offset)
{
return (unsigned char)data[offset] * 256 + (unsigned char)data[offset + 1];
}
static int readInt32BE(const std::string& data, size_t offset)
{
return (unsigned((unsigned char)data[offset]) << 24) |
(unsigned((unsigned char)data[offset + 1]) << 16) |
(unsigned((unsigned char)data[offset + 2]) << 8) |
unsigned((unsigned char)data[offset + 3]);
}
static int readInt32LE(const std::string& data, size_t offset)
{
return unsigned((unsigned char)data[offset]) |
(unsigned((unsigned char)data[offset + 1]) << 8) |
(unsigned((unsigned char)data[offset + 2]) << 16) |
(unsigned((unsigned char)data[offset + 3]) << 24);
}
// https://en.wikipedia.org/wiki/PNG#File_format
static bool getDimensionsPng(const std::string& data, int& width, int& height)
{
if (data.size() < 8 + 8 + 13 + 4)
return false;
const char* signature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
if (data.compare(0, 8, signature) != 0)
return false;
if (data.compare(12, 4, "IHDR") != 0)
return false;
width = readInt32BE(data, 16);
height = readInt32BE(data, 20);
return true;
}
// https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure
static bool getDimensionsJpeg(const std::string& data, int& width, int& height)
{
size_t offset = 0;
// note, this can stop parsing before reaching the end but we stop at SOF anyway
while (offset + 4 <= data.size())
{
if (data[offset] != '\xff')
return false;
char marker = data[offset + 1];
if (marker == '\xff')
{
offset++;
continue; // padding
}
// d0..d9 correspond to SOI, RSTn, EOI
if (marker == 0 || unsigned(marker - '\xd0') <= 9)
{
offset += 2;
continue; // no payload
}
// c0/c2 correspond to SOF0/SOF2
if (marker == '\xc0' || marker == '\xc2')
{
if (offset + 10 > data.size())
return false;
width = readInt16BE(data, offset + 7);
height = readInt16BE(data, offset + 5);
return true;
}
offset += 2 + readInt16BE(data, offset + 2);
}
return false;
}
// https://en.wikipedia.org/wiki/PNG#File_format
static bool hasTransparencyPng(const std::string& data)
{
if (data.size() < 8 + 8 + 13 + 4)
return false;
const char* signature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
if (data.compare(0, 8, signature) != 0)
return false;
if (data.compare(12, 4, "IHDR") != 0)
return false;
int ctype = data[25];
if (ctype != 3)
return ctype == 4 || ctype == 6;
size_t offset = 8; // reparse IHDR chunk for simplicity
while (offset + 12 <= data.size())
{
int length = readInt32BE(data, offset);
if (length < 0)
return false;
if (data.compare(offset + 4, 4, "tRNS") == 0)
return true;
offset += 12 + length;
}
return false;
}
// https://github.khronos.org/KTX-Specification/ktxspec.v2.html
static bool hasTransparencyKtx2(const std::string& data)
{
if (data.size() < 12 + 17 * 4)
return false;
const char* signature = "\xabKTX 20\xbb\r\n\x1a\n";
if (data.compare(0, 12, signature) != 0)
return false;
int dfdOffset = readInt32LE(data, 48);
int dfdLength = readInt32LE(data, 52);
if (dfdLength < 4 + 24 + 16 || unsigned(dfdOffset) > data.size() || unsigned(dfdLength) > data.size() - dfdOffset)
return false;
const int KDF_DF_MODEL_ETC1S = 163;
const int KDF_DF_MODEL_UASTC = 166;
const int KHR_DF_CHANNEL_UASTC_RGBA = 3;
const int KHR_DF_CHANNEL_ETC1S_RGB = 0;
const int KHR_DF_CHANNEL_ETC1S_AAA = 15;
int colorModel = readInt32LE(data, dfdOffset + 12) & 0xff;
int channelType1 = (readInt32LE(data, dfdOffset + 28) >> 24) & 0xf;
int channelType2 = dfdLength >= 4 + 24 + 16 * 2 ? (readInt32LE(data, dfdOffset + 44) >> 24) & 0xf : channelType1;
if (colorModel == KDF_DF_MODEL_ETC1S)
{
return channelType1 == KHR_DF_CHANNEL_ETC1S_RGB && channelType2 == KHR_DF_CHANNEL_ETC1S_AAA;
}
else if (colorModel == KDF_DF_MODEL_UASTC)
{
return channelType1 == KHR_DF_CHANNEL_UASTC_RGBA;
}
return false;
}
// https://developers.google.com/speed/webp/docs/riff_container
static bool hasTransparencyWebP(const std::string& data)
{
if (data.size() < 12 + 4 + 12)
return false;
if (data.compare(0, 4, "RIFF") != 0)
return false;
if (data.compare(8, 4, "WEBP") != 0)
return false;
// WebP data may use VP8L, VP8X or VP8 format, but VP8 does not support transparency
if (data.compare(12, 4, "VP8L") == 0)
{
if (unsigned(data[20]) != 0x2f)
return false;
// width (14) | height (14) | alpha_is_used (1) | version_number(3)
unsigned int header = readInt32LE(data, 21);
return (header & (1 << 28)) != 0;
}
else if (data.compare(12, 4, "VP8X") == 0)
{
// zero (2) | icc (1) | alpha (1) | exif (1) | xmp (1) | animation (1) | zero (1)
unsigned char header = data[20];
return (header & (1 << 4)) != 0;
}
return false;
}
bool hasAlpha(const std::string& data, const char* mime_type)
{
if (strcmp(mime_type, "image/png") == 0)
return hasTransparencyPng(data);
else if (strcmp(mime_type, "image/ktx2") == 0)
return hasTransparencyKtx2(data);
else if (strcmp(mime_type, "image/webp") == 0)
return hasTransparencyWebP(data);
else
return false;
}
bool getDimensions(const std::string& data, const char* mime_type, int& width, int& height)
{
if (strcmp(mime_type, "image/png") == 0)
return getDimensionsPng(data, width, height);
if (strcmp(mime_type, "image/jpeg") == 0)
return getDimensionsJpeg(data, width, height);
return false;
}
static int roundPow2(int value)
{
int result = 1;
while (result < value)
result <<= 1;
// to prevent odd texture sizes from increasing the size too much, we round to nearest power of 2 above a certain size
if (value > 128 && result * 3 / 4 > value)
result >>= 1;
return result;
}
static int roundBlock(int value, bool pow2)
{
if (value == 0)
return 4;
if (pow2 && value > 4)
return roundPow2(value);
return (value + 3) & ~3;
}
void adjustDimensions(int& width, int& height, float scale, int limit, bool pow2)
{
width = int(width * scale);
height = int(height * scale);
if (limit && (width > limit || height > limit))
{
float limit_scale = float(limit) / float(width > height ? width : height);
width = int(width * limit_scale);
height = int(height * limit_scale);
}
width = roundBlock(width, pow2);
height = roundBlock(height, pow2);
}
const char* mimeExtension(const char* mime_type)
{
for (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i)
if (strcmp(kMimeTypes[i][0], mime_type) == 0)
return kMimeTypes[i][1];
return ".raw";
}
+92
View File
@@ -0,0 +1,92 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <float.h>
#include <math.h>
#include <stdio.h>
void comma(std::string& s)
{
char ch = s.empty() ? 0 : s[s.size() - 1];
if (ch != 0 && ch != '[' && ch != '{')
s += ",";
}
void append(std::string& s, size_t v)
{
char buf[32];
snprintf(buf, sizeof(buf), "%zu", v);
s += buf;
}
void append(std::string& s, float v)
{
// sanitize +-inf to +-FLT_MAX and NaN to FLT_MAX
// it would be more consistent to use null for NaN but that makes JSON invalid, and 0 makes it hard to distinguish from valid values
float sv = fabsf(v) < FLT_MAX ? v : (v < 0 ? -FLT_MAX : FLT_MAX);
char buf[64];
snprintf(buf, sizeof(buf), "%.9g", sv);
s += buf;
}
void append(std::string& s, const char* v)
{
s += v;
}
void append(std::string& s, const std::string& v)
{
s += v;
}
void append(std::string& s, const float* data, size_t count)
{
s += '[';
for (size_t i = 0; i < count; ++i)
{
if (i != 0)
s += ',';
append(s, data[i]);
}
s += ']';
}
void appendJson(std::string& s, const char* data)
{
enum State
{
None,
Escape,
Quoted
} state = None;
for (const char* it = data; *it; ++it)
{
char ch = *it;
// whitespace outside of quoted strings can be ignored
if (state != None || !isspace(ch))
s += ch;
// the finite automata tracks whether we're inside a quoted string
switch (state)
{
case None:
state = (ch == '"') ? Quoted : None;
break;
case Quoted:
state = (ch == '"') ? None : (ch == '\\' ? Escape : Quoted);
break;
case Escape:
state = Quoted;
break;
default:
assert(!"Unexpected parsing state");
}
}
}
+355
View File
@@ -0,0 +1,355 @@
// This file is part of gltfpack and is distributed under the terms of MIT License.
/**
* Initialize the library with the Wasm module (library.wasm)
*
* @param wasm Promise with contents of library.wasm
*
* Note: this is called automatically in node.js
*/
function init(wasm) {
if (ready) {
throw new Error('init must be called once');
}
ready = Promise.resolve(wasm)
.then(function (buffer) {
return WebAssembly.instantiate(buffer, { wasi_snapshot_preview1: wasi });
})
.then(function (result) {
instance = result.instance;
instance.exports.__wasm_call_ctors();
});
}
/**
* Pack the requested glTF data using the requested command line and access interface.
*
* @param args An array of strings with the input arguments; the paths for input and output files are interpreted by the interface
* @param iface An interface to the system that will be used to service file requests and other system calls
* @return Promise that indicates completion of the operation
*
* iface should contain the following methods:
* read(path): Given a path, return a Uint8Array with the contents of that path
* write(path, data): Write the specified Uint8Array to the provided path
*/
function pack(args, iface) {
if (!ready) {
throw new Error('init must be called before pack');
}
var argv = args.slice();
argv.unshift('gltfpack');
return ready.then(function () {
var buf = uploadArgv(argv);
output.position = 0;
output.size = 0;
fs_interface = iface;
var result = instance.exports.pack(argv.length, buf);
fs_interface = undefined;
instance.exports.free(buf);
var log = getString(output.data.buffer, 0, output.size);
if (result != 0) {
throw new Error(log);
} else {
return log;
}
});
}
// Library implementation (here be dragons)
var WASI_EBADF = 8;
var WASI_EINVAL = 28;
var WASI_EIO = 29;
var WASI_ENOSYS = 52;
var ready;
var instance;
var fs_interface;
var output = { data: new Uint8Array(), position: 0, size: 0 };
var fds = { 1: output, 2: output, 3: { mount: '/', path: '/' }, 4: { mount: '/gltfpack-$pwd', path: '' } };
var wasi = {
proc_exit: function (rval) {},
fd_close: function (fd) {
if (!fds[fd]) {
return WASI_EBADF;
}
try {
if (fds[fd].close) {
fds[fd].close();
}
fds[fd] = undefined;
return 0;
} catch (err) {
fds[fd] = undefined;
return WASI_EIO;
}
},
fd_fdstat_get: function (fd, stat) {
if (!fds[fd]) {
return WASI_EBADF;
}
var heap = getHeap();
heap.setUint8(stat + 0, fds[fd].path !== undefined ? 3 : 4);
heap.setUint16(stat + 2, 0, true);
heap.setUint32(stat + 8, 0, true);
heap.setUint32(stat + 12, 0, true);
heap.setUint32(stat + 16, 0, true);
heap.setUint32(stat + 20, 0, true);
return 0;
},
path_open32: function (parent_fd, dirflags, path, path_len, oflags, fs_rights_base, fs_rights_inheriting, fdflags, opened_fd) {
if (!fds[parent_fd] || fds[parent_fd].path === undefined) {
return WASI_EBADF;
}
var heap = getHeap();
var file = {};
file.name = fds[parent_fd].path + getString(heap.buffer, path, path_len);
file.position = 0;
if (oflags & 1) {
file.data = new Uint8Array(4096);
file.size = 0;
file.close = function () {
fs_interface.write(file.name, new Uint8Array(file.data.buffer, 0, file.size));
};
} else {
try {
file.data = fs_interface.read(file.name);
if (!file.data) {
return WASI_EIO;
}
file.size = file.data.length;
} catch (err) {
return WASI_EIO;
}
}
var fd = nextFd();
fds[fd] = file;
heap.setUint32(opened_fd, fd, true);
return 0;
},
path_filestat_get: function (parent_fd, flags, path, path_len, buf) {
if (!fds[parent_fd] || fds[parent_fd].path === undefined) {
return WASI_EBADF;
}
var heap = getHeap();
var name = getString(heap.buffer, path, path_len);
for (var i = 0; i < 64; ++i) heap.setUint8(buf + i, 0);
heap.setUint8(buf + 16, name == '.' ? 3 : 4);
return 0;
},
fd_prestat_get: function (fd, buf) {
if (!fds[fd] || fds[fd].path === undefined) {
return WASI_EBADF;
}
var path_buf = stringBuffer(fds[fd].mount);
var heap = getHeap();
heap.setUint8(buf, 0);
heap.setUint32(buf + 4, path_buf.length, true);
return 0;
},
fd_prestat_dir_name: function (fd, path, path_len) {
if (!fds[fd] || fds[fd].path === undefined) {
return WASI_EBADF;
}
var path_buf = stringBuffer(fds[fd].mount);
if (path_len != path_buf.length) {
return WASI_EINVAL;
}
var heap = getHeap();
new Uint8Array(heap.buffer).set(path_buf, path);
return 0;
},
path_remove_directory: function (parent_fd, path, path_len) {
return WASI_EINVAL;
},
fd_fdstat_set_flags: function (fd, flags) {
return WASI_ENOSYS;
},
fd_seek32: function (fd, offset, whence, newoffset) {
if (!fds[fd]) {
return WASI_EBADF;
}
var newposition;
switch (whence) {
case 0:
newposition = offset;
break;
case 1:
newposition = fds[fd].position + offset;
break;
case 2:
newposition = fds[fd].size + offset;
break;
default:
return WASI_EINVAL;
}
if (newposition < 0 || newposition > fds[fd].size) {
return WASI_EINVAL;
}
fds[fd].position = newposition;
var heap = getHeap();
heap.setUint32(newoffset, newposition, true);
return 0;
},
fd_read: function (fd, iovs, iovs_len, nread) {
if (!fds[fd]) {
return WASI_EBADF;
}
var heap = getHeap();
var read = 0;
for (var i = 0; i < iovs_len; ++i) {
var buf = heap.getUint32(iovs + 8 * i + 0, true);
var buf_len = heap.getUint32(iovs + 8 * i + 4, true);
var readi = Math.min(fds[fd].size - fds[fd].position, buf_len);
new Uint8Array(heap.buffer).set(fds[fd].data.subarray(fds[fd].position, fds[fd].position + readi), buf);
fds[fd].position += readi;
read += readi;
}
heap.setUint32(nread, read, true);
return 0;
},
fd_write: function (fd, iovs, iovs_len, nwritten) {
if (!fds[fd]) {
return WASI_EBADF;
}
var heap = getHeap();
var written = 0;
for (var i = 0; i < iovs_len; ++i) {
var buf = heap.getUint32(iovs + 8 * i + 0, true);
var buf_len = heap.getUint32(iovs + 8 * i + 4, true);
if (fds[fd].position + buf_len > fds[fd].data.length) {
fds[fd].data = growArray(fds[fd].data, fds[fd].position + buf_len);
}
fds[fd].data.set(new Uint8Array(heap.buffer, buf, buf_len), fds[fd].position);
fds[fd].position += buf_len;
fds[fd].size = Math.max(fds[fd].position, fds[fd].size);
written += buf_len;
}
heap.setUint32(nwritten, written, true);
return 0;
},
};
function nextFd() {
for (var i = 1; ; ++i) {
if (fds[i] === undefined) {
return i;
}
}
}
function getHeap() {
return new DataView(instance.exports.memory.buffer);
}
function getString(buffer, offset, length) {
return new TextDecoder().decode(new Uint8Array(buffer, offset, length));
}
function stringBuffer(string) {
return new TextEncoder().encode(string);
}
function growArray(data, len) {
var new_length = Math.max(1, data.length);
while (new_length < len) {
new_length *= 2;
}
var new_data = new Uint8Array(new_length);
new_data.set(data);
return new_data;
}
function uploadArgv(argv) {
var buf_size = argv.length * 4;
for (var i = 0; i < argv.length; ++i) {
buf_size += stringBuffer(argv[i]).length + 1;
}
var buf = instance.exports.malloc(buf_size);
var argp = buf + argv.length * 4;
var heap = getHeap();
for (var i = 0; i < argv.length; ++i) {
var item = stringBuffer(argv[i]);
heap.setUint32(buf + i * 4, argp, true);
new Uint8Array(heap.buffer).set(item, argp);
heap.setUint8(argp + item.length, 0);
argp += item.length + 1;
}
return buf;
}
// Automatic initialization for node.js
if (typeof process !== 'undefined' && process.release && process.release.name === 'node') {
init(
import('node:fs').then(function (fs) {
return fs.readFileSync(new URL('./library.wasm', import.meta.url));
})
);
}
export { init, pack };
+637
View File
@@ -0,0 +1,637 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <string.h>
static bool areTexturesEqual(const cgltf_texture& lhs, const cgltf_texture& rhs)
{
if (lhs.image != rhs.image)
return false;
if (lhs.sampler != rhs.sampler)
return false;
if (lhs.basisu_image != rhs.basisu_image)
return false;
if (lhs.webp_image != rhs.webp_image)
return false;
return true;
}
static bool areTextureViewsEqual(const cgltf_texture_view& lhs, const cgltf_texture_view& rhs)
{
if (lhs.has_transform != rhs.has_transform)
return false;
if (lhs.has_transform)
{
const cgltf_texture_transform& lt = lhs.transform;
const cgltf_texture_transform& rt = rhs.transform;
if (memcmp(lt.offset, rt.offset, sizeof(cgltf_float) * 2) != 0)
return false;
if (lt.rotation != rt.rotation)
return false;
if (memcmp(lt.scale, rt.scale, sizeof(cgltf_float) * 2) != 0)
return false;
if (lt.texcoord != rt.texcoord)
return false;
}
if (lhs.texture != rhs.texture && (!lhs.texture || !rhs.texture || !areTexturesEqual(*lhs.texture, *rhs.texture)))
return false;
if (lhs.texcoord != rhs.texcoord)
return false;
if (lhs.scale != rhs.scale)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_pbr_metallic_roughness& lhs, const cgltf_pbr_metallic_roughness& rhs)
{
if (!areTextureViewsEqual(lhs.base_color_texture, rhs.base_color_texture))
return false;
if (!areTextureViewsEqual(lhs.metallic_roughness_texture, rhs.metallic_roughness_texture))
return false;
if (memcmp(lhs.base_color_factor, rhs.base_color_factor, sizeof(cgltf_float) * 4) != 0)
return false;
if (lhs.metallic_factor != rhs.metallic_factor)
return false;
if (lhs.roughness_factor != rhs.roughness_factor)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_pbr_specular_glossiness& lhs, const cgltf_pbr_specular_glossiness& rhs)
{
if (!areTextureViewsEqual(lhs.diffuse_texture, rhs.diffuse_texture))
return false;
if (!areTextureViewsEqual(lhs.specular_glossiness_texture, rhs.specular_glossiness_texture))
return false;
if (memcmp(lhs.diffuse_factor, rhs.diffuse_factor, sizeof(cgltf_float) * 4) != 0)
return false;
if (memcmp(lhs.specular_factor, rhs.specular_factor, sizeof(cgltf_float) * 3) != 0)
return false;
if (lhs.glossiness_factor != rhs.glossiness_factor)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_clearcoat& lhs, const cgltf_clearcoat& rhs)
{
if (!areTextureViewsEqual(lhs.clearcoat_texture, rhs.clearcoat_texture))
return false;
if (!areTextureViewsEqual(lhs.clearcoat_roughness_texture, rhs.clearcoat_roughness_texture))
return false;
if (!areTextureViewsEqual(lhs.clearcoat_normal_texture, rhs.clearcoat_normal_texture))
return false;
if (lhs.clearcoat_factor != rhs.clearcoat_factor)
return false;
if (lhs.clearcoat_roughness_factor != rhs.clearcoat_roughness_factor)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_transmission& lhs, const cgltf_transmission& rhs)
{
if (!areTextureViewsEqual(lhs.transmission_texture, rhs.transmission_texture))
return false;
if (lhs.transmission_factor != rhs.transmission_factor)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_ior& lhs, const cgltf_ior& rhs)
{
if (lhs.ior != rhs.ior)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_specular& lhs, const cgltf_specular& rhs)
{
if (!areTextureViewsEqual(lhs.specular_texture, rhs.specular_texture))
return false;
if (!areTextureViewsEqual(lhs.specular_color_texture, rhs.specular_color_texture))
return false;
if (lhs.specular_factor != rhs.specular_factor)
return false;
if (memcmp(lhs.specular_color_factor, rhs.specular_color_factor, sizeof(cgltf_float) * 3) != 0)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_sheen& lhs, const cgltf_sheen& rhs)
{
if (!areTextureViewsEqual(lhs.sheen_color_texture, rhs.sheen_color_texture))
return false;
if (memcmp(lhs.sheen_color_factor, rhs.sheen_color_factor, sizeof(cgltf_float) * 3) != 0)
return false;
if (!areTextureViewsEqual(lhs.sheen_roughness_texture, rhs.sheen_roughness_texture))
return false;
if (lhs.sheen_roughness_factor != rhs.sheen_roughness_factor)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_volume& lhs, const cgltf_volume& rhs)
{
if (!areTextureViewsEqual(lhs.thickness_texture, rhs.thickness_texture))
return false;
if (lhs.thickness_factor != rhs.thickness_factor)
return false;
if (memcmp(lhs.attenuation_color, rhs.attenuation_color, sizeof(cgltf_float) * 3) != 0)
return false;
if (lhs.attenuation_distance != rhs.attenuation_distance)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_emissive_strength& lhs, const cgltf_emissive_strength& rhs)
{
if (lhs.emissive_strength != rhs.emissive_strength)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_iridescence& lhs, const cgltf_iridescence& rhs)
{
if (lhs.iridescence_factor != rhs.iridescence_factor)
return false;
if (!areTextureViewsEqual(lhs.iridescence_texture, rhs.iridescence_texture))
return false;
if (lhs.iridescence_ior != rhs.iridescence_ior)
return false;
if (lhs.iridescence_thickness_min != rhs.iridescence_thickness_min)
return false;
if (lhs.iridescence_thickness_max != rhs.iridescence_thickness_max)
return false;
if (!areTextureViewsEqual(lhs.iridescence_thickness_texture, rhs.iridescence_thickness_texture))
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_anisotropy& lhs, const cgltf_anisotropy& rhs)
{
if (lhs.anisotropy_strength != rhs.anisotropy_strength)
return false;
if (lhs.anisotropy_rotation != rhs.anisotropy_rotation)
return false;
if (!areTextureViewsEqual(lhs.anisotropy_texture, rhs.anisotropy_texture))
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_dispersion& lhs, const cgltf_dispersion& rhs)
{
if (lhs.dispersion != rhs.dispersion)
return false;
return true;
}
static bool areMaterialComponentsEqual(const cgltf_diffuse_transmission& lhs, const cgltf_diffuse_transmission& rhs)
{
if (lhs.diffuse_transmission_factor != rhs.diffuse_transmission_factor)
return false;
if (memcmp(lhs.diffuse_transmission_color_factor, rhs.diffuse_transmission_color_factor, sizeof(cgltf_float) * 3) != 0)
return false;
if (!areTextureViewsEqual(lhs.diffuse_transmission_texture, rhs.diffuse_transmission_texture))
return false;
if (!areTextureViewsEqual(lhs.diffuse_transmission_color_texture, rhs.diffuse_transmission_color_texture))
return false;
return true;
}
static bool areMaterialsEqual(const cgltf_material& lhs, const cgltf_material& rhs, const Settings& settings)
{
if (lhs.has_pbr_metallic_roughness != rhs.has_pbr_metallic_roughness)
return false;
if (lhs.has_pbr_metallic_roughness && !areMaterialComponentsEqual(lhs.pbr_metallic_roughness, rhs.pbr_metallic_roughness))
return false;
if (lhs.has_pbr_specular_glossiness != rhs.has_pbr_specular_glossiness)
return false;
if (lhs.has_pbr_specular_glossiness && !areMaterialComponentsEqual(lhs.pbr_specular_glossiness, rhs.pbr_specular_glossiness))
return false;
if (lhs.has_clearcoat != rhs.has_clearcoat)
return false;
if (lhs.has_clearcoat && !areMaterialComponentsEqual(lhs.clearcoat, rhs.clearcoat))
return false;
if (lhs.has_transmission != rhs.has_transmission)
return false;
if (lhs.has_transmission && !areMaterialComponentsEqual(lhs.transmission, rhs.transmission))
return false;
if (lhs.has_ior != rhs.has_ior)
return false;
if (lhs.has_ior && !areMaterialComponentsEqual(lhs.ior, rhs.ior))
return false;
if (lhs.has_specular != rhs.has_specular)
return false;
if (lhs.has_specular && !areMaterialComponentsEqual(lhs.specular, rhs.specular))
return false;
if (lhs.has_sheen != rhs.has_sheen)
return false;
if (lhs.has_sheen && !areMaterialComponentsEqual(lhs.sheen, rhs.sheen))
return false;
if (lhs.has_volume != rhs.has_volume)
return false;
if (lhs.has_volume && !areMaterialComponentsEqual(lhs.volume, rhs.volume))
return false;
if (lhs.has_emissive_strength != rhs.has_emissive_strength)
return false;
if (lhs.has_emissive_strength && !areMaterialComponentsEqual(lhs.emissive_strength, rhs.emissive_strength))
return false;
if (lhs.has_iridescence != rhs.has_iridescence)
return false;
if (lhs.has_iridescence && !areMaterialComponentsEqual(lhs.iridescence, rhs.iridescence))
return false;
if (lhs.has_anisotropy != rhs.has_anisotropy)
return false;
if (lhs.has_anisotropy && !areMaterialComponentsEqual(lhs.anisotropy, rhs.anisotropy))
return false;
if (lhs.has_dispersion != rhs.has_dispersion)
return false;
if (lhs.has_dispersion && !areMaterialComponentsEqual(lhs.dispersion, rhs.dispersion))
return false;
if (lhs.has_diffuse_transmission != rhs.has_diffuse_transmission)
return false;
if (lhs.has_diffuse_transmission && !areMaterialComponentsEqual(lhs.diffuse_transmission, rhs.diffuse_transmission))
return false;
if (!areTextureViewsEqual(lhs.normal_texture, rhs.normal_texture))
return false;
if (!areTextureViewsEqual(lhs.occlusion_texture, rhs.occlusion_texture))
return false;
if (!areTextureViewsEqual(lhs.emissive_texture, rhs.emissive_texture))
return false;
if (memcmp(lhs.emissive_factor, rhs.emissive_factor, sizeof(cgltf_float) * 3) != 0)
return false;
if (lhs.alpha_mode != rhs.alpha_mode)
return false;
if (lhs.alpha_cutoff != rhs.alpha_cutoff)
return false;
if (lhs.double_sided != rhs.double_sided)
return false;
if (lhs.unlit != rhs.unlit)
return false;
if (settings.keep_extras && !areExtrasEqual(lhs.extras, rhs.extras))
return false;
return true;
}
void mergeMeshMaterials(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings)
{
std::vector<cgltf_material*> material_remap(data->materials_count);
for (size_t i = 0; i < data->materials_count; ++i)
{
material_remap[i] = &data->materials[i];
if (settings.keep_materials && data->materials[i].name && *data->materials[i].name)
continue;
assert(areMaterialsEqual(data->materials[i], data->materials[i], settings));
for (size_t j = 0; j < i; ++j)
{
if (settings.keep_materials && data->materials[j].name && *data->materials[j].name)
continue;
if (areMaterialsEqual(data->materials[i], data->materials[j], settings))
{
material_remap[i] = &data->materials[j];
break;
}
}
}
for (size_t i = 0; i < meshes.size(); ++i)
{
Mesh& mesh = meshes[i];
if (mesh.material)
mesh.material = material_remap[mesh.material - data->materials];
for (size_t j = 0; j < mesh.variants.size(); ++j)
mesh.variants[j].material = material_remap[mesh.variants[j].material - data->materials];
}
}
void markNeededMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, const std::vector<Mesh>& meshes, const Settings& settings)
{
// mark all used materials as kept
for (size_t i = 0; i < meshes.size(); ++i)
{
const Mesh& mesh = meshes[i];
if (mesh.material)
{
MaterialInfo& mi = materials[mesh.material - data->materials];
mi.keep = true;
}
for (size_t j = 0; j < mesh.variants.size(); ++j)
{
MaterialInfo& mi = materials[mesh.variants[j].material - data->materials];
mi.keep = true;
}
}
// mark all named materials as kept if requested
if (settings.keep_materials)
{
for (size_t i = 0; i < data->materials_count; ++i)
{
cgltf_material& material = data->materials[i];
if (material.name && *material.name)
{
materials[i].keep = true;
}
}
}
}
bool isValidTransform(const cgltf_texture_transform& transform)
{
return transform.offset[0] != 0.0f || transform.offset[1] != 0.0f ||
transform.scale[0] != 1.0f || transform.scale[1] != 1.0f ||
transform.rotation != 0.0f;
}
static const cgltf_image* getTextureImage(const cgltf_texture* texture)
{
if (texture && texture->image)
return texture->image;
if (texture && texture->basisu_image)
return texture->basisu_image;
if (texture && texture->webp_image)
return texture->webp_image;
return NULL;
}
static void analyzeMaterialTexture(const cgltf_texture_view& view, TextureKind kind, MaterialInfo& mi, cgltf_data* data, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images)
{
mi.uses_texture_transform |= view.has_transform && isValidTransform(view.transform);
if (view.texture)
{
textures[view.texture - data->textures].keep = true;
mi.texture_set_mask |= 1u << view.texcoord;
mi.needs_tangents |= (kind == TextureKind_Normal);
}
if (const cgltf_image* image = getTextureImage(view.texture))
{
ImageInfo& info = images[image - data->images];
if (info.kind == TextureKind_Generic)
info.kind = kind;
else if (info.kind > kind) // this is useful to keep color textures that have attrib data in alpha tagged as color
info.kind = kind;
info.normal_map |= (kind == TextureKind_Normal);
info.srgb |= (kind == TextureKind_Color);
}
}
static void analyzeMaterial(const cgltf_material& material, MaterialInfo& mi, cgltf_data* data, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images)
{
if (material.has_pbr_metallic_roughness)
{
analyzeMaterialTexture(material.pbr_metallic_roughness.base_color_texture, TextureKind_Color, mi, data, textures, images);
analyzeMaterialTexture(material.pbr_metallic_roughness.metallic_roughness_texture, TextureKind_Attrib, mi, data, textures, images);
}
if (material.has_pbr_specular_glossiness)
{
analyzeMaterialTexture(material.pbr_specular_glossiness.diffuse_texture, TextureKind_Color, mi, data, textures, images);
analyzeMaterialTexture(material.pbr_specular_glossiness.specular_glossiness_texture, TextureKind_Attrib, mi, data, textures, images);
}
if (material.has_clearcoat)
{
analyzeMaterialTexture(material.clearcoat.clearcoat_texture, TextureKind_Attrib, mi, data, textures, images);
analyzeMaterialTexture(material.clearcoat.clearcoat_roughness_texture, TextureKind_Attrib, mi, data, textures, images);
analyzeMaterialTexture(material.clearcoat.clearcoat_normal_texture, TextureKind_Normal, mi, data, textures, images);
}
if (material.has_transmission)
{
analyzeMaterialTexture(material.transmission.transmission_texture, TextureKind_Attrib, mi, data, textures, images);
}
if (material.has_specular)
{
analyzeMaterialTexture(material.specular.specular_texture, TextureKind_Attrib, mi, data, textures, images);
analyzeMaterialTexture(material.specular.specular_color_texture, TextureKind_Color, mi, data, textures, images);
}
if (material.has_sheen)
{
analyzeMaterialTexture(material.sheen.sheen_color_texture, TextureKind_Color, mi, data, textures, images);
analyzeMaterialTexture(material.sheen.sheen_roughness_texture, TextureKind_Attrib, mi, data, textures, images);
}
if (material.has_volume)
{
analyzeMaterialTexture(material.volume.thickness_texture, TextureKind_Attrib, mi, data, textures, images);
}
if (material.has_iridescence)
{
analyzeMaterialTexture(material.iridescence.iridescence_texture, TextureKind_Attrib, mi, data, textures, images);
analyzeMaterialTexture(material.iridescence.iridescence_thickness_texture, TextureKind_Attrib, mi, data, textures, images);
}
if (material.has_anisotropy)
{
analyzeMaterialTexture(material.anisotropy.anisotropy_texture, TextureKind_Normal, mi, data, textures, images);
}
if (material.has_diffuse_transmission)
{
analyzeMaterialTexture(material.diffuse_transmission.diffuse_transmission_texture, TextureKind_Attrib, mi, data, textures, images);
analyzeMaterialTexture(material.diffuse_transmission.diffuse_transmission_color_texture, TextureKind_Color, mi, data, textures, images);
}
analyzeMaterialTexture(material.normal_texture, TextureKind_Normal, mi, data, textures, images);
analyzeMaterialTexture(material.occlusion_texture, TextureKind_Attrib, mi, data, textures, images);
analyzeMaterialTexture(material.emissive_texture, TextureKind_Color, mi, data, textures, images);
if (material.unlit)
mi.unlit = true;
}
void analyzeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images)
{
for (size_t i = 0; i < data->materials_count; ++i)
{
analyzeMaterial(data->materials[i], materials[i], data, textures, images);
}
}
static int getChannels(const cgltf_image& image, ImageInfo& info, const char* input_path)
{
if (info.channels)
return info.channels;
std::string img_data;
std::string mime_type;
if (readImage(image, input_path, img_data, mime_type))
info.channels = hasAlpha(img_data, mime_type.c_str()) ? 4 : 3;
else
info.channels = -1;
return info.channels;
}
static bool shouldKeepAlpha(const cgltf_texture_view& color, float alpha, cgltf_data* data, const char* input_path, std::vector<ImageInfo>& images)
{
if (alpha != 1.f)
return true;
const cgltf_image* image = getTextureImage(color.texture);
return image && getChannels(*image, images[image - data->images], input_path) == 4;
}
void optimizeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<ImageInfo>& images, const char* input_path)
{
for (size_t i = 0; i < data->materials_count; ++i)
{
// remove BLEND/MASK from materials that don't have alpha information
cgltf_material& material = data->materials[i];
if (material.alpha_mode != cgltf_alpha_mode_opaque && !materials[i].mesh_alpha)
{
if (material.has_pbr_metallic_roughness && shouldKeepAlpha(material.pbr_metallic_roughness.base_color_texture, material.pbr_metallic_roughness.base_color_factor[3], data, input_path, images))
continue;
if (material.has_pbr_specular_glossiness && shouldKeepAlpha(material.pbr_specular_glossiness.diffuse_texture, material.pbr_specular_glossiness.diffuse_factor[3], data, input_path, images))
continue;
material.alpha_mode = cgltf_alpha_mode_opaque;
material.alpha_cutoff = 0.5f; // reset to default to avoid writing it to output
}
}
}
void mergeTextures(cgltf_data* data, std::vector<TextureInfo>& textures)
{
size_t offset = 0;
for (size_t i = 0; i < textures.size(); ++i)
{
TextureInfo& info = textures[i];
if (!info.keep)
continue;
for (size_t j = 0; j < i; ++j)
if (textures[j].keep && areTexturesEqual(data->textures[i], data->textures[j]))
{
info.keep = false;
info.remap = textures[j].remap;
break;
}
if (info.keep)
{
info.remap = int(offset);
offset++;
}
}
}
File diff suppressed because it is too large Load Diff
+206
View File
@@ -0,0 +1,206 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <math.h>
#include <string.h>
void markScenes(cgltf_data* data, std::vector<NodeInfo>& nodes)
{
for (size_t i = 0; i < nodes.size(); ++i)
nodes[i].scene = -1;
for (size_t i = 0; i < data->scenes_count; ++i)
for (size_t j = 0; j < data->scenes[i].nodes_count; ++j)
{
NodeInfo& ni = nodes[data->scenes[i].nodes[j] - data->nodes];
if (ni.scene >= 0)
ni.scene = -2; // multiple scenes
else
ni.scene = int(i);
}
for (size_t i = 0; i < data->nodes_count; ++i)
{
cgltf_node* root = &data->nodes[i];
while (root->parent)
root = root->parent;
nodes[i].scene = nodes[root - data->nodes].scene;
}
}
void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Animation>& animations)
{
for (size_t i = 0; i < animations.size(); ++i)
{
const Animation& animation = animations[i];
for (size_t j = 0; j < animation.tracks.size(); ++j)
{
const Track& track = animation.tracks[j];
// mark nodes that have animation tracks that change their base transform as animated
if (!track.dummy)
{
NodeInfo& ni = nodes[track.node - data->nodes];
ni.animated_path_mask |= (1 << track.path);
}
}
}
for (size_t i = 0; i < data->nodes_count; ++i)
{
NodeInfo& ni = nodes[i];
for (cgltf_node* node = &data->nodes[i]; node; node = node->parent)
ni.animated |= nodes[node - data->nodes].animated_path_mask != 0;
}
}
void markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Mesh>& meshes, const std::vector<Animation>& animations, const Settings& settings)
{
// mark all joints as kept
for (size_t i = 0; i < data->skins_count; ++i)
{
const cgltf_skin& skin = data->skins[i];
// for now we keep all joints directly referenced by the skin and the entire ancestry tree; we keep names for joints as well
for (size_t j = 0; j < skin.joints_count; ++j)
{
NodeInfo& ni = nodes[skin.joints[j] - data->nodes];
ni.keep = true;
}
}
// mark all animated nodes as kept
for (size_t i = 0; i < animations.size(); ++i)
{
const Animation& animation = animations[i];
for (size_t j = 0; j < animation.tracks.size(); ++j)
{
const Track& track = animation.tracks[j];
if (settings.anim_const || !track.dummy)
{
NodeInfo& ni = nodes[track.node - data->nodes];
ni.keep = true;
}
}
}
// mark all mesh nodes as kept
for (size_t i = 0; i < meshes.size(); ++i)
{
const Mesh& mesh = meshes[i];
for (size_t j = 0; j < mesh.nodes.size(); ++j)
{
NodeInfo& ni = nodes[mesh.nodes[j] - data->nodes];
ni.keep = true;
}
}
// mark all light/camera nodes as kept
for (size_t i = 0; i < data->nodes_count; ++i)
{
const cgltf_node& node = data->nodes[i];
if (node.light || node.camera)
{
nodes[i].keep = true;
}
}
// mark all named nodes as needed (if -kn is specified)
if (settings.keep_nodes)
{
for (size_t i = 0; i < data->nodes_count; ++i)
{
const cgltf_node& node = data->nodes[i];
if (node.name && *node.name)
{
nodes[i].keep = true;
}
}
}
}
void remapNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, size_t& node_offset)
{
// to keep a node, we currently need to keep the entire ancestry chain
for (size_t i = 0; i < data->nodes_count; ++i)
{
if (!nodes[i].keep)
continue;
for (cgltf_node* node = &data->nodes[i]; node; node = node->parent)
nodes[node - data->nodes].keep = true;
}
// generate sequential indices for all nodes; they aren't sorted topologically
for (size_t i = 0; i < data->nodes_count; ++i)
{
NodeInfo& ni = nodes[i];
if (ni.keep)
{
ni.remap = int(node_offset);
node_offset++;
}
}
}
void decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform)
{
float m[4][4] = {};
memcpy(m, transform, 16 * sizeof(float));
// extract translation from last row
translation[0] = m[3][0];
translation[1] = m[3][1];
translation[2] = m[3][2];
// compute determinant to determine handedness
float det =
m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) -
m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) +
m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
float sign = (det < 0.f) ? -1.f : 1.f;
// recover scale from axis lengths
scale[0] = sqrtf(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]) * sign;
scale[1] = sqrtf(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]) * sign;
scale[2] = sqrtf(m[2][0] * m[2][0] + m[2][1] * m[2][1] + m[2][2] * m[2][2]) * sign;
// normalize axes to get a pure rotation matrix
float rsx = (scale[0] == 0.f) ? 0.f : 1.f / scale[0];
float rsy = (scale[1] == 0.f) ? 0.f : 1.f / scale[1];
float rsz = (scale[2] == 0.f) ? 0.f : 1.f / scale[2];
float r00 = m[0][0] * rsx, r10 = m[1][0] * rsy, r20 = m[2][0] * rsz;
float r01 = m[0][1] * rsx, r11 = m[1][1] * rsy, r21 = m[2][1] * rsz;
float r02 = m[0][2] * rsx, r12 = m[1][2] * rsy, r22 = m[2][2] * rsz;
// "branchless" version of Mike Day's matrix to quaternion conversion
int qc = r22 < 0 ? (r00 > r11 ? 0 : 1) : (r00 < -r11 ? 2 : 3);
float qs1 = qc & 2 ? -1.f : 1.f;
float qs2 = qc & 1 ? -1.f : 1.f;
float qs3 = (qc - 1) & 2 ? -1.f : 1.f;
float qt = 1.f - qs3 * r00 - qs2 * r11 - qs1 * r22;
float qs = 0.5f / sqrtf(qt);
rotation[qc ^ 0] = qs * qt;
rotation[qc ^ 1] = qs * (r01 + qs1 * r10);
rotation[qc ^ 2] = qs * (r20 + qs2 * r02);
rotation[qc ^ 3] = qs * (r12 + qs3 * r21);
}
+29
View File
@@ -0,0 +1,29 @@
{
"name": "gltfpack",
"version": "1.2.0",
"description": "A command-line tool that can optimize glTF files for size and speed",
"author": "Arseny Kapoulkine",
"license": "MIT",
"bugs": "https://github.com/zeux/meshoptimizer/issues",
"homepage": "https://github.com/zeux/meshoptimizer",
"keywords": [
"gltf"
],
"repository": {
"type": "git",
"url": "https://github.com/zeux/meshoptimizer"
},
"type": "module",
"bin": "./cli.js",
"main": "./library.js",
"files": [
"*.js",
"*.wasm"
],
"scripts": {
"prepublishOnly": "node cli.js -v"
},
"engines": {
"node": ">=18"
}
}
+704
View File
@@ -0,0 +1,704 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../src/meshoptimizer.h"
static const size_t kMaxStreams = 16;
static const char* getError(cgltf_result result, cgltf_data* data)
{
switch (result)
{
case cgltf_result_file_not_found:
return data ? "resource not found" : "file not found";
case cgltf_result_io_error:
return "I/O error";
case cgltf_result_invalid_json:
return "invalid JSON";
case cgltf_result_invalid_gltf:
return "invalid GLTF";
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";
}
}
static 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());
}
static 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];
}
}
static void readAccessor(std::vector<Attr>& data, const cgltf_accessor* accessor, const std::vector<unsigned int>& sparse)
{
data.resize(sparse.size());
for (size_t i = 0; i < sparse.size(); ++i)
cgltf_accessor_read_float(accessor, sparse[i], &data[i].f[0], 4);
}
static void fixupIndices(std::vector<unsigned int>& indices, cgltf_primitive_type& type)
{
if (type == cgltf_primitive_type_line_loop)
{
std::vector<unsigned int> result;
result.reserve(indices.size() * 2 + 2);
for (size_t i = 1; i <= indices.size(); ++i)
{
result.push_back(indices[i - 1]);
result.push_back(indices[i % indices.size()]);
}
indices.swap(result);
type = cgltf_primitive_type_lines;
}
else if (type == cgltf_primitive_type_line_strip)
{
std::vector<unsigned int> result;
result.reserve(indices.size() * 2);
for (size_t i = 1; i < indices.size(); ++i)
{
result.push_back(indices[i - 1]);
result.push_back(indices[i]);
}
indices.swap(result);
type = cgltf_primitive_type_lines;
}
else if (type == cgltf_primitive_type_triangle_strip)
{
std::vector<unsigned int> result;
result.reserve(indices.size() * 3);
for (size_t i = 2; i < indices.size(); ++i)
{
int flip = i & 1;
result.push_back(indices[i - 2 + flip]);
result.push_back(indices[i - 1 - flip]);
result.push_back(indices[i]);
}
indices.swap(result);
type = cgltf_primitive_type_triangles;
}
else if (type == cgltf_primitive_type_triangle_fan)
{
std::vector<unsigned int> result;
result.reserve(indices.size() * 3);
for (size_t i = 2; i < indices.size(); ++i)
{
result.push_back(indices[0]);
result.push_back(indices[i - 1]);
result.push_back(indices[i]);
}
indices.swap(result);
type = cgltf_primitive_type_triangles;
}
else if (type == cgltf_primitive_type_lines)
{
// glTF files don't require that line index count is divisible by 2, but it is obviously critical for scenes to render
indices.resize(indices.size() / 2 * 2);
}
else if (type == cgltf_primitive_type_triangles)
{
// glTF files don't require that triangle index count is divisible by 3, but it is obviously critical for scenes to render
indices.resize(indices.size() / 3 * 3);
}
}
static bool isIdAttribute(const char* name)
{
return strcmp(name, "_ID") == 0 ||
strcmp(name, "_BATCHID") == 0 ||
strncmp(name, "_FEATURE_ID_", 12) == 0;
}
static void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes, std::vector<std::pair<size_t, size_t> >& mesh_remap)
{
size_t total_primitives = 0;
for (size_t mi = 0; mi < data->meshes_count; ++mi)
total_primitives += data->meshes[mi].primitives_count;
meshes.reserve(total_primitives);
mesh_remap.resize(data->meshes_count);
for (size_t mi = 0; mi < data->meshes_count; ++mi)
{
const cgltf_mesh& mesh = data->meshes[mi];
size_t remap_offset = meshes.size();
for (size_t pi = 0; pi < mesh.primitives_count; ++pi)
{
const cgltf_primitive& primitive = mesh.primitives[pi];
if (primitive.type == cgltf_primitive_type_points && primitive.indices)
{
fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because indexed points are not supported\n", int(pi), int(mi));
continue;
}
const cgltf_accessor* positions = cgltf_find_accessor(&primitive, cgltf_attribute_type_position, 0);
if (!positions)
{
fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because it has no position data\n", int(pi), int(mi));
continue;
}
meshes.push_back(Mesh());
Mesh& result = meshes.back();
result.scene = -1;
result.material = primitive.material;
result.extras = primitive.extras;
result.type = primitive.type;
result.streams.reserve(primitive.attributes_count);
size_t vertex_count = positions->count;
if (primitive.indices)
{
result.indices.resize(primitive.indices->count);
if (!result.indices.empty())
cgltf_accessor_unpack_indices(primitive.indices, &result.indices[0], sizeof(unsigned int), result.indices.size());
for (size_t i = 0; i < result.indices.size(); ++i)
assert(result.indices[i] < vertex_count);
}
else if (primitive.type != cgltf_primitive_type_points)
{
// note, while we could generate a good index buffer here, mesh will be reindexed during processing
result.indices.resize(vertex_count);
for (size_t i = 0; i < vertex_count; ++i)
result.indices[i] = unsigned(i);
}
// convert line loops and line/triangle strips to lists
fixupIndices(result.indices, result.type);
std::vector<unsigned int> sparse;
// if the index data is very sparse, switch to deindexing on the fly to avoid the excessive cost of reading large accessors
if (!result.indices.empty() && result.indices.size() < vertex_count / 2)
{
sparse = result.indices;
// mesh will be reindexed during processing
for (size_t i = 0; i < result.indices.size(); ++i)
result.indices[i] = unsigned(i);
}
for (size_t ai = 0; ai < primitive.attributes_count; ++ai)
{
const cgltf_attribute& attr = primitive.attributes[ai];
if (attr.type == cgltf_attribute_type_invalid || (attr.type == cgltf_attribute_type_custom && !isIdAttribute(attr.name)))
{
fprintf(stderr, "Warning: ignoring %s attribute %s in primitive %d of mesh %d\n", attr.type == cgltf_attribute_type_invalid ? "unknown" : "custom", attr.name, int(pi), int(mi));
continue;
}
if (result.streams.size() == kMaxStreams)
{
fprintf(stderr, "Warning: ignoring attribute %s in primitive %d of mesh %d (limit %d reached)\n", attr.name, int(pi), int(mi), int(kMaxStreams));
continue;
}
result.streams.push_back(Stream());
Stream& s = result.streams.back();
s.type = attr.type;
s.index = attr.index;
if (attr.type == cgltf_attribute_type_custom)
s.custom_name = attr.name;
if (sparse.empty())
readAccessor(s.data, attr.data);
else
readAccessor(s.data, attr.data, sparse);
if (attr.type == cgltf_attribute_type_color && attr.data->type == cgltf_type_vec3)
{
for (size_t i = 0; i < s.data.size(); ++i)
s.data[i].f[3] = 1.0f;
}
}
for (size_t ti = 0; ti < primitive.targets_count; ++ti)
{
const cgltf_morph_target& target = primitive.targets[ti];
for (size_t ai = 0; ai < target.attributes_count; ++ai)
{
const cgltf_attribute& attr = target.attributes[ai];
if (attr.type == cgltf_attribute_type_invalid || attr.type == cgltf_attribute_type_custom)
{
fprintf(stderr, "Warning: ignoring %s attribute %s in morph target %d of primitive %d of mesh %d\n", attr.type == cgltf_attribute_type_invalid ? "unknown" : "custom", attr.name, int(ti), int(pi), int(mi));
continue;
}
result.streams.push_back(Stream());
Stream& s = result.streams.back();
s.type = attr.type;
s.index = attr.index;
s.target = int(ti + 1);
if (sparse.empty())
readAccessor(s.data, attr.data);
else
readAccessor(s.data, attr.data, sparse);
}
}
result.targets = primitive.targets_count;
result.target_weights.assign(mesh.weights, mesh.weights + mesh.weights_count);
result.target_names.assign(mesh.target_names, mesh.target_names + mesh.target_names_count);
result.variants.assign(primitive.mappings, primitive.mappings + primitive.mappings_count);
}
mesh_remap[mi] = std::make_pair(remap_offset, meshes.size());
}
}
static void parseMeshInstancesGltf(std::vector<Instance>& instances, cgltf_node* node, size_t ni)
{
cgltf_accessor* translation = NULL;
cgltf_accessor* rotation = NULL;
cgltf_accessor* scale = NULL;
cgltf_accessor* color = NULL;
for (size_t i = 0; i < node->mesh_gpu_instancing.attributes_count; ++i)
{
const cgltf_attribute& attr = node->mesh_gpu_instancing.attributes[i];
if (strcmp(attr.name, "TRANSLATION") == 0 && attr.data->type == cgltf_type_vec3)
translation = attr.data;
else if (strcmp(attr.name, "ROTATION") == 0 && attr.data->type == cgltf_type_vec4)
rotation = attr.data;
else if (strcmp(attr.name, "SCALE") == 0 && attr.data->type == cgltf_type_vec3)
scale = attr.data;
else if (strcmp(attr.name, "_COLOR_0") == 0 && (attr.data->type == cgltf_type_vec3 || attr.data->type == cgltf_type_vec4))
color = attr.data;
else
fprintf(stderr, "Warning: ignoring %s instance attribute %s in node %d\n", *attr.name == '_' ? "custom" : "unknown", attr.name, int(ni));
}
size_t count = node->mesh_gpu_instancing.attributes[0].data->count;
instances.reserve(instances.size() + count);
cgltf_node instance = {};
instance.parent = node;
instance.has_translation = translation != NULL;
instance.has_rotation = rotation != NULL;
instance.has_scale = scale != NULL;
instance.rotation[3] = 1.f;
instance.scale[0] = 1.f;
instance.scale[1] = 1.f;
instance.scale[2] = 1.f;
for (size_t i = 0; i < count; ++i)
{
if (translation)
cgltf_accessor_read_float(translation, i, instance.translation, 4);
if (rotation)
cgltf_accessor_read_float(rotation, i, instance.rotation, 4);
if (scale)
cgltf_accessor_read_float(scale, i, instance.scale, 4);
Instance obj = {};
cgltf_node_transform_world(&instance, obj.transform);
obj.color[0] = obj.color[1] = obj.color[2] = obj.color[3] = 1.0f;
if (color)
cgltf_accessor_read_float(color, i, obj.color, 4);
instances.push_back(obj);
}
}
static void parseMeshNodesGltf(cgltf_data* data, std::vector<Mesh>& meshes, const std::vector<std::pair<size_t, size_t> >& mesh_remap)
{
for (size_t i = 0; i < data->nodes_count; ++i)
{
cgltf_node& node = data->nodes[i];
if (!node.mesh)
continue;
std::pair<size_t, size_t> range = mesh_remap[node.mesh - data->meshes];
for (size_t mi = range.first; mi < range.second; ++mi)
{
Mesh* mesh = &meshes[mi];
if (mesh->skin != node.skin && (!mesh->nodes.empty() || !mesh->instances.empty()))
{
// this should be extremely rare - if the same mesh is used with different skins, we need to duplicate it
// in this case we don't spend any effort on keeping the number of duplicates to the minimum, because this
// should really never happen.
meshes.push_back(*mesh);
mesh = &meshes.back();
}
if (node.has_mesh_gpu_instancing)
{
mesh->scene = 0; // we need to assign scene index since instances are attached to a scene; for now we assume 0
parseMeshInstancesGltf(mesh->instances, &node, i);
}
else
{
mesh->skin = node.skin;
mesh->nodes.push_back(&node);
}
}
}
for (size_t i = 0; i < meshes.size(); ++i)
{
Mesh& mesh = meshes[i];
// because the rest of gltfpack assumes that empty nodes array = world-space mesh, we need to filter unused meshes
if (mesh.nodes.empty() && mesh.instances.empty())
{
mesh.streams.clear();
mesh.indices.clear();
}
}
}
static void parseAnimationsGltf(cgltf_data* data, std::vector<Animation>& animations)
{
animations.reserve(data->animations_count);
for (size_t i = 0; i < data->animations_count; ++i)
{
const cgltf_animation& animation = data->animations[i];
animations.push_back(Animation());
Animation& result = animations.back();
result.name = animation.name;
result.tracks.reserve(animation.channels_count);
for (size_t j = 0; j < animation.channels_count; ++j)
{
const cgltf_animation_channel& channel = animation.channels[j];
if (!channel.target_node)
{
fprintf(stderr, "Warning: ignoring channel %d of animation %d (%s) because it has no target node\n", int(j), int(i), animation.name ? animation.name : "");
continue;
}
result.tracks.push_back(Track());
Track& track = result.tracks.back();
track.node = channel.target_node;
track.path = channel.target_path;
track.components = (channel.target_path == cgltf_animation_path_type_weights) ? track.node->mesh->primitives[0].targets_count : 1;
track.interpolation = channel.sampler->interpolation;
readAccessor(track.time, channel.sampler->input);
readAccessor(track.data, channel.sampler->output);
}
if (result.tracks.empty())
{
fprintf(stderr, "Warning: ignoring animation %d (%s) because it has no valid tracks\n", int(i), animation.name ? animation.name : "");
animations.pop_back();
}
}
}
static bool requiresExtension(cgltf_data* data, const char* name)
{
for (size_t i = 0; i < data->extensions_required_count; ++i)
if (strcmp(data->extensions_required[i], name) == 0)
return true;
return false;
}
static 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->data == NULL && 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;
}
}
for (size_t i = 0; i < data->images_count; ++i)
{
cgltf_image* image = &data->images[i];
if (image->buffer_view && image->buffer_view->buffer->data == NULL)
return true;
}
return false;
}
static void freeFile(cgltf_data* data)
{
data->json = NULL;
data->bin = NULL;
free(data->file_data);
data->file_data = NULL;
}
static bool freeUnusedBuffers(cgltf_data* data)
{
std::vector<char> used(data->buffers_count);
for (size_t i = 0; i < data->skins_count; ++i)
{
const cgltf_skin& skin = data->skins[i];
if (skin.inverse_bind_matrices && skin.inverse_bind_matrices->buffer_view)
{
assert(skin.inverse_bind_matrices->buffer_view->buffer);
used[skin.inverse_bind_matrices->buffer_view->buffer - data->buffers] = 1;
}
}
for (size_t i = 0; i < data->images_count; ++i)
{
const cgltf_image& image = data->images[i];
if (image.buffer_view)
{
assert(image.buffer_view->buffer);
used[image.buffer_view->buffer - data->buffers] = 1;
}
}
bool free_bin = false;
for (size_t i = 0; i < data->buffers_count; ++i)
{
cgltf_buffer& buffer = data->buffers[i];
if (!used[i] && buffer.data)
{
if (buffer.data != data->bin)
free(buffer.data);
else
free_bin = true;
buffer.data = NULL;
}
}
return free_bin;
}
static cgltf_result decompressMeshopt(cgltf_data* data)
{
for (size_t i = 0; i < data->buffer_views_count; ++i)
{
if (!data->buffer_views[i].has_meshopt_compression)
continue;
cgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression;
const unsigned char* source = (const unsigned char*)mc->buffer->data;
if (!source)
return cgltf_result_invalid_gltf;
source += mc->offset;
void* result = malloc(mc->count * mc->stride);
if (!result)
return cgltf_result_out_of_memory;
data->buffer_views[i].data = result;
int rc = -1;
switch (mc->mode)
{
case cgltf_meshopt_compression_mode_attributes:
rc = meshopt_decodeVertexBuffer(result, mc->count, mc->stride, source, mc->size);
break;
case cgltf_meshopt_compression_mode_triangles:
rc = meshopt_decodeIndexBuffer(result, mc->count, mc->stride, source, mc->size);
break;
case cgltf_meshopt_compression_mode_indices:
rc = meshopt_decodeIndexSequence(result, mc->count, mc->stride, source, mc->size);
break;
default:
return cgltf_result_invalid_gltf;
}
if (rc != 0)
return cgltf_result_io_error;
switch (mc->filter)
{
case cgltf_meshopt_compression_filter_octahedral:
meshopt_decodeFilterOct(result, mc->count, mc->stride);
break;
case cgltf_meshopt_compression_filter_quaternion:
meshopt_decodeFilterQuat(result, mc->count, mc->stride);
break;
case cgltf_meshopt_compression_filter_exponential:
meshopt_decodeFilterExp(result, mc->count, mc->stride);
break;
case cgltf_meshopt_compression_filter_color:
meshopt_decodeFilterColor(result, mc->count, mc->stride);
break;
default:
break;
}
}
return cgltf_result_success;
}
static cgltf_data* parseGltf(cgltf_data* data, cgltf_result result, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error)
{
*error = NULL;
if (result != cgltf_result_success)
*error = getError(result, data);
else if (requiresExtension(data, "KHR_draco_mesh_compression"))
*error = "file requires Draco mesh compression support";
else if (needsDummyBuffers(data))
*error = "buffer has no data";
if (*error)
{
cgltf_free(data);
return NULL;
}
if (requiresExtension(data, "KHR_mesh_quantization"))
fprintf(stderr, "Warning: file uses quantized geometry; repacking may result in increased quantization error\n");
if (requiresExtension(data, "EXT_mesh_gpu_instancing") && data->scenes_count > 1)
fprintf(stderr, "Warning: file uses instancing and has more than one scene; results may be incorrect\n");
std::vector<std::pair<size_t, size_t> > mesh_remap;
parseMeshesGltf(data, meshes, mesh_remap);
parseMeshNodesGltf(data, meshes, mesh_remap);
parseAnimationsGltf(data, animations);
bool free_bin = freeUnusedBuffers(data);
if (data->bin && free_bin)
freeFile(data);
return data;
}
cgltf_data* parseGltf(const char* path, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error)
{
cgltf_data* data = NULL;
cgltf_options options = {};
cgltf_result result = cgltf_parse_file(&options, path, &data);
if (result == cgltf_result_success && !data->bin)
freeFile(data);
result = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, path) : result;
result = (result == cgltf_result_success) ? cgltf_validate(data) : result;
result = (result == cgltf_result_success) ? decompressMeshopt(data) : result;
return parseGltf(data, result, meshes, animations, error);
}
cgltf_data* parseGlb(const void* buffer, size_t size, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error)
{
cgltf_data* data = NULL;
cgltf_options options = {};
options.type = cgltf_file_type_glb;
cgltf_result result = cgltf_parse(&options, buffer, size, &data);
result = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, NULL) : result;
result = (result == cgltf_result_success) ? cgltf_validate(data) : result;
result = (result == cgltf_result_success) ? decompressMeshopt(data) : result;
return parseGltf(data, result, meshes, animations, error);
}
bool areExtrasEqual(const cgltf_extras& lhs, const cgltf_extras& rhs)
{
if (lhs.data && rhs.data)
return strcmp(lhs.data, rhs.data) == 0;
else
return lhs.data == rhs.data;
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#define CGLTF_IMPLEMENTATION
#include "../extern/cgltf.h"
#define FAST_OBJ_IMPLEMENTATION
#include "../extern/fast_obj.h"
#undef CGLTF_IMPLEMENTATION
#undef FAST_OBJ_IMPLEMENTATION
+326
View File
@@ -0,0 +1,326 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include "../extern/fast_obj.h"
#include "../src/meshoptimizer.h"
#include <stdlib.h>
#include <string.h>
static void defaultFree(void*, void* p)
{
free(p);
}
static int textureIndex(const std::vector<unsigned int>& textures, unsigned int name)
{
for (size_t i = 0; i < textures.size(); ++i)
if (textures[i] == name)
return int(i);
return -1;
}
static void fixupUri(char* uri)
{
// Some .obj paths come with back slashes, that are invalid as URI separators and won't open on macOS/Linux when embedding textures
for (char* s = uri; *s; ++s)
if (*s == '\\')
*s = '/';
}
static void parseMaterialsObj(fastObjMesh* obj, cgltf_data* data)
{
// every texture in obj has a unique id (1+); we convert it to a 0-based index
// this effectively extracts only used textures out of the obj, as we don't remove unused textures later in the processing
std::vector<unsigned int> textures;
for (unsigned int mi = 0; mi < obj->material_count; ++mi)
{
fastObjMaterial& om = obj->materials[mi];
if (om.map_Kd && textureIndex(textures, om.map_Kd) < 0)
textures.push_back(om.map_Kd);
}
data->images = (cgltf_image*)calloc(textures.size(), sizeof(cgltf_image));
data->images_count = textures.size();
for (size_t i = 0; i < textures.size(); ++i)
{
unsigned int id = textures[i];
data->images[i].uri = strdup(obj->textures[id].name);
fixupUri(data->images[i].uri);
}
data->textures = (cgltf_texture*)calloc(textures.size(), sizeof(cgltf_texture));
data->textures_count = textures.size();
for (size_t i = 0; i < textures.size(); ++i)
{
data->textures[i].image = &data->images[i];
}
data->materials = (cgltf_material*)calloc(obj->material_count, sizeof(cgltf_material));
data->materials_count = obj->material_count;
for (unsigned int mi = 0; mi < obj->material_count; ++mi)
{
const fastObjMaterial& om = obj->materials[mi];
cgltf_material& gm = data->materials[mi];
if (om.name)
gm.name = strdup(om.name);
gm.has_pbr_metallic_roughness = true;
gm.pbr_metallic_roughness.base_color_factor[0] = 1.0f;
gm.pbr_metallic_roughness.base_color_factor[1] = 1.0f;
gm.pbr_metallic_roughness.base_color_factor[2] = 1.0f;
gm.pbr_metallic_roughness.base_color_factor[3] = 1.0f;
gm.pbr_metallic_roughness.metallic_factor = 0.0f;
gm.pbr_metallic_roughness.roughness_factor = 1.0f;
gm.alpha_cutoff = 0.5f;
if (om.map_Kd)
{
gm.pbr_metallic_roughness.base_color_texture.texture = &data->textures[textureIndex(textures, om.map_Kd)];
gm.pbr_metallic_roughness.base_color_texture.scale = 1.0f;
gm.alpha_mode = (om.illum == 4 || om.illum == 6 || om.illum == 7 || om.illum == 9) ? cgltf_alpha_mode_mask : cgltf_alpha_mode_opaque;
}
else
{
gm.pbr_metallic_roughness.base_color_factor[0] = om.Kd[0];
gm.pbr_metallic_roughness.base_color_factor[1] = om.Kd[1];
gm.pbr_metallic_roughness.base_color_factor[2] = om.Kd[2];
}
if (om.map_d)
{
if (om.map_Kd && strcmp(obj->textures[om.map_Kd].name, obj->textures[om.map_d].name) != 0)
fprintf(stderr, "Warning: material has different diffuse and alpha textures (Kd: %s, d: %s) and might not render correctly\n", obj->textures[om.map_Kd].name, obj->textures[om.map_d].name);
gm.alpha_mode = cgltf_alpha_mode_blend;
}
else if (om.d < 1.0f)
{
gm.pbr_metallic_roughness.base_color_factor[3] = om.d;
gm.alpha_mode = cgltf_alpha_mode_blend;
}
}
}
static void parseNodesObj(fastObjMesh* obj, cgltf_data* data)
{
data->nodes = (cgltf_node*)calloc(obj->object_count, sizeof(cgltf_node));
data->nodes_count = obj->object_count;
for (unsigned int oi = 0; oi < obj->object_count; ++oi)
{
const fastObjGroup& og = obj->objects[oi];
cgltf_node* node = &data->nodes[oi];
if (og.name)
node->name = strdup(og.name);
node->rotation[3] = 1.0f;
node->scale[0] = 1.0f;
node->scale[1] = 1.0f;
node->scale[2] = 1.0f;
}
data->scenes = (cgltf_scene*)calloc(1, sizeof(cgltf_scene));
data->scenes_count = 1;
data->scene = data->scenes;
data->scenes->nodes = (cgltf_node**)calloc(obj->object_count, sizeof(cgltf_node*));
data->scenes->nodes_count = obj->object_count;
for (unsigned int oi = 0; oi < obj->object_count; ++oi)
data->scenes->nodes[oi] = &data->nodes[oi];
}
static void parseMeshObj(fastObjMesh* obj, unsigned int face_offset, unsigned int face_vertex_offset, unsigned int face_count, unsigned int face_vertex_count, unsigned int index_count, Mesh& mesh)
{
std::vector<unsigned int> remap(face_vertex_count);
size_t unique_vertices = meshopt_generateVertexRemap(remap.data(), NULL, face_vertex_count, &obj->indices[face_vertex_offset], face_vertex_count, sizeof(fastObjIndex));
int pos_stream = 0;
int nrm_stream = obj->normal_count > 1 ? 1 : -1;
int tex_stream = obj->texcoord_count > 1 ? 1 + (nrm_stream >= 0) : -1;
int col_stream = obj->color_count > 1 ? 1 + (nrm_stream >= 0) + (tex_stream >= 0) : -1;
mesh.streams.resize(1 + (nrm_stream >= 0) + (tex_stream >= 0) + (col_stream >= 0));
mesh.streams[pos_stream].type = cgltf_attribute_type_position;
mesh.streams[pos_stream].data.resize(unique_vertices);
if (nrm_stream >= 0)
{
mesh.streams[nrm_stream].type = cgltf_attribute_type_normal;
mesh.streams[nrm_stream].data.resize(unique_vertices);
}
if (tex_stream >= 0)
{
mesh.streams[tex_stream].type = cgltf_attribute_type_texcoord;
mesh.streams[tex_stream].data.resize(unique_vertices);
}
if (col_stream >= 0)
{
mesh.streams[col_stream].type = cgltf_attribute_type_color;
mesh.streams[col_stream].data.resize(unique_vertices);
}
mesh.indices.resize(index_count);
for (unsigned int vi = 0; vi < face_vertex_count; ++vi)
{
unsigned int target = remap[vi];
// TODO: this fills every target vertex multiple times
fastObjIndex ii = obj->indices[face_vertex_offset + vi];
Attr p = {{obj->positions[ii.p * 3 + 0], obj->positions[ii.p * 3 + 1], obj->positions[ii.p * 3 + 2]}};
mesh.streams[pos_stream].data[target] = p;
if (nrm_stream >= 0)
{
Attr n = {{obj->normals[ii.n * 3 + 0], obj->normals[ii.n * 3 + 1], obj->normals[ii.n * 3 + 2]}};
mesh.streams[nrm_stream].data[target] = n;
}
if (tex_stream >= 0)
{
Attr t = {{obj->texcoords[ii.t * 2 + 0], 1.f - obj->texcoords[ii.t * 2 + 1]}};
mesh.streams[tex_stream].data[target] = t;
}
if (col_stream >= 0)
{
Attr c = {{obj->colors[ii.p * 3 + 0], obj->colors[ii.p * 3 + 1], obj->colors[ii.p * 3 + 2]}};
mesh.streams[col_stream].data[target] = c;
}
}
unsigned int vertex_offset = 0;
unsigned int index_offset = 0;
for (unsigned int fi = 0; fi < face_count; ++fi)
{
unsigned int face_vertices = obj->face_vertices[face_offset + fi];
if (mesh.type == cgltf_primitive_type_lines)
{
for (unsigned int vi = 1; vi < face_vertices; ++vi)
{
size_t to = index_offset + (vi - 1) * 2;
mesh.indices[to + 0] = remap[vertex_offset + vi - 1];
mesh.indices[to + 1] = remap[vertex_offset + vi];
}
vertex_offset += face_vertices;
index_offset += (face_vertices - 1) * 2;
}
else
{
for (unsigned int vi = 2; vi < face_vertices; ++vi)
{
size_t to = index_offset + (vi - 2) * 3;
mesh.indices[to + 0] = remap[vertex_offset];
mesh.indices[to + 1] = remap[vertex_offset + vi - 1];
mesh.indices[to + 2] = remap[vertex_offset + vi];
}
vertex_offset += face_vertices;
index_offset += (face_vertices - 2) * 3;
}
}
assert(vertex_offset == face_vertex_count);
assert(index_offset == index_count);
}
static void parseMeshGroupObj(fastObjMesh* obj, const fastObjGroup& og, cgltf_data* data, cgltf_node* node, std::vector<Mesh>& meshes)
{
unsigned int face_vertex_offset = og.index_offset;
unsigned int face_end_offset = og.face_offset + og.face_count;
for (unsigned int face_offset = og.face_offset; face_offset < face_end_offset;)
{
unsigned int mi = obj->face_materials[face_offset];
unsigned char isl = obj->face_lines ? obj->face_lines[face_offset] : 0;
unsigned int face_count = 0;
unsigned int face_vertex_count = 0;
unsigned int index_count = 0;
for (unsigned int fj = face_offset; fj < face_end_offset && obj->face_materials[fj] == mi && (!obj->face_lines || obj->face_lines[fj] == isl); ++fj)
{
face_count += 1;
face_vertex_count += obj->face_vertices[fj];
index_count += isl ? (obj->face_vertices[fj] - 1) * 2 : (obj->face_vertices[fj] - 2) * 3;
}
meshes.push_back(Mesh());
Mesh& mesh = meshes.back();
if (data->materials_count)
{
assert(mi < data->materials_count);
mesh.material = &data->materials[mi];
}
mesh.type = isl ? cgltf_primitive_type_lines : cgltf_primitive_type_triangles;
mesh.targets = 0;
if (node)
mesh.nodes.push_back(node);
parseMeshObj(obj, face_offset, face_vertex_offset, face_count, face_vertex_count, index_count, mesh);
face_offset += face_count;
face_vertex_offset += face_vertex_count;
}
}
cgltf_data* parseObj(const char* path, std::vector<Mesh>& meshes, const char** error)
{
fastObjMesh* obj = fast_obj_read(path);
if (!obj)
{
*error = "file not found";
return NULL;
}
int fallback_materials = 0;
for (unsigned int mi = 0; mi < obj->material_count; ++mi)
fallback_materials += obj->materials[mi].fallback;
if (fallback_materials)
fprintf(stderr, "Warning: %d/%d materials could not be loaded from mtllib\n", fallback_materials, obj->material_count);
cgltf_data* data = (cgltf_data*)calloc(1, sizeof(cgltf_data));
data->memory.free_func = defaultFree;
parseMaterialsObj(obj, data);
parseNodesObj(obj, data);
assert(data->nodes_count == obj->object_count);
for (unsigned int oi = 0; oi < obj->object_count; ++oi)
parseMeshGroupObj(obj, obj->objects[oi], data, &data->nodes[oi], meshes);
fast_obj_destroy(obj);
return data;
}
+913
View File
@@ -0,0 +1,913 @@
// This file is part of gltfpack; see gltfpack.h for version/license details
#include "gltfpack.h"
#include <algorithm>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdint.h>
#include "../src/meshoptimizer.h"
struct Bounds
{
Attr min, max;
Bounds()
{
min.f[0] = min.f[1] = min.f[2] = min.f[3] = +FLT_MAX;
max.f[0] = max.f[1] = max.f[2] = max.f[3] = -FLT_MAX;
}
bool isValid() const
{
return min.f[0] <= max.f[0] && min.f[1] <= max.f[1] && min.f[2] <= max.f[2] && min.f[3] <= max.f[3];
}
float getExtent() const
{
return std::max(max.f[0] - min.f[0], std::max(max.f[1] - min.f[1], max.f[2] - min.f[2]));
}
void merge(const Bounds& other)
{
for (int k = 0; k < 4; ++k)
{
min.f[k] = std::min(min.f[k], other.min.f[k]);
max.f[k] = std::max(max.f[k], other.max.f[k]);
}
}
};
static Bounds computeBounds(const Mesh& mesh, cgltf_attribute_type type)
{
Bounds b;
Attr pad = {};
for (size_t j = 0; j < mesh.streams.size(); ++j)
{
const Stream& s = mesh.streams[j];
if (s.type == type)
{
if (s.target == 0)
{
for (size_t k = 0; k < s.data.size(); ++k)
{
const Attr& a = s.data[k];
b.min.f[0] = std::min(b.min.f[0], a.f[0]);
b.min.f[1] = std::min(b.min.f[1], a.f[1]);
b.min.f[2] = std::min(b.min.f[2], a.f[2]);
b.min.f[3] = std::min(b.min.f[3], a.f[3]);
b.max.f[0] = std::max(b.max.f[0], a.f[0]);
b.max.f[1] = std::max(b.max.f[1], a.f[1]);
b.max.f[2] = std::max(b.max.f[2], a.f[2]);
b.max.f[3] = std::max(b.max.f[3], a.f[3]);
}
}
else
{
for (size_t k = 0; k < s.data.size(); ++k)
{
const Attr& a = s.data[k];
pad.f[0] = std::max(pad.f[0], fabsf(a.f[0]));
pad.f[1] = std::max(pad.f[1], fabsf(a.f[1]));
pad.f[2] = std::max(pad.f[2], fabsf(a.f[2]));
pad.f[3] = std::max(pad.f[3], fabsf(a.f[3]));
}
}
}
}
for (int k = 0; k < 4; ++k)
{
b.min.f[k] -= pad.f[k];
b.max.f[k] += pad.f[k];
}
return b;
}
static float computeUvArea(const Mesh& mesh)
{
if (mesh.indices.empty() || mesh.type != cgltf_primitive_type_triangles)
return 0.f;
float result = 0.f;
for (size_t j = 0; j < mesh.streams.size(); ++j)
{
const Stream& s = mesh.streams[j];
if (s.type != cgltf_attribute_type_texcoord)
continue;
float uvarea = 0.f;
for (size_t i = 0; i < mesh.indices.size(); i += 3)
{
unsigned int a = mesh.indices[i + 0];
unsigned int b = mesh.indices[i + 1];
unsigned int c = mesh.indices[i + 2];
const Attr& va = s.data[a];
const Attr& vb = s.data[b];
const Attr& vc = s.data[c];
uvarea += fabsf((vb.f[0] - va.f[0]) * (vc.f[1] - va.f[1]) - (vc.f[0] - va.f[0]) * (vb.f[1] - va.f[1]));
}
result = std::max(result, uvarea / float(mesh.indices.size() / 3));
}
return result;
}
QuantizationPosition prepareQuantizationPosition(const std::vector<Mesh>& meshes, const Settings& settings)
{
QuantizationPosition result = {};
result.bits = settings.pos_bits;
result.normalized = settings.pos_normalized;
std::vector<Bounds> bounds(meshes.size());
for (size_t i = 0; i < meshes.size(); ++i)
bounds[i] = computeBounds(meshes[i], cgltf_attribute_type_position);
Bounds b;
for (size_t i = 0; i < meshes.size(); ++i)
b.merge(bounds[i]);
if (b.isValid())
{
result.offset[0] = b.min.f[0];
result.offset[1] = b.min.f[1];
result.offset[2] = b.min.f[2];
result.scale = b.getExtent();
}
if (b.isValid() && settings.quantize && !settings.pos_float)
{
float error = result.scale * 0.5f / (1 << (result.bits - 1));
float max_rel_error = 0;
for (size_t i = 0; i < meshes.size(); ++i)
if (bounds[i].isValid() && bounds[i].getExtent() > 1e-2f)
max_rel_error = std::max(max_rel_error, error / bounds[i].getExtent());
if (max_rel_error > 5e-2f)
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);
}
result.node_scale = result.scale / float((1 << result.bits) - 1) * (result.normalized ? 65535.f : 1.f);
return result;
}
static size_t follow(std::vector<size_t>& parents, size_t index)
{
while (index != parents[index])
{
size_t parent = parents[index];
parents[index] = parents[parent];
index = parent;
}
return index;
}
void prepareQuantizationTexture(cgltf_data* data, std::vector<QuantizationTexture>& result, std::vector<size_t>& indices, const std::vector<Mesh>& meshes, const Settings& settings)
{
// use union-find to associate each material with a canonical material
// this is necessary because any set of materials that are used on the same mesh must use the same quantization
std::vector<size_t> parents(result.size());
for (size_t i = 0; i < parents.size(); ++i)
parents[i] = i;
for (size_t i = 0; i < meshes.size(); ++i)
{
const Mesh& mesh = meshes[i];
if (!mesh.material && mesh.variants.empty())
continue;
size_t root = follow(parents, (mesh.material ? mesh.material : mesh.variants[0].material) - data->materials);
for (size_t j = 0; j < mesh.variants.size(); ++j)
{
size_t var = follow(parents, mesh.variants[j].material - data->materials);
parents[var] = root;
}
indices[i] = root;
}
// compute canonical material bounds based on meshes that use them
std::vector<Bounds> bounds(result.size());
for (size_t i = 0; i < meshes.size(); ++i)
{
const Mesh& mesh = meshes[i];
if (!mesh.material && mesh.variants.empty())
continue;
indices[i] = follow(parents, indices[i]);
Bounds mb = computeBounds(mesh, cgltf_attribute_type_texcoord);
bounds[indices[i]].merge(mb);
}
// detect potential precision issues and warn about them
if (settings.quantize && !settings.tex_float)
{
float max_rel_error = 0;
for (size_t i = 0; i < meshes.size(); ++i)
{
const Mesh& mesh = meshes[i];
if (!mesh.material && mesh.variants.empty())
continue;
const Bounds& b = bounds[indices[i]];
if (!b.isValid())
continue;
float scale = std::max(b.max.f[0] - b.min.f[0], b.max.f[1] - b.min.f[1]);
float error = scale * 0.5f / (1 << (settings.tex_bits - 1));
if (error < 1e-3f)
continue;
float uvarea = computeUvArea(mesh);
float rel_error = uvarea > 0 ? error / sqrtf(uvarea) : 0.f;
max_rel_error = std::max(max_rel_error, rel_error);
}
if (max_rel_error > 1e-1f)
fprintf(stderr, "Warning: texture coordinate data has significant error (%.0f%%); consider using floating-point quantization (-vtf) or more bits (-vt N)\n", max_rel_error * 100);
}
// update all material data using canonical bounds
for (size_t i = 0; i < result.size(); ++i)
{
QuantizationTexture& qt = result[i];
qt.bits = settings.tex_bits;
qt.normalized = true;
const Bounds& b = bounds[follow(parents, i)];
if (b.isValid())
{
qt.offset[0] = b.min.f[0];
qt.offset[1] = b.min.f[1];
qt.scale[0] = b.max.f[0] - b.min.f[0];
qt.scale[1] = b.max.f[1] - b.min.f[1];
}
}
}
void getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition& qp, const Settings& settings)
{
assert(stream.type == cgltf_attribute_type_position);
assert(stream.data.size() > 0);
min[0] = min[1] = min[2] = FLT_MAX;
max[0] = max[1] = max[2] = -FLT_MAX;
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
for (int k = 0; k < 3; ++k)
{
min[k] = std::min(min[k], a.f[k]);
max[k] = std::max(max[k], a.f[k]);
}
}
if (settings.quantize)
{
if (settings.pos_float)
{
for (int k = 0; k < 3; ++k)
{
min[k] = meshopt_quantizeFloat(min[k], qp.bits);
max[k] = meshopt_quantizeFloat(max[k], qp.bits);
}
}
else
{
float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale * (stream.target > 0 && qp.normalized ? 32767.f / 65535.f : 1.f);
for (int k = 0; k < 3; ++k)
{
if (stream.target == 0)
{
min[k] = float(meshopt_quantizeUnorm((min[k] - qp.offset[k]) * pos_rscale, qp.bits));
max[k] = float(meshopt_quantizeUnorm((max[k] - qp.offset[k]) * pos_rscale, qp.bits));
}
else
{
min[k] = (min[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(min[k]) * pos_rscale, qp.bits));
max[k] = (max[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(max[k]) * pos_rscale, qp.bits));
}
}
}
}
}
static void renormalizeWeights(uint8_t (&w)[4])
{
int sum = w[0] + w[1] + w[2] + w[3];
if (sum == 255)
return;
// we assume that the total error is limited to 0.5/component = 2
// this means that it's acceptable to adjust the max. component to compensate for the error
int max = 0;
for (int k = 1; k < 4; ++k)
if (w[k] > w[max])
max = k;
w[max] += uint8_t(255 - sum);
}
static void encodeSnorm(void* destination, size_t count, size_t stride, int bits, const float* data)
{
assert(stride == 4 || stride == 8);
assert(bits >= 1 && bits <= 16);
signed char* d8 = static_cast<signed char*>(destination);
short* d16 = static_cast<short*>(destination);
for (size_t i = 0; i < count; ++i)
{
const float* v = &data[i * 4];
int fx = meshopt_quantizeSnorm(v[0], bits);
int fy = meshopt_quantizeSnorm(v[1], bits);
int fz = meshopt_quantizeSnorm(v[2], bits);
int fw = meshopt_quantizeSnorm(v[3], bits);
if (stride == 4)
{
d8[i * 4 + 0] = (signed char)(fx);
d8[i * 4 + 1] = (signed char)(fy);
d8[i * 4 + 2] = (signed char)(fz);
d8[i * 4 + 3] = (signed char)(fw);
}
else
{
d16[i * 4 + 0] = short(fx);
d16[i * 4 + 1] = short(fy);
d16[i * 4 + 2] = short(fz);
d16[i * 4 + 3] = short(fw);
}
}
}
static int quantizeColor(float v, int bytebits, int bits)
{
int result = meshopt_quantizeUnorm(v, bytebits);
// replicate the top bit into the low significant bits
const int mask = (1 << (bytebits - bits)) - 1;
return (result & ~mask) | (mask & -(result >> (bytebits - 1)));
}
static void encodeColor(void* destination, size_t count, size_t stride, int bits, const float* data)
{
assert(stride == 4 || stride == 8);
assert(bits >= 2 && bits <= 16);
unsigned char* d8 = static_cast<unsigned char*>(destination);
unsigned short* d16 = static_cast<unsigned short*>(destination);
for (size_t i = 0; i < count; ++i)
{
const float* c = &data[i * 4];
if (stride == 4)
{
d8[i * 4 + 0] = uint8_t(quantizeColor(c[0], 8, bits));
d8[i * 4 + 1] = uint8_t(quantizeColor(c[1], 8, bits));
d8[i * 4 + 2] = uint8_t(quantizeColor(c[2], 8, bits));
d8[i * 4 + 3] = uint8_t(quantizeColor(c[3], 8, bits));
}
else
{
d16[i * 4 + 0] = uint16_t(quantizeColor(c[0], 16, bits));
d16[i * 4 + 1] = uint16_t(quantizeColor(c[1], 16, bits));
d16[i * 4 + 2] = uint16_t(quantizeColor(c[2], 16, bits));
d16[i * 4 + 3] = uint16_t(quantizeColor(c[3], 16, bits));
}
}
}
static StreamFormat writeVertexStreamRaw(std::string& bin, const Stream& stream, cgltf_type type, size_t components)
{
assert(components >= 1 && components <= 4);
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
bin.append(reinterpret_cast<const char*>(a.f), sizeof(float) * components);
}
StreamFormat format = {type, cgltf_component_type_r_32f, false, sizeof(float) * components};
return format;
}
static StreamFormat writeVertexStreamFloat(std::string& bin, const Stream& stream, cgltf_type type, int components, bool expf, int bits, meshopt_EncodeExpMode mode)
{
assert(components >= 1 && components <= 4);
StreamFormat::Filter filter = expf ? StreamFormat::Filter_Exp : StreamFormat::Filter_None;
if (filter == StreamFormat::Filter_Exp)
{
size_t offset = bin.size();
size_t stride = sizeof(float) * components;
for (size_t i = 0; i < stream.data.size(); ++i)
bin.append(reinterpret_cast<const char*>(stream.data[i].f), stride);
meshopt_encodeFilterExp(&bin[offset], stream.data.size(), stride, bits + 1, reinterpret_cast<const float*>(&bin[offset]), mode);
}
else
{
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
float v[4];
for (int k = 0; k < components; ++k)
v[k] = meshopt_quantizeFloat(a.f[k], bits);
bin.append(reinterpret_cast<const char*>(v), sizeof(float) * components);
}
}
StreamFormat format = {type, cgltf_component_type_r_32f, false, sizeof(float) * components, filter};
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)
{
if (!settings.quantize)
return writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3);
if (settings.pos_float)
return writeVertexStreamFloat(bin, stream, cgltf_type_vec3, 3, settings.compress && filters, qp.bits,
settings.compressmore ? meshopt_EncodeExpSharedComponent : meshopt_EncodeExpSeparate);
if (stream.target == 0)
{
float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale;
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
uint16_t v[4] = {
uint16_t(meshopt_quantizeUnorm((a.f[0] - qp.offset[0]) * pos_rscale, qp.bits)),
uint16_t(meshopt_quantizeUnorm((a.f[1] - qp.offset[1]) * pos_rscale, qp.bits)),
uint16_t(meshopt_quantizeUnorm((a.f[2] - qp.offset[2]) * pos_rscale, qp.bits)),
0};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16u, qp.normalized, 8};
return format;
}
else
{
float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale * (qp.normalized ? 32767.f / 65535.f : 1.f);
int maxv = 0;
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits));
maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits));
maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits));
}
if (maxv <= 127 && !qp.normalized)
{
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
int8_t v[4] = {
int8_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)),
int8_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)),
int8_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)),
0};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_8, false, 4};
return format;
}
else
{
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
int16_t v[4] = {
int16_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)),
int16_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)),
int16_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)),
0};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16, qp.normalized, 8};
return format;
}
}
}
else if (stream.type == cgltf_attribute_type_texcoord)
{
if (!settings.quantize)
return writeVertexStreamRaw(bin, stream, cgltf_type_vec2, 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)
return writeVertexStreamFloat(bin, stream, cgltf_type_vec2, 2, settings.compress && filters, qt.bits, meshopt_EncodeExpClamped);
float uv_rscale[2] = {
qt.scale[0] == 0.f ? 0.f : 1.f / qt.scale[0],
qt.scale[1] == 0.f ? 0.f : 1.f / qt.scale[1],
};
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
uint16_t v[2] = {
uint16_t(meshopt_quantizeUnorm((a.f[0] - qt.offset[0]) * uv_rscale[0], qt.bits)),
uint16_t(meshopt_quantizeUnorm((a.f[1] - qt.offset[1]) * uv_rscale[1], qt.bits)),
};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_16u, qt.normalized, 4};
return format;
}
else if (stream.type == cgltf_attribute_type_normal)
{
if (!settings.quantize)
return writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3);
// expand the encoded range to ensure it covers [0..1) interval
// for unit length vectors, SharedComponent results in a slightly smaller encoding vs Clamped
// for morph deltas, we could use SharedComponent but it may preserve noisy small inputs redundantly
if (settings.nrm_float)
return writeVertexStreamFloat(bin, stream, cgltf_type_vec3, 3, settings.compress && filters, settings.nrm_bits,
settings.compressmore && stream.target == 0 ? meshopt_EncodeExpSharedComponent : meshopt_EncodeExpClamped);
bool oct = filters && settings.compressmore && stream.target == 0;
int bits = settings.nrm_bits;
StreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None;
size_t offset = bin.size();
size_t stride = bits > 8 ? 8 : 4;
bin.resize(bin.size() + stream.data.size() * stride);
if (oct)
meshopt_encodeFilterOct(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);
else
encodeSnorm(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);
cgltf_component_type component_type = bits > 8 ? cgltf_component_type_r_16 : cgltf_component_type_r_8;
StreamFormat format = {cgltf_type_vec3, component_type, true, stride, filter};
return format;
}
else if (stream.type == cgltf_attribute_type_tangent)
{
if (!settings.quantize)
return writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4);
bool oct = filters && settings.compressmore && stream.target == 0;
int bits = (settings.nrm_bits > 8) ? 8 : settings.nrm_bits;
StreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None;
size_t offset = bin.size();
size_t stride = 4;
bin.resize(bin.size() + stream.data.size() * stride);
if (oct)
meshopt_encodeFilterOct(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);
else
encodeSnorm(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);
cgltf_type type = (stream.target == 0) ? cgltf_type_vec4 : cgltf_type_vec3;
StreamFormat format = {type, cgltf_component_type_r_8, true, 4, filter};
return format;
}
else if (stream.type == cgltf_attribute_type_color)
{
bool col = filters && settings.compresskhr && settings.compressmore;
int bits = settings.col_bits;
StreamFormat::Filter filter = col ? StreamFormat::Filter_Color : StreamFormat::Filter_None;
size_t offset = bin.size();
size_t stride = bits > 8 ? 8 : 4;
bin.resize(bin.size() + stream.data.size() * stride);
if (col)
meshopt_encodeFilterColor(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);
else
encodeColor(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);
if (bits > 8)
{
StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, true, 8, filter};
return format;
}
else
{
StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4, filter};
return format;
}
}
else if (stream.type == cgltf_attribute_type_weights)
{
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
float ws = a.f[0] + a.f[1] + a.f[2] + a.f[3];
float wsi = (ws == 0.f) ? 0.f : 1.f / ws;
uint8_t v[4] = {
uint8_t(meshopt_quantizeUnorm(a.f[0] * wsi, 8)),
uint8_t(meshopt_quantizeUnorm(a.f[1] * wsi, 8)),
uint8_t(meshopt_quantizeUnorm(a.f[2] * wsi, 8)),
uint8_t(meshopt_quantizeUnorm(a.f[3] * wsi, 8))};
if (wsi != 0.f)
renormalizeWeights(v);
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4};
return format;
}
else if (stream.type == cgltf_attribute_type_joints)
{
unsigned int maxj = 0;
for (size_t i = 0; i < stream.data.size(); ++i)
for (int k = 0; k < 4; ++k)
maxj = std::max(maxj, unsigned(int(stream.data[i].f[k])));
assert(maxj <= 65535);
if (maxj <= 255)
{
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
uint8_t v[4] = {
uint8_t(a.f[0]),
uint8_t(a.f[1]),
uint8_t(a.f[2]),
uint8_t(a.f[3])};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, false, 4};
return format;
}
else
{
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
uint16_t v[4] = {
uint16_t(a.f[0]),
uint16_t(a.f[1]),
uint16_t(a.f[2]),
uint16_t(a.f[3])};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, false, 8};
return format;
}
}
else if (stream.type == cgltf_attribute_type_custom)
{
// note: _custom is equivalent to _ID, as such the data contains scalar integers
if (!settings.compressmore || !filters)
return writeVertexStreamRaw(bin, stream, cgltf_type_scalar, 1);
unsigned int maxv = 0;
for (size_t i = 0; i < stream.data.size(); ++i)
maxv = std::max(maxv, unsigned(int(stream.data[i].f[0])));
// exp encoding uses a signed mantissa with only 23 significant bits; input glTF encoding may encode indices losslessly up to 2^24
if (maxv >= (1 << 23))
return writeVertexStreamRaw(bin, stream, cgltf_type_scalar, 1);
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];
uint32_t id = uint32_t(a.f[0]);
uint32_t v = id; // exp encoding of integers in [0..2^23-1] range is equivalent to the integer itself
bin.append(reinterpret_cast<const char*>(&v), sizeof(v));
}
StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4, StreamFormat::Filter_Exp};
return format;
}
else
{
return writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4);
}
}
StreamFormat writeIndexStream(std::string& bin, const std::vector<unsigned int>& stream)
{
unsigned int maxi = 0;
for (size_t i = 0; i < stream.size(); ++i)
maxi = std::max(maxi, stream[i]);
// save 16-bit indices if we can; note that we can't use restart index (65535)
if (maxi < 65535)
{
for (size_t i = 0; i < stream.size(); ++i)
{
uint16_t v[1] = {uint16_t(stream[i])};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_16u, false, 2};
return format;
}
else
{
for (size_t i = 0; i < stream.size(); ++i)
{
uint32_t v[1] = {stream[i]};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32u, false, 4};
return format;
}
}
StreamFormat writeTimeStream(std::string& bin, const std::vector<float>& data)
{
for (size_t i = 0; i < data.size(); ++i)
{
float v[1] = {data[i]};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4};
return format;
}
StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector<Attr>& data, const Settings& settings, bool has_tangents)
{
if (type == cgltf_animation_path_type_rotation)
{
StreamFormat::Filter filter = settings.compressmore && !has_tangents ? StreamFormat::Filter_Quat : StreamFormat::Filter_None;
size_t offset = bin.size();
size_t stride = 8;
bin.resize(bin.size() + data.size() * stride);
if (filter == StreamFormat::Filter_Quat)
meshopt_encodeFilterQuat(&bin[offset], data.size(), stride, settings.rot_bits, data[0].f);
else
encodeSnorm(&bin[offset], data.size(), stride, 16, data[0].f);
StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16, true, 8, filter};
return format;
}
else if (type == cgltf_animation_path_type_weights)
{
for (size_t i = 0; i < data.size(); ++i)
{
const Attr& a = data[i];
uint8_t v[1] = {uint8_t(meshopt_quantizeUnorm(a.f[0], 8))};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_8u, true, 1};
return format;
}
else if (type == cgltf_animation_path_type_translation || type == cgltf_animation_path_type_scale)
{
StreamFormat::Filter filter = settings.compressmore ? StreamFormat::Filter_Exp : StreamFormat::Filter_None;
int bits = (type == cgltf_animation_path_type_translation) ? settings.trn_bits : settings.scl_bits;
size_t offset = bin.size();
for (size_t i = 0; i < data.size(); ++i)
{
const Attr& a = data[i];
float v[3] = {a.f[0], a.f[1], a.f[2]};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
if (filter == StreamFormat::Filter_Exp)
meshopt_encodeFilterExp(&bin[offset], data.size(), 12, bits, reinterpret_cast<const float*>(&bin[offset]), meshopt_EncodeExpSharedVector);
StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_32f, false, 12, filter};
return format;
}
else
{
for (size_t i = 0; i < data.size(); ++i)
{
const Attr& a = data[i];
float v[4] = {a.f[0], a.f[1], a.f[2], a.f[3]};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_32f, false, 16};
return format;
}
}
void compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride, int level)
{
assert(data.size() == count * stride);
std::vector<unsigned char> compressed(meshopt_encodeVertexBufferBound(count, stride));
size_t size = meshopt_encodeVertexBufferLevel(&compressed[0], compressed.size(), data.c_str(), count, stride, level, level > 0);
bin.append(reinterpret_cast<const char*>(&compressed[0]), size);
}
void compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride)
{
assert(stride == 2 || stride == 4);
assert(data.size() == count * stride);
assert(count % 3 == 0);
std::vector<unsigned char> compressed(meshopt_encodeIndexBufferBound(count, count));
size_t size = 0;
if (stride == 2)
size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast<const uint16_t*>(data.c_str()), count);
else
size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast<const uint32_t*>(data.c_str()), count);
bin.append(reinterpret_cast<const char*>(&compressed[0]), size);
}
void compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride)
{
assert(stride == 2 || stride == 4);
assert(data.size() == count * stride);
std::vector<unsigned char> compressed(meshopt_encodeIndexSequenceBound(count, count));
size_t size = 0;
if (stride == 2)
size = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast<const uint16_t*>(data.c_str()), count);
else
size = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast<const uint32_t*>(data.c_str()), count);
bin.append(reinterpret_cast<const char*>(&compressed[0]), size);
}
+45
View File
@@ -0,0 +1,45 @@
#ifdef __wasi__
#include <stdlib.h>
#include <string.h>
#include <wasi/api.h>
extern "C" void __cxa_throw(void* ptr, void* type, void* destructor)
{
abort();
}
extern "C" void* __cxa_allocate_exception(size_t thrown_size)
{
abort();
}
extern "C" int32_t __wasi_path_open32(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8)
__attribute__((
__import_module__("wasi_snapshot_preview1"),
__import_name__("path_open32"),
__warn_unused_result__));
extern "C" int32_t __imported_wasi_snapshot_preview1_path_open(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int64_t arg5, int64_t arg6, int32_t arg7, int32_t arg8)
{
return __wasi_path_open32(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
}
extern "C" int32_t __wasi_fd_seek32(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3)
__attribute__((
__import_module__("wasi_snapshot_preview1"),
__import_name__("fd_seek32"),
__warn_unused_result__));
extern "C" int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3)
{
*(uint64_t*)arg3 = 0;
return __wasi_fd_seek32(arg0, arg1, arg2, arg3);
}
extern "C" int32_t __imported_wasi_snapshot_preview1_clock_time_get(int32_t arg0, int64_t arg1, int32_t arg2)
{
return __WASI_ERRNO_NOSYS;
}
#endif
+2
View File
@@ -0,0 +1,2 @@
__wasi_path_open32
__wasi_fd_seek32
File diff suppressed because it is too large Load Diff