Skip to content

Commit ec1c337

Browse files
bkaradzic-microsoftbkaradzicCopilot
authored
Add native Draco and meshopt codec plugins (#1797)
Adds native C++ implementations of the Draco and meshopt mesh decoders as two new optional plugins, `NativeDraco` and `NativeMeshopt`. Today Babylon Native runs these codecs through the same WASM modules the web uses. That pulls a WASM runtime into a native app purely to decompress meshes, costs a module instantiation per session, and copies buffers across the JS/WASM boundary. These plugins let the native host do the work directly. ### What's here | Plugin | Exposes | Replaces | | --- | --- | --- | | `NativeDraco` | `_native.DracoCodec.Decode(data, attributeIds?)` | `draco_decoder_gltf.wasm` | | `NativeDraco` | `_native.DracoCodec.Version` | — | | `NativeMeshopt` | `_native.MeshoptCodec.Decode(source, count, stride, mode, filter?)` | `meshopt_decoder.wasm` | | `NativeMeshopt` | `_native.MeshoptCodec.Version` | — | Each codec is a single object rather than a set of free functions, so the JavaScript side needs one feature probe per codec instead of one per entry point, and so the object can publish the codec version compiled into the binary. Version matters because meshoptimizer records its codec version in the first header byte and a decoder refuses streams newer than it understands; a bare function name gives a caller no way to find that out except by failing. `DracoCodec.Decode` supports both call shapes the Babylon loaders use: the glTF path, where the caller passes the `EXT/KHR_draco_mesh_compression` map of Babylon vertex-buffer kind to Draco unique id, and the standalone `.drc` path, where no map is supplied and the plugin probes the standard named attributes. Attribute data is de-interleaved and tightly packed per point, mirroring emscripten's `GetAttributeDataArrayForAllPoints` that the WASM decoder relies on, so the returned buffers are drop-in equivalents. ### Dependencies Two new `FetchContent` dependencies, both pinned to release tags: - `google/draco` @ `1.5.7` - `zeux/meshoptimizer` @ `v0.22` Both are declared `EXCLUDE_FROM_ALL` and built with tests, install, executables and JS glue disabled. draco adds its include directories as `PRIVATE`, so `Dependencies/CMakeLists.txt` re-exports them as `PUBLIC` on the combined target; the target is named `draco` under MSVC and `draco_static` elsewhere and both spellings are handled. draco is additionally built with `DRACO_GLTF_BITSTREAM=ON`, which compiles only mesh compression, normal encoding and the standard edgebreaker. That is the same subset `draco_decoder_gltf.wasm` is built with, and that module is what Babylon.js's default configuration loads, so this costs no capability the JavaScript path has. It drops point cloud compression, the predictive edgebreaker and pre-glTF backwards compatibility; point cloud streams return a Draco error instead of decoding, matching Babylon.js. ### Footprint, and why both plugins default to `OFF` Both options default to `OFF`. Nothing routes to these entry points until the Babylon.js side lands, so until then no consumer should be paying for them. Measured on Playground, Win32 x64, RelWithDebInfo, same tree, only the two options changed: | configuration | `Playground.exe` | delta | | --- | ---: | ---: | | both `OFF` (default) | 11,100,672 B | baseline | | `NativeMeshopt` only | 11,132,928 B | +31.5 KB | | both `ON` | 11,734,528 B | +619 KB (+5.7%) | meshoptimizer is nearly free; draco is +587 KB. Draco started at +1.64 MB. Two things brought it down, both of which track Babylon.js more closely rather than less: - **No encoder.** Babylon.js does not bundle one either — `DracoDecoder` loads `draco_decoder_gltf.wasm` and `DracoEncoder` fetches a separate `draco_encoder.wasm` on demand. Linking the encoder here would cost every binary ~1.2 MB to serve an authoring path Babylon Native does not exercise. - **`DRACO_GLTF_BITSTREAM`**, as described above. Together that is a 65% reduction in draco's contribution, which is close enough to what a minidraco fork would buy that staying on the upstream bitstream looks like the better trade. ### Trust boundary Worth stating plainly, because it cuts both ways: - On **V8** and **JavaScriptCore**, this moves decoding of untrusted mesh data out of the WASM sandbox and into the host process. - On **QuickJS** and **Hermes** there is no WebAssembly at all, so this is net-new capability rather than a relocation of existing capability. - It removes a runtime fetch of executable code from a CDN, since no `.wasm` ships in this tree. That is the reason the input-validation and malformed/truncated-input tests below exist, and why the codecs validate every caller-supplied extent rather than trusting the calling loader. ### Scope The change is **additive**. Nothing existing is modified except the CMake wiring that registers the new plugins, four lines in `Embedding/Source/Runtime.cpp` that initialize them, the `UnitTests` wiring for the new coverage, and the three CI workflows that enable the plugins for the jobs which run `UnitTests`. No existing plugin, engine path, or test configuration is touched, and `Apps/Playground/Scripts/config.json` is deliberately left alone. ### On validation These entry points are not reachable from the currently pinned `babylonjs` npm package (9.15.0), so the plugins are inert until the corresponding Babylon.js side lands and the loaders start feature-detecting them. The validation suite therefore cannot exercise this code, but `Apps/UnitTests` can, and now does. Both plugins are linked and initialized in `Apps/UnitTests` behind their options, and `tests.javaScript.all.ts` carries a describe block for each — 16 cases covering decoding and malformed, truncated and out-of-range input for every entry point. The blocks report as **skipped** when the build did not opt in, rather than passing vacuously; CI enables both options for the Linux, macOS and Win32 jobs that run `UnitTests`, including the sanitizer configurations. Neither codec's positive test is self-referential. Since neither plugin exposes an encoder, both fixtures come from the reference JavaScript encoders, which pins these decoders to the upstream bitstreams rather than to themselves: - **Draco** — two fixtures from `draco3dgltf` **1.5.7**, the same package Babylon.js takes its decoder from. One is a two-triangle mesh; the other is a 63-vertex sphere carrying POSITION, NORMAL and TEX_COORD, which covers multi-attribute decoding and a non-degenerate edgebreaker traversal — the parts `DRACO_GLTF_BITSTREAM` actually restricts. - **meshopt** — a stream from the reference meshoptimizer **0.22.0** encoder, with the test asserting the native decoder reproduces the original bytes exactly. The sphere fixture is worth a note. It decodes to **62** vertices and 91 triangles, not the 63 and 96 that were encoded: Draco merges points whose attributes all match and drops the degenerate triangles at the poles. Rather than adjust the expected numbers to whatever this decoder produced, they were taken from the reference `draco3dgltf` decoder run over the same buffer, which reports 62 points and 91 faces. This decoder reproduces that exactly. meshoptimizer's vertex codec is versioned in the first header byte. Encoders from 0.23.0 onward emit format version 1 (`0xa1`), which a v0.22 decoder correctly rejects with `-1`, so the encoder used to produce the fixture was version-matched deliberately. That version is now also pinned at compile time with a `static_assert`, because bgfx vendors its own copy of meshoptimizer and the dependency guard skips our `FetchContent` when a target of that name already exists — a mismatch there would surface as silent data corruption rather than a build break. - **No regression.** Full validation suite on this branch: `ran=295 passed=295 failed=0 missingRef=0 skipped=420`, identical to the upstream `master` baseline. (Indices 53–57 are excluded from the run: they crash in `RendererContextD3D11::submit` on pristine `master` too.) - **Unit tests.** `JavaScript.All` — 42 passing, 0 failing. ### Note for the Babylon.js side `dracoEncoder.ts` on the Babylon.js side feature-detects `_native.encodeDracoMesh`. With no native encoder that probe is simply false and it falls back exactly as it does today, so nothing breaks — but the paired Babylon.js change should drop that hunk rather than ship a probe that can never succeed. If the capability is wanted, the encoder can come back behind an off-by-default option. --------- Co-authored-by: bkaradzic-microsoft <bkaradzic@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae
1 parent 84e54fc commit ec1c337

24 files changed

Lines changed: 1165 additions & 3 deletions

File tree

.github/workflows/build-android.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,6 @@ jobs:
4747
-PjsEngine=${{ inputs.js-engine }} \
4848
-PARM64Only \
4949
-PNDK_VERSION=${{ env.NDK_VERSION }} \
50+
-PBABYLON_NATIVE_PLUGIN_NATIVEDRACO=ON \
51+
-PBABYLON_NATIVE_PLUGIN_NATIVEMESHOPT=ON \
5052
-PSANITIZERS=OFF

.github/workflows/build-ios.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ jobs:
3737
-D IOS=ON \
3838
-D DEPLOYMENT_TARGET=${{ inputs.deployment-target }} \
3939
-D BABYLON_DEBUG_TRACE=ON \
40+
-D BABYLON_NATIVE_PLUGIN_NATIVEDRACO=ON \
41+
-D BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT=ON \
4042
-D CMAKE_IOS_INSTALL_COMBINED=NO
4143
4244
- name: Build Playground iOS

.github/workflows/build-linux.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ jobs:
4949
-D BX_CONFIG_DEBUG=ON \
5050
-D OpenGL_GL_PREFERENCE=GLVND \
5151
-D BABYLON_DEBUG_TRACE=ON \
52+
-D BABYLON_NATIVE_PLUGIN_NATIVEDRACO=ON \
53+
-D BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT=ON \
5254
${{ inputs.js-engine == 'Hermes' && '-D HERMES_UNICODE_LITE=ON -D HERMES_ALLOW_BOOST_CONTEXT=0' || '' }} \
5355
-D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} .
5456
ninja -C build/Linux

