@forgeax/engine-codec 0.0.0-dev.8d955ade1c79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +261 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/basis-encode-fast-preset.unit.test.d.ts +2 -0
- package/dist/__tests__/basis-encode-fast-preset.unit.test.d.ts.map +1 -0
- package/dist/__tests__/basis-encode-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/basis-encode-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/basis-transcoder.unit.test.d.ts +2 -0
- package/dist/__tests__/basis-transcoder.unit.test.d.ts.map +1 -0
- package/dist/__tests__/block-format.unit.test.d.ts +2 -0
- package/dist/__tests__/block-format.unit.test.d.ts.map +1 -0
- package/dist/__tests__/byte-identical-falsification.test.d.ts +2 -0
- package/dist/__tests__/byte-identical-falsification.test.d.ts.map +1 -0
- package/dist/__tests__/codec-errors.test-d.d.ts +2 -0
- package/dist/__tests__/codec-errors.test-d.d.ts.map +1 -0
- package/dist/__tests__/codec-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/codec-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/compression-field.unit.test.d.ts +19 -0
- package/dist/__tests__/compression-field.unit.test.d.ts.map +1 -0
- package/dist/__tests__/encode-pixel-limit.unit.test.d.ts +2 -0
- package/dist/__tests__/encode-pixel-limit.unit.test.d.ts.map +1 -0
- package/dist/__tests__/encode-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/encode-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-basis-capability-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/ktx2-basis-capability-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-errors.test.d.ts +2 -0
- package/dist/__tests__/ktx2-errors.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-parse-scheme0.test.d.ts +2 -0
- package/dist/__tests__/ktx2-parse-scheme0.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-parse-scheme2.test.d.ts +2 -0
- package/dist/__tests__/ktx2-parse-scheme2.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-scheme1-and-transcode-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/ktx2-scheme1-and-transcode-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-to-texture.integration.test.d.ts +2 -0
- package/dist/__tests__/ktx2-to-texture.integration.test.d.ts.map +1 -0
- package/dist/__tests__/transcode-select-target.unit.test.d.ts +2 -0
- package/dist/__tests__/transcode-select-target.unit.test.d.ts.map +1 -0
- package/dist/__tests__/wasm-init-smoke.test.d.ts +2 -0
- package/dist/__tests__/wasm-init-smoke.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-deterministic.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-deterministic.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-encode-singleton.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-encode-singleton.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-singleton.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-singleton.unit.test.d.ts.map +1 -0
- package/dist/basis-transcoder.d.ts +86 -0
- package/dist/basis-transcoder.d.ts.map +1 -0
- package/dist/block-format.d.ts +41 -0
- package/dist/block-format.d.ts.map +1 -0
- package/dist/encode/basis-encode.d.ts +68 -0
- package/dist/encode/basis-encode.d.ts.map +1 -0
- package/dist/encode/index.d.ts +10 -0
- package/dist/encode/index.d.ts.map +1 -0
- package/dist/encode/index.mjs +207 -0
- package/dist/encode/index.mjs.map +1 -0
- package/dist/encode-impl.d.ts +30 -0
- package/dist/encode-impl.d.ts.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +610 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ktx2.d.ts +111 -0
- package/dist/ktx2.d.ts.map +1 -0
- package/dist/transcode.d.ts +55 -0
- package/dist/transcode.d.ts.map +1 -0
- package/dist/wasm/basis-types.d.ts +179 -0
- package/dist/wasm/basis-types.d.ts.map +1 -0
- package/dist/zstd.d.ts +28 -0
- package/dist/zstd.d.ts.map +1 -0
- package/package.json +71 -0
- package/pkg/basis_transcoder.mjs +2 -0
- package/pkg/basis_transcoder.wasm +0 -0
- package/pkg/encode/basis_encoder.mjs +2 -0
- package/pkg/encode/basis_encoder.wasm +0 -0
- package/scripts/build-wasm.mjs +370 -0
- package/scripts/content-key.mjs +85 -0
- package/scripts/ensure-wasm.mjs +38 -0
- package/scripts/fetch-basis.mjs +150 -0
- package/scripts/fetch-wasm.mjs +97 -0
- package/src/__tests__/basis-encode-fast-preset.unit.test.ts +204 -0
- package/src/__tests__/basis-encode-lifecycle.unit.test.ts +249 -0
- package/src/__tests__/basis-transcoder.unit.test.ts +151 -0
- package/src/__tests__/block-format.unit.test.ts +215 -0
- package/src/__tests__/byte-identical-falsification.test.ts +164 -0
- package/src/__tests__/codec-errors.test-d.ts +67 -0
- package/src/__tests__/codec-errors.unit.test.ts +128 -0
- package/src/__tests__/compression-field.unit.test.ts +245 -0
- package/src/__tests__/encode-pixel-limit.unit.test.ts +117 -0
- package/src/__tests__/encode-roundtrip.unit.test.ts +239 -0
- package/src/__tests__/ktx2-basis-capability-matrix.unit.test.ts +241 -0
- package/src/__tests__/ktx2-errors.test.ts +239 -0
- package/src/__tests__/ktx2-parse-scheme0.test.ts +263 -0
- package/src/__tests__/ktx2-parse-scheme2.test.ts +220 -0
- package/src/__tests__/ktx2-scheme1-and-transcode-errors.unit.test.ts +178 -0
- package/src/__tests__/ktx2-to-texture.integration.test.ts +384 -0
- package/src/__tests__/transcode-select-target.unit.test.ts +167 -0
- package/src/__tests__/wasm-init-smoke.test.ts +102 -0
- package/src/__tests__/zstd-deterministic.unit.test.ts +49 -0
- package/src/__tests__/zstd-encode-singleton.unit.test.ts +63 -0
- package/src/__tests__/zstd-roundtrip.unit.test.ts +87 -0
- package/src/__tests__/zstd-singleton.unit.test.ts +105 -0
- package/src/basis-transcoder.ts +315 -0
- package/src/block-format.ts +128 -0
- package/src/encode/basis-encode.ts +264 -0
- package/src/encode/index.ts +19 -0
- package/src/encode-impl.ts +103 -0
- package/src/errors.ts +79 -0
- package/src/index.ts +38 -0
- package/src/ktx2.ts +469 -0
- package/src/transcode.ts +99 -0
- package/src/wasm/basis-types.ts +220 -0
- package/src/zstd.ts +85 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block-compressed texture format parameter table (D-6 shared SSOT).
|
|
3
|
+
*
|
|
4
|
+
* One table maps every WebGPU block-compressed `GPUTextureFormat` to its
|
|
5
|
+
* texel-block dimensions and compressed bytes-per-block. Both the texture
|
|
6
|
+
* upload path and the equirect upload path (M5) derive `bytesPerRow` /
|
|
7
|
+
* `rowsPerImage` from this single table -- block math is defined once
|
|
8
|
+
* (Derive, Don't Duplicate).
|
|
9
|
+
*
|
|
10
|
+
* Format list follows the WebGPU spec "Compressed Texture Formats" section.
|
|
11
|
+
* PVRTC and ASTC-HDR are intentionally absent (OOS-5 / not exposed by WebGPU
|
|
12
|
+
* core). Uncompressed formats are not in the table -- callers branch on
|
|
13
|
+
* `isCompressedFormat` first and handle the linear `width * bytesPerPixel`
|
|
14
|
+
* case themselves.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Texel-block dimensions + compressed size for one block-compressed format. */
|
|
18
|
+
export interface BlockParams {
|
|
19
|
+
readonly blockW: number;
|
|
20
|
+
readonly blockH: number;
|
|
21
|
+
readonly bytesPerBlock: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Block parameter table for every WebGPU block-compressed format.
|
|
26
|
+
*
|
|
27
|
+
* BC / ETC2 / EAC blocks are all 4x4 texels; ASTC block dimensions are encoded
|
|
28
|
+
* in the format name (`astc-<W>x<H>-...`) and every ASTC block is 16 bytes.
|
|
29
|
+
* bytesPerBlock: 8 for the 0.5-bpp formats (BC1, BC4, ETC2-rgb8, ETC2-rgb8a1,
|
|
30
|
+
* EAC-r11), 16 for the 1-bpp formats (BC2/3/5/6H/7, ETC2-rgba8, EAC-rg11, ASTC).
|
|
31
|
+
*/
|
|
32
|
+
const BLOCK_TABLE: ReadonlyMap<GPUTextureFormat, BlockParams> = new Map<
|
|
33
|
+
GPUTextureFormat,
|
|
34
|
+
BlockParams
|
|
35
|
+
>([
|
|
36
|
+
// -- BC (S3TC / RGTC / BPTC) --
|
|
37
|
+
['bc1-rgba-unorm', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
38
|
+
['bc1-rgba-unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
39
|
+
['bc2-rgba-unorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
40
|
+
['bc2-rgba-unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
41
|
+
['bc3-rgba-unorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
42
|
+
['bc3-rgba-unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
43
|
+
['bc4-r-unorm', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
44
|
+
['bc4-r-snorm', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
45
|
+
['bc5-rg-unorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
46
|
+
['bc5-rg-snorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
47
|
+
['bc6h-rgb-ufloat', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
48
|
+
['bc6h-rgb-float', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
49
|
+
['bc7-rgba-unorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
50
|
+
['bc7-rgba-unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
51
|
+
|
|
52
|
+
// -- ETC2 / EAC --
|
|
53
|
+
['etc2-rgb8unorm', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
54
|
+
['etc2-rgb8unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
55
|
+
['etc2-rgb8a1unorm', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
56
|
+
['etc2-rgb8a1unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
57
|
+
['etc2-rgba8unorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
58
|
+
['etc2-rgba8unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
59
|
+
['eac-r11unorm', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
60
|
+
['eac-r11snorm', { blockW: 4, blockH: 4, bytesPerBlock: 8 }],
|
|
61
|
+
['eac-rg11unorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
62
|
+
['eac-rg11snorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
63
|
+
|
|
64
|
+
// -- ASTC (all 16 bytes/block; block dims from the format name) --
|
|
65
|
+
['astc-4x4-unorm', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
66
|
+
['astc-4x4-unorm-srgb', { blockW: 4, blockH: 4, bytesPerBlock: 16 }],
|
|
67
|
+
['astc-5x4-unorm', { blockW: 5, blockH: 4, bytesPerBlock: 16 }],
|
|
68
|
+
['astc-5x4-unorm-srgb', { blockW: 5, blockH: 4, bytesPerBlock: 16 }],
|
|
69
|
+
['astc-5x5-unorm', { blockW: 5, blockH: 5, bytesPerBlock: 16 }],
|
|
70
|
+
['astc-5x5-unorm-srgb', { blockW: 5, blockH: 5, bytesPerBlock: 16 }],
|
|
71
|
+
['astc-6x5-unorm', { blockW: 6, blockH: 5, bytesPerBlock: 16 }],
|
|
72
|
+
['astc-6x5-unorm-srgb', { blockW: 6, blockH: 5, bytesPerBlock: 16 }],
|
|
73
|
+
['astc-6x6-unorm', { blockW: 6, blockH: 6, bytesPerBlock: 16 }],
|
|
74
|
+
['astc-6x6-unorm-srgb', { blockW: 6, blockH: 6, bytesPerBlock: 16 }],
|
|
75
|
+
['astc-8x5-unorm', { blockW: 8, blockH: 5, bytesPerBlock: 16 }],
|
|
76
|
+
['astc-8x5-unorm-srgb', { blockW: 8, blockH: 5, bytesPerBlock: 16 }],
|
|
77
|
+
['astc-8x6-unorm', { blockW: 8, blockH: 6, bytesPerBlock: 16 }],
|
|
78
|
+
['astc-8x6-unorm-srgb', { blockW: 8, blockH: 6, bytesPerBlock: 16 }],
|
|
79
|
+
['astc-8x8-unorm', { blockW: 8, blockH: 8, bytesPerBlock: 16 }],
|
|
80
|
+
['astc-8x8-unorm-srgb', { blockW: 8, blockH: 8, bytesPerBlock: 16 }],
|
|
81
|
+
['astc-10x5-unorm', { blockW: 10, blockH: 5, bytesPerBlock: 16 }],
|
|
82
|
+
['astc-10x5-unorm-srgb', { blockW: 10, blockH: 5, bytesPerBlock: 16 }],
|
|
83
|
+
['astc-10x6-unorm', { blockW: 10, blockH: 6, bytesPerBlock: 16 }],
|
|
84
|
+
['astc-10x6-unorm-srgb', { blockW: 10, blockH: 6, bytesPerBlock: 16 }],
|
|
85
|
+
['astc-10x8-unorm', { blockW: 10, blockH: 8, bytesPerBlock: 16 }],
|
|
86
|
+
['astc-10x8-unorm-srgb', { blockW: 10, blockH: 8, bytesPerBlock: 16 }],
|
|
87
|
+
['astc-10x10-unorm', { blockW: 10, blockH: 10, bytesPerBlock: 16 }],
|
|
88
|
+
['astc-10x10-unorm-srgb', { blockW: 10, blockH: 10, bytesPerBlock: 16 }],
|
|
89
|
+
['astc-12x10-unorm', { blockW: 12, blockH: 10, bytesPerBlock: 16 }],
|
|
90
|
+
['astc-12x10-unorm-srgb', { blockW: 12, blockH: 10, bytesPerBlock: 16 }],
|
|
91
|
+
['astc-12x12-unorm', { blockW: 12, blockH: 12, bytesPerBlock: 16 }],
|
|
92
|
+
['astc-12x12-unorm-srgb', { blockW: 12, blockH: 12, bytesPerBlock: 16 }],
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Look up the block parameters for a format. Returns `null` for uncompressed
|
|
97
|
+
* formats (the table only holds block-compressed entries).
|
|
98
|
+
*/
|
|
99
|
+
export function blockParamsForFormat(format: GPUTextureFormat): BlockParams | null {
|
|
100
|
+
return BLOCK_TABLE.get(format) ?? null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Guard: true iff `format` is a block-compressed format in the table. */
|
|
104
|
+
export function isCompressedFormat(format: GPUTextureFormat): boolean {
|
|
105
|
+
return BLOCK_TABLE.has(format);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Bytes per row for a compressed mip level of the given pixel width:
|
|
110
|
+
* `ceil(width / blockW) * bytesPerBlock`. Tail texels pad up to a full block.
|
|
111
|
+
* Returns `null` for uncompressed formats (no block entry) -- callers branch on
|
|
112
|
+
* `isCompressedFormat` first.
|
|
113
|
+
*/
|
|
114
|
+
export function bytesPerRow(format: GPUTextureFormat, width: number): number | null {
|
|
115
|
+
const params = BLOCK_TABLE.get(format);
|
|
116
|
+
if (params === undefined) return null;
|
|
117
|
+
return Math.ceil(width / params.blockW) * params.bytesPerBlock;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Block rows for a compressed mip level of the given pixel height:
|
|
122
|
+
* `ceil(height / blockH)`. Returns `null` for uncompressed formats.
|
|
123
|
+
*/
|
|
124
|
+
export function rowsPerImage(format: GPUTextureFormat, height: number): number | null {
|
|
125
|
+
const params = BLOCK_TABLE.get(format);
|
|
126
|
+
if (params === undefined) return null;
|
|
127
|
+
return Math.ceil(height / params.blockH);
|
|
128
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level basis encoder binding (build-time, D-5): raw pixels -> Basis KTX2.
|
|
3
|
+
*
|
|
4
|
+
* `basisEncode` wraps the self-built encoder WASM (scripts/build-wasm.mjs ->
|
|
5
|
+
* pkg/encode/basis_encoder.{mjs,wasm}) with a Result-returning function over the
|
|
6
|
+
* three delivery encodings this Loop produces (ETC1S / UASTC-LDR-4x4 /
|
|
7
|
+
* UASTC-HDR-4x4). It is the codec-side counterpart of `compressZstd`: it lives
|
|
8
|
+
* in the `@forgeax/engine-codec/encode` subpath (isolation path d guards runtime
|
|
9
|
+
* from statically importing it), and it carries the WASM + enum plumbing so the
|
|
10
|
+
* image-side arm (`encodeTextureToKtx2`, w18) can stay pure image semantics.
|
|
11
|
+
*
|
|
12
|
+
* Determinism (AC-02 / R-11): pthreads are disabled at build time and the
|
|
13
|
+
* per-encode threading is turned off here (`controlThreading(false, 0)`), so the
|
|
14
|
+
* same pixels + same options encode byte-identically. KTX2 carries no timestamp.
|
|
15
|
+
*
|
|
16
|
+
* Lazy-init (D-10, mirrors basis-transcoder): the encoder WASM module is
|
|
17
|
+
* dynamic-imported on the first encode and cached; subsequent encodes reuse the
|
|
18
|
+
* instance. The module .mjs is a MODULARIZE factory, node-legal here (build-time
|
|
19
|
+
* only, never bundled into the browser runtime by the isolation gate).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { CodecResult } from '../errors.js';
|
|
23
|
+
import { codecError } from '../errors.js';
|
|
24
|
+
import type { TranscodeModel } from '../transcode.js';
|
|
25
|
+
import type { BasisEncoder, BasisEncoderModule, BasisModuleFactory } from '../wasm/basis-types.js';
|
|
26
|
+
|
|
27
|
+
/** Which delivery encoding to produce. Mirrors the transcode source models (D-3). */
|
|
28
|
+
export type BasisEncodeMode = TranscodeModel;
|
|
29
|
+
|
|
30
|
+
// --- Fast-preset encode-effort constants (R-9) -------------------------------
|
|
31
|
+
// The 'auto' default (M5 w38) sends every existing texture through Basis encode,
|
|
32
|
+
// and the single-threaded encoder's default effort levels made the model-loading
|
|
33
|
+
// demo build run 10+ minutes. These three constants drop each arm to its lowest
|
|
34
|
+
// effort tier. Determinism (AC-02 / R-11) is unaffected -- effort tiers change
|
|
35
|
+
// the search budget, not the output nondeterminism (double-encode stays
|
|
36
|
+
// byte-identical; verified). Values anchor to the vendored encoder enums so a
|
|
37
|
+
// pin bump that renames a tier surfaces here.
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* ETC1S perf-vs-size effort level (`m_etc1s_compression_level`, range [0,6],
|
|
41
|
+
* vendored default 2 -- basisu_frontend.h). This lever is orthogonal to visual
|
|
42
|
+
* quality: quality is fixed by `setQualityLevel` below; a lower compression
|
|
43
|
+
* level only trades a marginally larger file for a much faster encode.
|
|
44
|
+
*/
|
|
45
|
+
const ETC1S_COMPRESSION_LEVEL_FASTEST = 0;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* UASTC-LDR pack level (`cPackUASTCLevelFastest`, the low nibble of
|
|
49
|
+
* `m_pack_uastc_ldr_4x4_flags`, vendored default `cPackUASTCLevelDefault` = 2 --
|
|
50
|
+
* basisu_uastc_enc.h). Unlike the ETC1S lever this DOES lower reconstruction
|
|
51
|
+
* quality (~43.5 dB vs ~47.5 dB avg), so R-9's per-fixture escape hatch
|
|
52
|
+
* (`compressionMode:'none'`) / D-12 upgrade path exists for any smoke that can
|
|
53
|
+
* not converge in epsilon at this tier.
|
|
54
|
+
*/
|
|
55
|
+
const PACK_UASTC_LDR_LEVEL_FASTEST = 0;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* UASTC-HDR quality level (`m_uastc_hdr_4x4_options` level, range [0,4],
|
|
59
|
+
* vendored default 1 -- basisu_uastc_hdr_4x4_enc.h). Level 0 is the fastest
|
|
60
|
+
* search tier; block-format bytes are unchanged in size, only encode time drops.
|
|
61
|
+
*/
|
|
62
|
+
const UASTC_HDR_QUALITY_LEVEL_FASTEST = 0;
|
|
63
|
+
|
|
64
|
+
/** Encode options -- pixel layout + delivery encoding + transfer / mip flags. */
|
|
65
|
+
export interface BasisEncodeOptions {
|
|
66
|
+
readonly mode: BasisEncodeMode;
|
|
67
|
+
readonly width: number;
|
|
68
|
+
readonly height: number;
|
|
69
|
+
/** Write the sRGB transfer function into the KTX2 + DFD (LDR color only). */
|
|
70
|
+
readonly srgb: boolean;
|
|
71
|
+
/** ETC1S perceptual (sRGB) error metric vs linear PSNR. Ignored for UASTC. */
|
|
72
|
+
readonly perceptual: boolean;
|
|
73
|
+
/** Wrap the UASTC-LDR payload in KTX2 zstd supercompression. Ignored otherwise. */
|
|
74
|
+
readonly uastcSupercompression: boolean;
|
|
75
|
+
/** Generate a mip chain in the encoder. M3 keeps this false (offline mips are w18/M5). */
|
|
76
|
+
readonly mipGen: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** How the encoder WASM module is loaded. Overridable in tests. @internal */
|
|
80
|
+
type EncoderImporter = () => Promise<BasisEncoderModule>;
|
|
81
|
+
|
|
82
|
+
const ENCODER_GLUE = new URL('../../pkg/encode/basis_encoder.mjs', import.meta.url);
|
|
83
|
+
const ENCODER_WASM = new URL('../../pkg/encode/basis_encoder.wasm', import.meta.url);
|
|
84
|
+
|
|
85
|
+
const defaultImporter: EncoderImporter = async () => {
|
|
86
|
+
const factory = (
|
|
87
|
+
(await import(/* @vite-ignore */ ENCODER_GLUE.href)) as {
|
|
88
|
+
default: BasisModuleFactory<BasisEncoderModule>;
|
|
89
|
+
}
|
|
90
|
+
).default;
|
|
91
|
+
const mod = await factory({ locateFile: () => ENCODER_WASM.href });
|
|
92
|
+
mod.initializeBasis();
|
|
93
|
+
return mod;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let importer: EncoderImporter = defaultImporter;
|
|
97
|
+
|
|
98
|
+
/** Lazy-init singleton (D-10): null = never attempted, cleared on failure to retry. @internal */
|
|
99
|
+
let _initPromise: Promise<BasisEncoderModule> | null = null;
|
|
100
|
+
|
|
101
|
+
/** Count of importer invocations; observed in tests to prove single-init. @internal */
|
|
102
|
+
let initCount = 0;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Lazy-init the Basis encoder WASM module (D-10 main-thread singleton).
|
|
106
|
+
* First call dynamic-imports + initializes; subsequent calls return the cache.
|
|
107
|
+
*/
|
|
108
|
+
export function initBasisEncoder(): Promise<BasisEncoderModule> {
|
|
109
|
+
if (_initPromise !== null) return _initPromise;
|
|
110
|
+
initCount++;
|
|
111
|
+
_initPromise = importer().catch((cause: unknown) => {
|
|
112
|
+
_initPromise = null;
|
|
113
|
+
throw new Error('codec-init-failed', { cause });
|
|
114
|
+
});
|
|
115
|
+
return _initPromise;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Test-only: number of times the encoder importer has been invoked.
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
export function _basisEncoderInitCount(): number {
|
|
123
|
+
return initCount;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Test-only: reset the lazy singleton and optionally override the importer.
|
|
128
|
+
* Call with no argument to restore the real WASM importer.
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
export function _setBasisEncoderImporter(next?: EncoderImporter): void {
|
|
132
|
+
importer = next ?? defaultImporter;
|
|
133
|
+
_initPromise = null;
|
|
134
|
+
initCount = 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Map a delivery encoding to the encoder `basis_tex_format` enum value. */
|
|
138
|
+
function formatModeValue(mod: BasisEncoderModule, mode: BasisEncodeMode): number {
|
|
139
|
+
const f = mod.basis_tex_format;
|
|
140
|
+
switch (mode) {
|
|
141
|
+
case 'etc1s':
|
|
142
|
+
return f.cETC1S.value;
|
|
143
|
+
case 'uastc-ldr':
|
|
144
|
+
return f.cUASTC_LDR_4x4.value;
|
|
145
|
+
case 'uastc-hdr':
|
|
146
|
+
return f.cUASTC_HDR_4x4.value;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Encode raw pixels into a Basis KTX2 container.
|
|
152
|
+
*
|
|
153
|
+
* `pixels` is tight-packed RGBA: 8-bit `R,G,B,A` per texel for LDR modes
|
|
154
|
+
* (`etc1s` / `uastc-ldr`), IEEE-754 binary16 RGBA (rgba16float bytes) for
|
|
155
|
+
* `uastc-hdr`. On any encoder-side failure returns a `ktx2-encode-failed`
|
|
156
|
+
* CodecError carrying the mode + a short reason.
|
|
157
|
+
*/
|
|
158
|
+
export async function basisEncode(
|
|
159
|
+
pixels: Uint8Array,
|
|
160
|
+
options: BasisEncodeOptions,
|
|
161
|
+
): Promise<CodecResult<Uint8Array>> {
|
|
162
|
+
let mod: BasisEncoderModule;
|
|
163
|
+
try {
|
|
164
|
+
mod = await initBasisEncoder();
|
|
165
|
+
} catch {
|
|
166
|
+
return codecError('codec-init-failed', { stage: 'dynamic-import-basis-encoder' });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const { mode, width, height, srgb, perceptual, uastcSupercompression, mipGen } = options;
|
|
170
|
+
if (width <= 0 || height <= 0) {
|
|
171
|
+
return codecError('ktx2-encode-failed', {
|
|
172
|
+
mode,
|
|
173
|
+
reason: `invalid dimensions ${width}x${height}`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let encoder: BasisEncoder;
|
|
178
|
+
try {
|
|
179
|
+
encoder = new mod.BasisEncoder();
|
|
180
|
+
} catch {
|
|
181
|
+
return codecError('ktx2-encode-failed', {
|
|
182
|
+
mode,
|
|
183
|
+
reason: 'encoder constructor failed',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
let result: CodecResult<Uint8Array> = codecError('ktx2-encode-failed', {
|
|
187
|
+
mode,
|
|
188
|
+
reason: 'encoder returned 0 bytes',
|
|
189
|
+
});
|
|
190
|
+
try {
|
|
191
|
+
// Single-threaded for deterministic, byte-identical output (AC-02 / R-11).
|
|
192
|
+
encoder.controlThreading(false, 0);
|
|
193
|
+
|
|
194
|
+
let sourceAccepted: boolean;
|
|
195
|
+
if (mode === 'uastc-hdr') {
|
|
196
|
+
// img_type 0 = cHITRGBAHalfFloat: the source is rgba16float bytes; no
|
|
197
|
+
// LDR->HDR upconversion (already linear half-float from the image arm).
|
|
198
|
+
sourceAccepted = encoder.setSliceSourceImageHDR(0, pixels, width, height, 0, false, 1.0);
|
|
199
|
+
} else {
|
|
200
|
+
// img_type 0 = cRGBA32: tight-packed 8-bit RGBA.
|
|
201
|
+
sourceAccepted = encoder.setSliceSourceImage(0, pixels, width, height, 0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!sourceAccepted) {
|
|
205
|
+
result = codecError('ktx2-encode-failed', {
|
|
206
|
+
mode,
|
|
207
|
+
reason:
|
|
208
|
+
mode === 'uastc-hdr' ? 'setSliceSourceImageHDR failed' : 'setSliceSourceImage failed',
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
encoder.setCreateKTX2File(true);
|
|
212
|
+
encoder.setFormatMode(formatModeValue(mod, mode));
|
|
213
|
+
encoder.setMipGen(mipGen);
|
|
214
|
+
|
|
215
|
+
if (mode === 'etc1s') {
|
|
216
|
+
encoder.setQualityLevel(128);
|
|
217
|
+
// Fastest perf tier (R-9): quality stays fixed by setQualityLevel above;
|
|
218
|
+
// the compression level only trades a marginally larger file for a much
|
|
219
|
+
// faster encode (the 'auto' default routes every texture through here).
|
|
220
|
+
encoder.setETC1SCompressionLevel(ETC1S_COMPRESSION_LEVEL_FASTEST);
|
|
221
|
+
encoder.setPerceptual(perceptual);
|
|
222
|
+
} else if (mode === 'uastc-ldr') {
|
|
223
|
+
encoder.setKTX2UASTCSupercompression(uastcSupercompression);
|
|
224
|
+
// Fastest pack tier (R-9). Unlike ETC1S this lowers quality; per-fixture
|
|
225
|
+
// opt-out / D-12 upgrade covers any smoke that will not converge.
|
|
226
|
+
encoder.setPackUASTCFlags(PACK_UASTC_LDR_LEVEL_FASTEST);
|
|
227
|
+
encoder.setPerceptual(perceptual);
|
|
228
|
+
} else {
|
|
229
|
+
// uastc-hdr: fastest HDR search tier (R-9); block bytes unchanged in size.
|
|
230
|
+
encoder.setUASTCHDRQualityLevel(UASTC_HDR_QUALITY_LEVEL_FASTEST);
|
|
231
|
+
}
|
|
232
|
+
// sRGB transfer function into the KTX2 header + DFD (LDR color arm).
|
|
233
|
+
encoder.setKTX2AndBasisSRGBTransferFunc(srgb);
|
|
234
|
+
|
|
235
|
+
// encode() copies into the provided buffer and returns the byte length (0 on
|
|
236
|
+
// failure). A 4 MiB scratch buffer covers a single-slice mip-0 image at the
|
|
237
|
+
// sizes this offline arm handles; grow-on-overflow retries once.
|
|
238
|
+
let capacity = 1 << 22;
|
|
239
|
+
let out = new Uint8Array(capacity);
|
|
240
|
+
let n = encoder.encode(out);
|
|
241
|
+
if (n === 0) {
|
|
242
|
+
capacity = 1 << 25;
|
|
243
|
+
out = new Uint8Array(capacity);
|
|
244
|
+
n = encoder.encode(out);
|
|
245
|
+
}
|
|
246
|
+
if (n === 0) {
|
|
247
|
+
result = codecError('ktx2-encode-failed', { mode, reason: 'encoder returned 0 bytes' });
|
|
248
|
+
} else {
|
|
249
|
+
result = { ok: true, value: out.slice(0, n) };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (e) {
|
|
253
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
254
|
+
result = codecError('ktx2-encode-failed', { mode, reason });
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
encoder.delete();
|
|
258
|
+
} catch {
|
|
259
|
+
if (result.ok) {
|
|
260
|
+
result = codecError('ktx2-encode-failed', { mode, reason: 'encoder cleanup failed' });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @forgeax/engine-codec/encode
|
|
3
|
+
*
|
|
4
|
+
* Build-time encoding subpath: zstd compression + Basis KTX2 texture encode.
|
|
5
|
+
* Gated from runtime import by `check-image-pipeline-isolation.mjs` path d.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
_setZstdEncoderImporter,
|
|
10
|
+
_zstdEncodeInitCount,
|
|
11
|
+
compressZstd,
|
|
12
|
+
} from '../encode-impl.js';
|
|
13
|
+
export type { BasisEncodeMode, BasisEncodeOptions } from './basis-encode.js';
|
|
14
|
+
export {
|
|
15
|
+
_basisEncoderInitCount,
|
|
16
|
+
_setBasisEncoderImporter,
|
|
17
|
+
basisEncode,
|
|
18
|
+
initBasisEncoder,
|
|
19
|
+
} from './basis-encode.js';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import type { CodecResult } from './errors.js';
|
|
3
|
+
import { codecError } from './errors.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CJS require for @bokuweb/zstd-wasm (build-time Node.js only).
|
|
7
|
+
*
|
|
8
|
+
* @bokuweb/zstd-wasm is distributed as a CommonJS package with a `.wasm`
|
|
9
|
+
* sidecar. We use createRequire to load it from ESM, since it does not
|
|
10
|
+
* provide ESM exports.
|
|
11
|
+
*/
|
|
12
|
+
const zstdRequire = createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
interface ZstdWasm {
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
compress(buf: Uint8Array, level?: number): Uint8Array;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** How the build-time zstd WASM encoder module is loaded. @internal */
|
|
20
|
+
type ZstdImporter = () => ZstdWasm;
|
|
21
|
+
|
|
22
|
+
const defaultImporter: ZstdImporter = () => zstdRequire('@bokuweb/zstd-wasm');
|
|
23
|
+
|
|
24
|
+
let importer: ZstdImporter = defaultImporter;
|
|
25
|
+
|
|
26
|
+
/** Lazy-init singleton handle for the build-time zstd WASM encoder. @internal */
|
|
27
|
+
let _zstd: ZstdWasm | null = null;
|
|
28
|
+
/**
|
|
29
|
+
* Shared while the encoder WASM is initializing; cleared after a failed attempt.
|
|
30
|
+
* The dependency mutates module-level emscripten exports and memory views on
|
|
31
|
+
* every init, so a second init can bind calls to one instance and reads to another.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
let _initPromise: Promise<ZstdWasm> | null = null;
|
|
35
|
+
/** Test-only count of encoder WASM initialization attempts. @internal */
|
|
36
|
+
let initCount = 0;
|
|
37
|
+
|
|
38
|
+
function getZstd(): Promise<ZstdWasm> {
|
|
39
|
+
if (_zstd !== null) {
|
|
40
|
+
return Promise.resolve(_zstd);
|
|
41
|
+
}
|
|
42
|
+
if (_initPromise !== null) {
|
|
43
|
+
return _initPromise;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
initCount++;
|
|
47
|
+
_initPromise = Promise.resolve()
|
|
48
|
+
.then(() => {
|
|
49
|
+
const mod = importer();
|
|
50
|
+
return mod.init().then(() => {
|
|
51
|
+
_zstd = mod;
|
|
52
|
+
return mod;
|
|
53
|
+
});
|
|
54
|
+
})
|
|
55
|
+
.catch((cause: unknown) => {
|
|
56
|
+
// Clear cached failure so a later import can retry (do not permanently cache).
|
|
57
|
+
_initPromise = null;
|
|
58
|
+
throw new Error('codec-init-failed', { cause });
|
|
59
|
+
});
|
|
60
|
+
return _initPromise;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Test-only: number of zstd encoder WASM initialization attempts.
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export function _zstdEncodeInitCount(): number {
|
|
68
|
+
return initCount;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Test-only: reset the encoder singleton and optionally override its importer.
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
export function _setZstdEncoderImporter(next?: ZstdImporter): void {
|
|
76
|
+
importer = next ?? defaultImporter;
|
|
77
|
+
_zstd = null;
|
|
78
|
+
_initPromise = null;
|
|
79
|
+
initCount = 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compress bytes with zstd using a fixed compression level (level 3 = default).
|
|
84
|
+
*
|
|
85
|
+
* Build-time only. Uses @bokuweb/zstd-wasm pinned at 0.0.27 for
|
|
86
|
+
* deterministic output (same input + same level = byte-identical output).
|
|
87
|
+
*
|
|
88
|
+
* Returns `{ ok: true, value: compressed }` on success.
|
|
89
|
+
* Returns `{ ok: false, error: { code: 'decompression-failed', ... } }` on failure
|
|
90
|
+
* (encoding failures mapped to decompression-failed per D-8, same code different detail).
|
|
91
|
+
*/
|
|
92
|
+
export async function compressZstd(bytes: Uint8Array): Promise<CodecResult<Uint8Array>> {
|
|
93
|
+
try {
|
|
94
|
+
const zstd = await getZstd();
|
|
95
|
+
const result = zstd.compress(bytes, 3);
|
|
96
|
+
return { ok: true, value: result };
|
|
97
|
+
} catch (e) {
|
|
98
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
99
|
+
return codecError('decompression-failed', {
|
|
100
|
+
reason: `zstd compression failed: ${reason}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codec error model — closed union per D-8.
|
|
3
|
+
*
|
|
4
|
+
* All codec error codes live here as the SSOT. Runtime never extends the types
|
|
5
|
+
* shared union; codec errors are nested in existing asset error detail.
|
|
6
|
+
*
|
|
7
|
+
* Union is order-locked and add-only-minor for Loop 2 additions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Per-code narrowed detail payloads. */
|
|
11
|
+
interface CodecErrorDetails {
|
|
12
|
+
'decompression-failed': { readonly reason: string };
|
|
13
|
+
'codec-init-failed': { readonly stage: string };
|
|
14
|
+
'ktx2-parse-failed': { readonly reason: string };
|
|
15
|
+
'ktx2-unsupported-scheme': { readonly scheme: number };
|
|
16
|
+
'transcode-failed': { readonly sourceFormat: string; readonly targetFormat: string };
|
|
17
|
+
'ktx2-encode-failed': { readonly mode: string; readonly reason: string };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Order-locked closed union of codec error codes. */
|
|
21
|
+
export type CodecErrorCode = keyof CodecErrorDetails;
|
|
22
|
+
|
|
23
|
+
/** Structured codec error with executable hint + per-code narrowed detail. */
|
|
24
|
+
export type CodecError = {
|
|
25
|
+
[C in CodecErrorCode]: {
|
|
26
|
+
readonly ok: false;
|
|
27
|
+
readonly error: {
|
|
28
|
+
readonly code: C;
|
|
29
|
+
readonly expected: string;
|
|
30
|
+
readonly hint: string;
|
|
31
|
+
readonly detail: CodecErrorDetails[C];
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}[CodecErrorCode];
|
|
35
|
+
|
|
36
|
+
type CodecErrorFor<C extends CodecErrorCode> = Extract<
|
|
37
|
+
CodecError,
|
|
38
|
+
{ readonly error: { readonly code: C } }
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
/** Success branch of a Result<T, CodecError>. */
|
|
42
|
+
export interface CodecOk<T> {
|
|
43
|
+
readonly ok: true;
|
|
44
|
+
readonly value: T;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Result type for codec operations — discriminated union on `.ok`. */
|
|
48
|
+
export type CodecResult<T> = CodecOk<T> | CodecError;
|
|
49
|
+
|
|
50
|
+
/** Error factory — produces a full CodecError object for the given code + detail. */
|
|
51
|
+
export function codecError<C extends CodecErrorCode>(
|
|
52
|
+
code: C,
|
|
53
|
+
detail: CodecErrorDetails[C],
|
|
54
|
+
): CodecErrorFor<C> {
|
|
55
|
+
const hints: Record<CodecErrorCode, string> = {
|
|
56
|
+
'decompression-failed':
|
|
57
|
+
'Check catalog row compression field and asset binary consistency; re-run asset import.',
|
|
58
|
+
'codec-init-failed':
|
|
59
|
+
'Uncompressed assets are still loadable. Verify the codec module is installed correctly.',
|
|
60
|
+
'ktx2-parse-failed':
|
|
61
|
+
'Check that the KTX2 file is valid and not truncated. Re-import the texture asset.',
|
|
62
|
+
'ktx2-unsupported-scheme':
|
|
63
|
+
'This supercompression scheme requires a future codec upgrade. Check the codec README Loop 2 extension points.',
|
|
64
|
+
'transcode-failed':
|
|
65
|
+
'Basis transcode failed for this source/target format pair. Verify the KTX2 payload is a valid Basis (ETC1S / UASTC) texture and re-import the asset.',
|
|
66
|
+
'ktx2-encode-failed':
|
|
67
|
+
'Basis encode failed for this compression mode. Verify the source image dimensions / pixel format match the mode (LDR rgba8 vs HDR rgba16float) and retry the asset import.',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: {
|
|
73
|
+
code,
|
|
74
|
+
expected: 'valid compressed data or supported compression scheme',
|
|
75
|
+
hint: hints[code],
|
|
76
|
+
detail,
|
|
77
|
+
},
|
|
78
|
+
} as CodecErrorFor<C>;
|
|
79
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @forgeax/engine-codec
|
|
3
|
+
*
|
|
4
|
+
* Runtime-safe zstd decode + KTX2 container parse for the forgeax asset pipeline.
|
|
5
|
+
* Build-time zstd encode lives in the `./encode` subpath.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
BasisSourceInspection,
|
|
10
|
+
BasisSourceMeta,
|
|
11
|
+
TranscodedBasisTexture,
|
|
12
|
+
TranscodedMip,
|
|
13
|
+
TranscodedTexture,
|
|
14
|
+
} from './basis-transcoder.js';
|
|
15
|
+
export {
|
|
16
|
+
initBasisTranscoder,
|
|
17
|
+
inspectBasisSource,
|
|
18
|
+
transcodeBasis,
|
|
19
|
+
transcodeKtx2,
|
|
20
|
+
} from './basis-transcoder.js';
|
|
21
|
+
export type { BlockParams } from './block-format.js';
|
|
22
|
+
export {
|
|
23
|
+
blockParamsForFormat,
|
|
24
|
+
bytesPerRow,
|
|
25
|
+
isCompressedFormat,
|
|
26
|
+
rowsPerImage,
|
|
27
|
+
} from './block-format.js';
|
|
28
|
+
export type { CodecError, CodecErrorCode, CodecOk, CodecResult } from './errors.js';
|
|
29
|
+
export { codecError } from './errors.js';
|
|
30
|
+
export { KTX2_IDENTIFIER, ktx2ColorSpace, ktx2LevelsToRGBA, parseKtx2 } from './ktx2.js';
|
|
31
|
+
export type {
|
|
32
|
+
TranscodeCaps,
|
|
33
|
+
TranscodeChannels,
|
|
34
|
+
TranscodeModel,
|
|
35
|
+
TranscodeSource,
|
|
36
|
+
} from './transcode.js';
|
|
37
|
+
export { selectTranscodeTarget } from './transcode.js';
|
|
38
|
+
export { decompressZstd } from './zstd.js';
|