.github/workflows/build-macos.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ jobs:
4747
cmake -G "${{ inputs.generator }}" -B build/macOS \
4848
${{ inputs.js-engine != '' && format('-D NAPI_JAVASCRIPT_ENGINE={0}', inputs.js-engine) || '' }} \
4949
-D BABYLON_DEBUG_TRACE=ON \
50+
-D BABYLON_NATIVE_PLUGIN_NATIVEDRACO=ON \
51+
-D BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT=ON \
5052
-D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} \
5153
-D BABYLON_NATIVE_TESTS_USE_NOOP_METAL_DEVICE=ON
5254

.github/workflows/build-uwp.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
-D CMAKE_SYSTEM_VERSION=10.0 ^
4646
${{ steps.napi.outputs.define }} ^
4747
-A ${{ inputs.platform }} ^
48+
-D BABYLON_NATIVE_PLUGIN_NATIVEDRACO=ON ^
49+
-D BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT=ON ^
4850
-D BABYLON_DEBUG_TRACE=ON
4951
5052
- name: Build UWP

.github/workflows/build-win32.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ jobs:
6767
-D GRAPHICS_API=${{ inputs.graphics-api }} ^
6868
-D BGFX_CONFIG_MAX_FRAME_BUFFERS=256 ^
6969
-D BABYLON_DEBUG_TRACE=ON ^
70+
-D BABYLON_NATIVE_PLUGIN_NATIVEDRACO=ON ^
71+
-D BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT=ON ^
7072
-D ENABLE_SANITIZERS=${{ steps.vars.outputs.sanitizer_flag }}
7173
7274
- name: Build

Apps/Playground/Android/BabylonNative/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ if (project.hasProperty("importHostCompilers")) {
3737
cmakeArguments.add("-DIMPORT_HOST_COMPILERS=${project.property('importHostCompilers')}")
3838
}
3939

40+
// The experimental codec plugins default to OFF in CMake. Allow the caller (CI) to
41+
// turn them on so the NDK toolchain gets build coverage for their third-party
42+
// dependencies, without changing the default for local Android builds.
43+
["BABYLON_NATIVE_PLUGIN_NATIVEDRACO", "BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT"].each { option ->
44+
if (project.hasProperty(option)) {
45+
cmakeArguments.add("-D${option}=${project.property(option)}")
46+
}
47+
}
48+
4049
configurations { natives }
4150

4251
android {

Apps/UnitTests/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ target_link_libraries(UnitTests
8585

8686
target_compile_definitions(UnitTests PRIVATE ${ADDITIONAL_COMPILE_DEFINITIONS})
8787

88+
# NativeDraco and NativeMeshopt default to OFF, so link and exercise them only when the
89+
# consuming build opted in. CI turns both on for the jobs that run UnitTests.
90+
if(BABYLON_NATIVE_PLUGIN_NATIVEDRACO)
91+
target_link_libraries(UnitTests PRIVATE NativeDraco)
92+
target_compile_definitions(UnitTests PRIVATE HAS_NATIVE_DRACO)
93+
endif()
94+
95+
if(BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT)
96+
target_link_libraries(UnitTests PRIVATE NativeMeshopt)
97+
target_compile_definitions(UnitTests PRIVATE HAS_NATIVE_MESHOPT)
98+
endif()
99+
88100
if(GRAPHICS_API STREQUAL "D3D12")
89101
target_compile_definitions(UnitTests PRIVATE SKIP_RENDER_TESTS)
90102
endif()

Apps/UnitTests/JavaScript/dist/tests.javaScript.all.js

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28247,7 +28247,7 @@ __webpack_require__.r(__webpack_exports__);
2824728247
/* harmony import */ var chai__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! chai */ "../../node_modules/chai/index.js");
2824828248
/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @babylonjs/materials */ "@babylonjs/core");
2824928249
/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_babylonjs_core__WEBPACK_IMPORTED_MODULE_4__);
28250-
28250+
function _createForOfIteratorHelper(r, e) {var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];if (!t) {if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {t && (r = t);var _n = 0,F = function F() {};return { s: F, n: function n() {return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] };}, e: function e(r) {throw r;}, f: F };}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");}var o,a = !0,u = !1;return { s: function s() {t = t.call(r);}, n: function n() {var r = t.next();return a = r.done, r;}, e: function e(r) {u = !0, o = r;}, f: function f() {try {a || null == t.return || t.return();} finally {if (u) throw o;}} };}function _unsupportedIterableToArray(r, a) {if (r) {if ("string" == typeof r) return _arrayLikeToArray(r, a);var t = {}.toString.call(r).slice(8, -1);return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;}}function _arrayLikeToArray(r, a) {(null == a || a > r.length) && (a = r.length);for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];return n;}
2825128251

2825228252

2825328253

@@ -28559,6 +28559,223 @@ describe("NativeEncoding", function () {
2855928559
);
2856028560
});
2856128561

28562+
function hexToBytes(hex) {
28563+
var out = new Uint8Array(hex.length / 2);
28564+
for (var i = 0; i < out.length; ++i) {
28565+
out[i] = parseInt(hex.substr(i * 2, 2), 16);
28566+
}
28567+
return out;
28568+
}
28569+
28570+
// Both plugins default to OFF, so report them as skipped rather than silently passing
28571+
// when the build did not opt in. CI enables both for the jobs that run UnitTests.
28572+
(typeof _native.DracoCodec !== "undefined" ? describe : describe.skip)("NativeDraco", function () {
28573+
this.timeout(0);
28574+
28575+
// Two triangles sharing an edge. Values are exact halves so they survive the float32
28576+
// round trip bit-for-bit once quantization is disabled.
28577+
var positions = new Float32Array([
28578+
0, 0, 0,
28579+
1, 0, 0,
28580+
0, 1, 0,
28581+
1, 1, 0]
28582+
);
28583+
var indices = new Uint16Array([0, 1, 2, 1, 3, 2]);
28584+
28585+
// Encoded by the reference draco3dgltf 1.5.7 encoder (the same package Babylon.js takes
28586+
// its decoder from), standard edgebreaker, 14-bit position quantization. Using a fixture
28587+
// from the reference encoder rather than our own output pins this decoder to the upstream
28588+
// bitstream instead of to itself.
28589+
var ENCODED = hexToBytes(
28590+
"445241434f02020101000000040200020000011fff011101ff00000100090300000201010100030301300110030024824a0400000000ff3f00000000000000000000000000000000803f0e");
28591+
var POSITION_ATTRIBUTE_ID = 0;
28592+
28593+
// A 63-vertex UV sphere carrying POSITION, NORMAL and TEX_COORD, encoded by the same
28594+
// reference encoder with per-attribute quantization. The single-triangle-pair fixture
28595+
// above cannot exercise multi-attribute decoding or a non-degenerate edgebreaker
28596+
// traversal, which are what the glTF-bitstream-only build actually restricts.
28597+
//
28598+
// The expected counts below are what the reference draco3dgltf decoder reports for this
28599+
// exact buffer, not the pre-encode mesh: Draco merges points whose attributes all match
28600+
// and drops the degenerate triangles at the poles, so 63 vertices / 96 triangles going
28601+
// in becomes 62 vertices / 91 triangles coming out.
28602+
var SPHERE = hexToBytes(
28603+
"445241434f020201010000003a5b025b05001a5fd73e55ad3e55d5aa3e5555adaa3ea55455559faaaaaa565501ff0111" +
28604+
"ff02694af8058097a3755f03ff0000000000010100010009030000020101090300010301030902000202010101000f2b" +
28605+
"a106b907592e51030c141534f4dfc29a78ddaf7f80bed2ffff2bad28fedf4fcb03010030a7577c44104633030047e86d" +
28606+
"00c02304008f08128a7a003c181d05009108c20010d48301102848f888fa0fdc030090d02b010050db9591d895901000" +
28607+
"749b07f388bca224060084f91f49f408cd0c0088ee9110006466480800ba0d840110a80703405010e61175c5500b00ba" +
28608+
"12a805005dc92b23d12b9dc1b6fbb400e0113dc2480c8007eb8a9208c2b43d000009bd1200f8b4a016007425510b00bc" +
28609+
"525702009fd62b010050eb950040a8ed4a0040d48a5a00d095422d00f04aa0160078a54f0b00ba92570200a2b62b0180" +
28610+
"50eb0cb89da8ed3600e0110200490409030580074762004098a00000e1aef8881e8cb7010090b0ed018047f8081f1100" +
28611+
"80b0001075ea0e00cc0c00000000ff3f0000000080bf000080bf000080bf000000400e000301000b036904135103f109" +
28612+
"a106b9270e3ecb87a50ecead84ceeaa8bdde65000002dc7542d24fdbb29af35e882de5dab6c48d3dd1bbeceebaec45c6" +
28613+
"73b0bb6b772244eb067057e873dc4e3d72c57b21b6539244f9b4fe44ef620ee3d4cf9108b00b40340076034034000000" +
28614+
"44130dc01c19673cc72e4413bd8b398c673cc72e00d10010057042d294b60000fc0fd127b6b63cbd9acf91f1ffff9492" +
28615+
"621f37ff030000ff0100000a010101000d039532550a1b0901090109010a0eb9fbab2e93abef3f8700fcff00aceaff00" +
28616+
"60a0001a8220220800400800820822089a0000000000ff0f000000000000000000000000803f0c");
28617+
var SPHERE_VERTICES = 62;
28618+
var SPHERE_INDICES = 273;
28619+
28620+
it("publishes the codec version it was built against", function () {
28621+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(_native.DracoCodec.Version).to.be.a("string");
28622+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(_native.DracoCodec.Version).to.match(/^\d+\.\d+\.\d+$/);
28623+
});
28624+
28625+
it("decodes a mesh produced by the reference glTF encoder", function () {
28626+
var decoded = _native.DracoCodec.Decode(ENCODED, { position: POSITION_ATTRIBUTE_ID });
28627+
28628+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.totalVertices).to.equal(positions.length / 3);
28629+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.indices.length).to.equal(indices.length);
28630+
28631+
// Draco reorders points, so compare the triangles as sets of resolved corner
28632+
// positions rather than assuming the original vertex order survived. Rounded to
28633+
// two decimals so the comparison tolerates quantization but still separates
28634+
// coordinates that are a whole unit apart.
28635+
var attribute = decoded.attributes.find(function (a) {return a.kind === "position";});
28636+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(attribute, "decoded position attribute").to.not.equal(undefined);
28637+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(attribute.size).to.equal(3);
28638+
28639+
var corner = function corner(buffer, i) {return (
28640+
[buffer[i * 3], buffer[i * 3 + 1], buffer[i * 3 + 2]].
28641+
map(function (v) {return v.toFixed(2);}).
28642+
join(","));};
28643+
28644+
var expectedCorners = [];
28645+
var actualCorners = [];
28646+
for (var i = 0; i < indices.length; ++i) {
28647+
expectedCorners.push(corner(positions, indices[i]));
28648+
actualCorners.push(corner(attribute.data, decoded.indices[i]));
28649+
}
28650+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(actualCorners.sort()).to.deep.equal(expectedCorners.sort());
28651+
});
28652+
28653+
it("decodes without an explicit attribute id map", function () {
28654+
var decoded = _native.DracoCodec.Decode(ENCODED);
28655+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.totalVertices).to.equal(positions.length / 3);
28656+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.attributes.find(function (a) {return a.kind === "position";})).to.not.equal(undefined);
28657+
});
28658+
28659+
it("decodes a multi-attribute mesh", function () {
28660+
var decoded = _native.DracoCodec.Decode(SPHERE, { position: 0, normal: 1, uv: 2 });
28661+
28662+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.totalVertices).to.equal(SPHERE_VERTICES);
28663+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.indices.length).to.equal(SPHERE_INDICES);
28664+
28665+
var byKind = {};var _iterator = _createForOfIteratorHelper(
28666+
decoded.attributes),_step;try {for (_iterator.s(); !(_step = _iterator.n()).done;) {var a = _step.value;
28667+
byKind[a.kind] = a;
28668+
}} catch (err) {_iterator.e(err);} finally {_iterator.f();}
28669+
28670+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(byKind.position.size).to.equal(3);
28671+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(byKind.normal.size).to.equal(3);
28672+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(byKind.uv.size).to.equal(2);
28673+
28674+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(byKind.position.data.length).to.equal(SPHERE_VERTICES * 3);
28675+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(byKind.normal.data.length).to.equal(SPHERE_VERTICES * 3);
28676+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(byKind.uv.data.length).to.equal(SPHERE_VERTICES * 2);
28677+
28678+
// Every index must address a real vertex, and the geometry must actually be the
28679+
// unit sphere that was encoded rather than plausible-looking noise.
28680+
for (var i = 0; i < decoded.indices.length; ++i) {
28681+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.indices[i]).to.be.lessThan(SPHERE_VERTICES);
28682+
}
28683+
28684+
for (var v = 0; v < SPHERE_VERTICES; ++v) {
28685+
var x = byKind.position.data[v * 3];
28686+
var y = byKind.position.data[v * 3 + 1];
28687+
var z = byKind.position.data[v * 3 + 2];
28688+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(Math.sqrt(x * x + y * y + z * z)).to.be.closeTo(1, 0.01);
28689+
}
28690+
});
28691+
28692+
it("rejects malformed input", function () {
28693+
var garbage = new Uint8Array(64);
28694+
for (var i = 0; i < garbage.length; ++i) {
28695+
garbage[i] = i * 37 & 0xff;
28696+
}
28697+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.DracoCodec.Decode(garbage);}).to.throw();
28698+
});
28699+
28700+
it("rejects truncated input", function () {
28701+
var truncated = ENCODED.slice(0, Math.floor(ENCODED.length / 2));
28702+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.DracoCodec.Decode(truncated);}).to.throw();
28703+
});
28704+
28705+
it("rejects an empty buffer", function () {
28706+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.DracoCodec.Decode(new Uint8Array(0));}).to.throw();
28707+
});
28708+
28709+
it("does not expose an encoder", function () {
28710+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(_native.DracoCodec.Encode).to.equal(undefined);
28711+
});
28712+
});
28713+
28714+
(typeof _native.MeshoptCodec !== "undefined" ? describe : describe.skip)("NativeMeshopt", function () {
28715+
this.timeout(0);
28716+
28717+
// Produced by the reference meshoptimizer 0.22 JavaScript encoder
28718+
// (MeshoptEncoder.encodeVertexBuffer) over 6 vertices of 16-byte stride, so this
28719+
// pins our native decoder against the upstream bitstream rather than against itself.
28720+
var ENCODED = hexToBytes(
28721+
"a00000013ff000007fffa0606001380000007e0000013ff0000020ff9070480130800000800000013ff0000080ff" +
28722+
"a0606001320000007e012aa000000000000000000000000000000000000000000000000000000000800000000000beadde");
28723+
var EXPECTED = hexToBytes(
28724+
"00000000000000800000000000beadde0000c03f000010c00000403f01beadde00004040000090c00000c03f02beadde" +
28725+
"000090400000d8c00000104003beadde0000c040000010c10000404004beadde0000f040000034c10000704005beadde");
28726+
var COUNT = 6;
28727+
var STRIDE = 16;
28728+
28729+
it("publishes the codec version it was built against", function () {
28730+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(_native.MeshoptCodec.Version).to.be.a("string");
28731+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(_native.MeshoptCodec.Version).to.match(/^\d+\.\d+$/);
28732+
});
28733+
28734+
it("decodes a reference stream byte for byte", function () {
28735+
var decoded = _native.MeshoptCodec.Decode(ENCODED, COUNT, STRIDE, "ATTRIBUTES");
28736+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(decoded.length).to.equal(EXPECTED.length);
28737+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(Array.from(decoded)).to.deep.equal(Array.from(EXPECTED));
28738+
});
28739+
28740+
it("rejects malformed input", function () {
28741+
var garbage = new Uint8Array(ENCODED.length);
28742+
for (var i = 0; i < garbage.length; ++i) {
28743+
garbage[i] = i * 37 & 0xff;
28744+
}
28745+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(garbage, COUNT, STRIDE, "ATTRIBUTES");}).to.throw();
28746+
});
28747+
28748+
it("rejects truncated input", function () {
28749+
var truncated = ENCODED.slice(0, Math.floor(ENCODED.length / 2));
28750+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(truncated, COUNT, STRIDE, "ATTRIBUTES");}).to.throw();
28751+
});
28752+
28753+
it("rejects an unknown mode", function () {
28754+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(ENCODED, COUNT, STRIDE, "NOT_A_MODE");}).to.throw();
28755+
});
28756+
28757+
it("rejects a stride outside [1, 256]", function () {
28758+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(ENCODED, COUNT, 0, "ATTRIBUTES");}).to.throw();
28759+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(ENCODED, COUNT, 257, "ATTRIBUTES");}).to.throw();
28760+
});
28761+
28762+
it("rejects an ATTRIBUTES stride that is not a multiple of 4", function () {
28763+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(ENCODED, COUNT, 6, "ATTRIBUTES");}).to.throw();
28764+
});
28765+
28766+
it("rejects a negative count", function () {
28767+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(ENCODED, -1, STRIDE, "ATTRIBUTES");}).to.throw();
28768+
});
28769+
28770+
it("rejects a TRIANGLES count that is not a multiple of 3", function () {
28771+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(ENCODED, 4, 2, "TRIANGLES");}).to.throw();
28772+
});
28773+
28774+
it("rejects a non-typed-array source", function () {
28775+
(0,chai__WEBPACK_IMPORTED_MODULE_3__.expect)(function () {return _native.MeshoptCodec.Decode(null, COUNT, STRIDE, "ATTRIBUTES");}).to.throw();
28776+
});
28777+
});
28778+
2856228779
mocha.run(function (failures) {
2856328780
// Test program will wait for code to be set before exiting
2856428781
if (failures > 0) {

0 commit comments

Comments
 (0)