gputex 0.1.2 → 0.3.0
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/README.md +121 -19
- package/dist/index.d.ts +37 -160
- package/dist/index.js +422 -534
- package/dist/testing.d.ts +120 -0
- package/dist/testing.js +945 -0
- package/dist/three.d.ts +141 -0
- package/dist/three.js +1629 -0
- package/package.json +14 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
# GPUtex
|
|
1
|
+
# GPUtex | On-the-fly GPU texture encoding
|
|
2
2
|
|
|
3
3
|
Runtime GPU texture compression via WebGPU compute shaders, with a WebGL2 fragment-shader fallback. Feed it a PNG/JPG/WebP/AVIF and get back a GPU-compressed texture (BC7, BC5, ASTC 4x4, or BC1) ready for Three.js or React Three Fiber.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
⚠️ 100% vibe-coded. The code is completely unreviewed and under-tested. Do not use for anything important.
|
|
6
|
+
|
|
7
|
+
🚀 Used in production on [Mana Blade](https://manablade.com).
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
@@ -14,7 +16,15 @@ pnpm add gputex
|
|
|
14
16
|
bun add gputex
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
### Entry points
|
|
20
|
+
|
|
21
|
+
`gputex` ships three entry points:
|
|
22
|
+
|
|
23
|
+
- **`gputex`** — the engine-agnostic core: the `*Encoder` classes, capability / format detection, and mip helpers. Nothing here imports `three`, so it works with Babylon.js, raw WebGPU/WebGL, workers, etc. Encoders return raw compressed block bytes via `encodeToBytes()`.
|
|
24
|
+
- **`gputex/three`** — the Three.js layer. Re-exports the entire core **plus** `compressTexture()`, `GputexLoader`, and `buildCompressedTexture()` / `encodeToTexture()`. This is the only entry that imports `three`.
|
|
25
|
+
- **`gputex/testing`** — the CPU reference encoders/decoders the GPU shaders are validated against. Test-suite material, not runtime API (see [Testing](#testing)).
|
|
26
|
+
|
|
27
|
+
`three` is an **optional** peer dependency (`>=0.170`): install it only if you import `gputex/three`. Pure-core consumers (e.g. Babylon.js) can skip it entirely.
|
|
18
28
|
|
|
19
29
|
## Formats
|
|
20
30
|
|
|
@@ -29,7 +39,7 @@ Format selection is automatic: BC7/BC5 on desktop, ASTC on mobile, uncompressed
|
|
|
29
39
|
|
|
30
40
|
## WebGL fallback
|
|
31
41
|
|
|
32
|
-
WebGPU is the primary path. When it's unavailable (older Safari, Firefox without WebGPU, locked-down environments) `compressTexture()` automatically falls back to a **WebGL2** path that runs the same block encoders as fragment shaders — each 4×4 block is computed in one fragment, written to an `RGBA32UI` render target, and read back. The
|
|
42
|
+
WebGPU is the primary path. When it's unavailable (older Safari, Firefox without WebGPU, locked-down environments) `compressTexture()` automatically falls back to a **WebGL2** path that runs the same family of block encoders as fragment shaders — each 4×4 block is computed in one fragment, written to an `RGBA32UI` render target, and read back. The WebGPU fast paths have since been rewritten for speed (projection assignment, f16), so the two backends are no longer byte-identical, but they implement the same algorithms at the same quality level and the resulting `CompressedTexture` looks the same under either renderer.
|
|
33
43
|
|
|
34
44
|
The fallback chain is **WebGPU → WebGL2 → uncompressed RGBA8**. The `backend` field on the result (`'webgpu' | 'webgl' | 'none'`) tells you which path ran.
|
|
35
45
|
|
|
@@ -44,7 +54,7 @@ Notes on the WebGL path:
|
|
|
44
54
|
### `compressTexture` — direct API
|
|
45
55
|
|
|
46
56
|
```ts
|
|
47
|
-
import { compressTexture } from 'gputex'
|
|
57
|
+
import { compressTexture } from 'gputex/three'
|
|
48
58
|
|
|
49
59
|
const { texture, format } = await compressTexture('/cobblestone.avif', {
|
|
50
60
|
hint: 'color', // 'color' | 'colorWithAlpha' | 'normal'
|
|
@@ -63,19 +73,23 @@ material.map = texture
|
|
|
63
73
|
- **`'fast'` (default)** — a bounding-box endpoint seed plus projection-based
|
|
64
74
|
index assignment (each pixel is projected onto the colinear endpoint line in
|
|
65
75
|
O(1) instead of searching every palette entry) with a single fused
|
|
66
|
-
least-squares refit
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
least-squares refit (accepted per block only when it lowers the error), and
|
|
77
|
+
the block bits packed with straight-line constant shifts. On GPUs that
|
|
78
|
+
report the `shader-f16` feature the whole fast path (all four formats, BC1
|
|
79
|
+
included) runs in f16 — the f32 path is the automatic fallback. Net vs
|
|
80
|
+
`'high'` on an Apple GPU: roughly **10–30× faster** depending on format,
|
|
81
|
+
for a PSNR cost of **≤0.65 dB on smooth/flat content** (imperceptible) and
|
|
82
|
+
up to a few dB on adversarial high-frequency noise, where the bbox seed
|
|
83
|
+
trails `'high'`'s exhaustive search. See the benchmark table below.
|
|
71
84
|
- **`'high'`** — exhaustive endpoint search (farthest-pair seed, full nearest
|
|
72
|
-
search, p-bit search);
|
|
73
|
-
|
|
85
|
+
search, p-bit search); matches the CPU reference encoders block-for-block
|
|
86
|
+
(byte-identical on >96% of blocks; the rest are equal-error FP tie-breaks,
|
|
87
|
+
enforced by the GPU test suite).
|
|
74
88
|
|
|
75
89
|
### `GputexLoader` — Three.js Loader
|
|
76
90
|
|
|
77
91
|
```ts
|
|
78
|
-
import { GputexLoader } from 'gputex'
|
|
92
|
+
import { GputexLoader } from 'gputex/three'
|
|
79
93
|
|
|
80
94
|
const loader = new GputexLoader()
|
|
81
95
|
loader.hint = 'normal'
|
|
@@ -90,7 +104,7 @@ The `GputexLoader` works with R3F's `useLoader`:
|
|
|
90
104
|
|
|
91
105
|
```tsx
|
|
92
106
|
import { useLoader } from '@react-three/fiber'
|
|
93
|
-
import { GputexLoader } from 'gputex'
|
|
107
|
+
import { GputexLoader } from 'gputex/three'
|
|
94
108
|
|
|
95
109
|
function Scene() {
|
|
96
110
|
const texture = useLoader(GputexLoader, '/cobblestone.avif', loader => {
|
|
@@ -113,7 +127,7 @@ For a reusable hook with metadata access:
|
|
|
113
127
|
```tsx
|
|
114
128
|
import { useLayoutEffect } from 'react'
|
|
115
129
|
import { useLoader } from '@react-three/fiber'
|
|
116
|
-
import { GputexLoader } from 'gputex'
|
|
130
|
+
import { GputexLoader } from 'gputex/three'
|
|
117
131
|
import type { TextureHint } from 'gputex'
|
|
118
132
|
|
|
119
133
|
function useGputex(url: string, options?: { hint?: TextureHint; colorSpace?: 'srgb' | 'linear'; mipmaps?: boolean }) {
|
|
@@ -157,18 +171,35 @@ function Scene() {
|
|
|
157
171
|
}
|
|
158
172
|
```
|
|
159
173
|
|
|
160
|
-
### Low-level encoders
|
|
174
|
+
### Low-level encoders (any engine)
|
|
161
175
|
|
|
162
|
-
|
|
176
|
+
The individual encoder classes live in the engine-agnostic core (`gputex`). `encodeToBytes()` returns raw compressed block bytes with no Three.js involvement — feed them into whatever compressed-texture upload your engine exposes (Babylon.js, raw WebGPU/WebGL, …):
|
|
163
177
|
|
|
164
178
|
```ts
|
|
165
179
|
import { BC7Encoder, BC5Encoder, ASTC4x4Encoder, BC1Encoder } from 'gputex'
|
|
166
180
|
|
|
167
181
|
const encoder = await BC7Encoder.create()
|
|
168
|
-
const { data, width, height } = await encoder.encodeToBytes(imageBitmap)
|
|
182
|
+
const { data, width, height, paddedWidth, paddedHeight } = await encoder.encodeToBytes(imageBitmap)
|
|
183
|
+
// `data` is a Uint8Array of BC7 blocks covering paddedWidth × paddedHeight.
|
|
169
184
|
encoder.destroy()
|
|
170
185
|
```
|
|
171
186
|
|
|
187
|
+
To turn an encoder's output into a Three.js `CompressedTexture` directly, use the helpers in `gputex/three`:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { BC7Encoder, TextureFormat } from 'gputex'
|
|
191
|
+
import { encodeToTexture, buildCompressedTexture } from 'gputex/three'
|
|
192
|
+
|
|
193
|
+
const encoder = await BC7Encoder.create()
|
|
194
|
+
|
|
195
|
+
// One-shot: image → CompressedTexture (plus the raw byte metadata)
|
|
196
|
+
const { texture } = await encodeToTexture(encoder, imageBitmap, { colorSpace: 'srgb' })
|
|
197
|
+
|
|
198
|
+
// …or assemble a texture from bytes you already have (e.g. a mip chain):
|
|
199
|
+
const bytes = await encoder.encodeToBytes(imageBitmap)
|
|
200
|
+
const tex = buildCompressedTexture([bytes], TextureFormat.BC7_SRGB)
|
|
201
|
+
```
|
|
202
|
+
|
|
172
203
|
## Options
|
|
173
204
|
|
|
174
205
|
### `compressTexture` options
|
|
@@ -181,6 +212,77 @@ encoder.destroy()
|
|
|
181
212
|
| `mipmaps` | `boolean` | `false` | Generate full mip chain down to 1x1 |
|
|
182
213
|
| `device` | `GPUDevice` | — | Reuse an existing WebGPU device instead of creating one |
|
|
183
214
|
|
|
215
|
+
## Benchmarks
|
|
216
|
+
|
|
217
|
+
Measured with the repo's GPU test suite (see below) on an Apple Silicon GPU
|
|
218
|
+
(`metal-3`) in Chrome, encoding a 2048×2048 image. **GPU pass** is the compute
|
|
219
|
+
shader alone (WebGPU timestamp queries, median of 20 runs); end-to-end wall
|
|
220
|
+
time adds ~3–4 ms of image upload + result readback regardless of format.
|
|
221
|
+
"Before" is the shader generation prior to the 2026-07 optimization pass
|
|
222
|
+
(projection-based index assignment everywhere, on-the-fly bit packing, an f16
|
|
223
|
+
BC1 fast shader, straight-line block assembly).
|
|
224
|
+
|
|
225
|
+
| Format | Quality | Shader | GPU pass before | GPU pass after | Speedup |
|
|
226
|
+
| -------- | -------------- | ------ | --------------- | -------------- | --------- |
|
|
227
|
+
| BC1 | fast (default) | f16 | — (had no f16) | **0.26 ms** | **2.8×**¹ |
|
|
228
|
+
| BC1 | fast | f32 | 0.72 ms | 0.33 ms | 2.2× |
|
|
229
|
+
| BC5 | fast (default) | f16 | 0.33 ms | **0.20 ms** | 1.7× |
|
|
230
|
+
| BC5 | fast | f32 | 0.79 ms | 0.39 ms | 2.0× |
|
|
231
|
+
| BC7 | fast (default) | f16 | 1.38 ms | **0.52 ms** | 2.7× |
|
|
232
|
+
| BC7 | fast | f32 | 2.65 ms | 1.70 ms | 1.6× |
|
|
233
|
+
| ASTC 4×4 | fast (default) | f16 | 0.33 ms | **0.20 ms** | 1.7× |
|
|
234
|
+
| ASTC 4×4 | fast | f32 | 0.72 ms | 0.66 ms | 1.1× |
|
|
235
|
+
| BC1 | high | f32 | 2.9 ms | 3.0 ms | unchanged |
|
|
236
|
+
| BC5 | high | f32 | 3.4 ms | 3.4 ms | unchanged |
|
|
237
|
+
| BC7 | high | f32 | 14.1 ms | 14.1 ms | unchanged |
|
|
238
|
+
| ASTC 4×4 | high | f32 | 2.6 ms | 2.4 ms | unchanged |
|
|
239
|
+
|
|
240
|
+
¹ vs the old f32 fast shader, which was the only BC1 fast path before. The
|
|
241
|
+
BC1 rows were measured with old and new pipelines interleaved in one session
|
|
242
|
+
(the most noise-robust method); the others are cross-run suite medians.
|
|
243
|
+
|
|
244
|
+
Per-quadrant PSNR on the committed 512² test card is equal to or better than
|
|
245
|
+
the previous fast encoders everywhere (flat tiles bit-identical, gradients
|
|
246
|
+
+0.01 dB, noise −0.01 dB); the `high` paths still match the CPU reference
|
|
247
|
+
encoders. Timestamps are quantised to 100 µs by Chrome, so sub-millisecond
|
|
248
|
+
figures are ±0.05–0.1 ms.
|
|
249
|
+
|
|
250
|
+
## Testing
|
|
251
|
+
|
|
252
|
+
Unit tests (`bun test`) cover the CPU reference encoders and metadata, but the
|
|
253
|
+
WGSL shaders can only be validated on a real GPU. The repo ships a browser
|
|
254
|
+
test + benchmark suite at `example/pages/test.tsx` (logic in
|
|
255
|
+
`example/lib/gpuTestSuite.ts`):
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
bun run --filter gputex build # build the library the example consumes
|
|
259
|
+
cd example && bunx next dev # then open http://localhost:3000/test
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The page runs three groups against the live WebGPU device and renders
|
|
263
|
+
PASS/FAIL tables (machine-readable copy on `window.__GPUTEX_TESTS__`):
|
|
264
|
+
|
|
265
|
+
- **Correctness** — `quality: 'high'` output is compared block-by-block
|
|
266
|
+
against the CPU reference encoders (`gputex/testing`), including a
|
|
267
|
+
non-multiple-of-4 image for the clamp-to-edge padding path. Differing blocks
|
|
268
|
+
must have equal decoded error (FP tie-break tolerance) and the aggregate
|
|
269
|
+
PSNR delta must be ≤0.05 dB. Plus determinism checks (same input twice →
|
|
270
|
+
identical bytes).
|
|
271
|
+
- **Quality** — `'fast'` and `'high'` output is CPU-decoded and validated on
|
|
272
|
+
the FULL 512² test cards (every quadrant stresses a different failure mode)
|
|
273
|
+
with two gates, for both the f16 and (force-disabled-f16) f32 shaders:
|
|
274
|
+
aggregate PSNR must beat per-format thresholds pinned ~0.15 dB under the
|
|
275
|
+
measured baseline, and — because a handful of catastrophically wrong blocks
|
|
276
|
+
barely moves aggregate PSNR — the worst _easy_ block (one that `'high'`
|
|
277
|
+
encodes near-losslessly) must not exceed `'high'`'s error by more than a
|
|
278
|
+
small per-format limit.
|
|
279
|
+
- **Performance** — the benchmark table above: wall + GPU-pass time per
|
|
280
|
+
format × quality × shader variant.
|
|
281
|
+
|
|
282
|
+
The `gputex/testing` entry point exports the CPU reference
|
|
283
|
+
encoders/decoders (`encodeBC7Mode6Block`, `decodeASTC4x4Block`, …) so any
|
|
284
|
+
consumer can run the same validation.
|
|
285
|
+
|
|
184
286
|
## Requirements
|
|
185
287
|
|
|
186
288
|
- WebGPU (primary) **or** WebGL2 (fallback) — almost every current browser has at least one
|
|
@@ -195,4 +297,4 @@ encoder.destroy()
|
|
|
195
297
|
|
|
196
298
|
## Acknowledgements
|
|
197
299
|
|
|
198
|
-
The concept of encoding images on the GPU on the fly via compute shaders was first introduced by [spark.js](https://ludicon.com/sparkjs/)
|
|
300
|
+
The concept of encoding images on the GPU on the fly via compute shaders was first introduced by [spark.js](https://ludicon.com/sparkjs/). GPUtex is not derived from Spark. Its encoders have been implemented from scratch using official references, which have been ported to TypeScript, and then converted to WGSL and GLSL via AI. Spark was never mentioned or used as reference at any point of the implementation, and multiple reviews have found the implementations to be completely independent. For any serious production use of GPU-compressed textures, Spark is the recommended choice over GPUtex.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { CompressedPixelFormat, CompressedTexture, Texture, Loader } from 'three';
|
|
2
|
-
|
|
3
1
|
declare const TextureFormat: {
|
|
4
2
|
readonly BC1: "BC1";
|
|
5
3
|
readonly BC1_SRGB: "BC1_SRGB";
|
|
@@ -44,13 +42,19 @@ interface EncoderOptions {
|
|
|
44
42
|
device: GPUDevice;
|
|
45
43
|
adapter?: GPUAdapter;
|
|
46
44
|
ownsDevice?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Force the f32 'fast' shader even when the device supports shader-f16.
|
|
47
|
+
* For tests/benchmarks that need to exercise the f32 fallback path on
|
|
48
|
+
* f16-capable hardware. Default false.
|
|
49
|
+
*/
|
|
50
|
+
disableF16?: boolean;
|
|
47
51
|
}
|
|
48
52
|
/**
|
|
49
|
-
* Encoder quality level. 'fast' (default) uses the
|
|
50
|
-
* shaders —
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
53
|
+
* Encoder quality level. 'fast' (default) uses the projection-based paths in
|
|
54
|
+
* the shaders — an order of magnitude faster for a ≤0.65 dB PSNR cost. 'high'
|
|
55
|
+
* runs the exhaustive search, matching the CPU reference encoders
|
|
56
|
+
* block-for-block (byte-identical up to FP tie-breaks with equal error).
|
|
57
|
+
* BC1's 'high' adds a principal-axis endpoint seed and iterative refit.
|
|
54
58
|
*/
|
|
55
59
|
type EncodeQuality = 'fast' | 'high';
|
|
56
60
|
interface EncodeCallOptions {
|
|
@@ -59,19 +63,11 @@ interface EncodeCallOptions {
|
|
|
59
63
|
/** Encode quality / speed trade-off. Default 'fast'. */
|
|
60
64
|
quality?: EncodeQuality;
|
|
61
65
|
}
|
|
62
|
-
interface EncodeResult {
|
|
63
|
-
width: number;
|
|
64
|
-
height: number;
|
|
65
|
-
paddedWidth: number;
|
|
66
|
-
paddedHeight: number;
|
|
67
|
-
data: Uint8Array;
|
|
68
|
-
texture: CompressedTexture;
|
|
69
|
-
encodeMs: number;
|
|
70
|
-
}
|
|
71
66
|
/**
|
|
72
|
-
* Result of a raw bytes-only encode
|
|
73
|
-
*
|
|
74
|
-
* `
|
|
67
|
+
* Result of a raw bytes-only encode. This is the encoder's native output: the
|
|
68
|
+
* compressed block bytes plus dimensions, with no Three.js (or any engine)
|
|
69
|
+
* involvement. Feed `data` into whatever renderer's compressed-texture upload
|
|
70
|
+
* you like, or use `buildCompressedTexture()` from `gputex/three`.
|
|
75
71
|
*/
|
|
76
72
|
interface EncodeBytesResult {
|
|
77
73
|
width: number;
|
|
@@ -80,6 +76,14 @@ interface EncodeBytesResult {
|
|
|
80
76
|
paddedHeight: number;
|
|
81
77
|
data: Uint8Array;
|
|
82
78
|
encodeMs: number;
|
|
79
|
+
/**
|
|
80
|
+
* GPU-side compute-pass time in ms, measured with timestamp queries.
|
|
81
|
+
* Present only when the encode was called with `withGpuTime: true` and the
|
|
82
|
+
* device has the 'timestamp-query' feature (requested automatically by
|
|
83
|
+
* `create()` when available). Browsers quantise timestamps (Chrome: 100µs),
|
|
84
|
+
* so treat small values as approximate.
|
|
85
|
+
*/
|
|
86
|
+
gpuMs?: number;
|
|
83
87
|
}
|
|
84
88
|
interface FormatVariant {
|
|
85
89
|
colorSpace: 'srgb' | 'linear';
|
|
@@ -96,6 +100,8 @@ interface FormatVariant {
|
|
|
96
100
|
type EncoderConstructor<T extends Encoder = Encoder> = {
|
|
97
101
|
new (opts: EncoderOptions): T;
|
|
98
102
|
requiredFeature: GPUFeatureName | null;
|
|
103
|
+
/** Logical formats this encoder can emit (linear first, then sRGB variant). */
|
|
104
|
+
readonly textureFormats: readonly TextureFormat[];
|
|
99
105
|
create(): Promise<T>;
|
|
100
106
|
};
|
|
101
107
|
declare abstract class Encoder {
|
|
@@ -119,12 +125,13 @@ declare abstract class Encoder {
|
|
|
119
125
|
readonly device: GPUDevice;
|
|
120
126
|
readonly adapter?: GPUAdapter;
|
|
121
127
|
readonly ownsDevice: boolean;
|
|
128
|
+
readonly disableF16: boolean;
|
|
122
129
|
protected _module: GPUShaderModule;
|
|
123
130
|
protected _moduleF16: GPUShaderModule | null;
|
|
124
131
|
protected _pipelineF16: GPUComputePipeline | null;
|
|
125
132
|
protected _pipeline: GPUComputePipeline;
|
|
126
133
|
protected _pipelineCache: Map<EncodeQuality, GPUComputePipeline>;
|
|
127
|
-
constructor({ device, adapter, ownsDevice }: EncoderOptions);
|
|
134
|
+
constructor({ device, adapter, ownsDevice, disableF16 }: EncoderOptions);
|
|
128
135
|
protected _buildPipeline(): void;
|
|
129
136
|
/**
|
|
130
137
|
* Pipeline for a given quality level. Encoders that don't declare a
|
|
@@ -150,7 +157,7 @@ declare abstract class Encoder {
|
|
|
150
157
|
/**
|
|
151
158
|
* Optional f16 WGSL for the 'fast' path. Used only when the device reports the
|
|
152
159
|
* `shader-f16` feature; the format's f32 `wgslSource()` is the fallback and
|
|
153
|
-
* `'high'` always uses it. Returns null when there's no f16 variant
|
|
160
|
+
* `'high'` always uses it. Returns null when there's no f16 variant.
|
|
154
161
|
*/
|
|
155
162
|
wgslSourceFastF16(): string | null;
|
|
156
163
|
/** Whether the f16 fast path is both available and supported on this device. */
|
|
@@ -159,40 +166,24 @@ declare abstract class Encoder {
|
|
|
159
166
|
abstract wgslSource(): string;
|
|
160
167
|
/** e.g. 'bc1-rgba-unorm-srgb'. */
|
|
161
168
|
abstract gpuTextureFormat(opts: FormatVariant): GPUTextureFormat;
|
|
162
|
-
/** Three.js `CompressedPixelFormat` constant for `CompressedTexture`. */
|
|
163
|
-
abstract threeTextureFormat(opts: FormatVariant): CompressedPixelFormat;
|
|
164
169
|
/**
|
|
165
170
|
* True if the device reports the feature the output texture needs.
|
|
166
171
|
* The encoder itself only writes to a storage buffer, so this is about
|
|
167
172
|
* whether the result can actually be sampled.
|
|
168
173
|
*/
|
|
169
174
|
get supportsSampling(): boolean;
|
|
170
|
-
encode(source: EncoderImageSource, { colorSpace, quality }?: EncodeCallOptions): Promise<EncodeResult>;
|
|
171
175
|
/**
|
|
172
|
-
* Encode one image source to raw compressed bytes
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* Public (not protected) because `compressTexture()` calls it across the
|
|
178
|
-
* encoder boundary. Still safe to call from outside — it just does
|
|
179
|
-
* less work than `encode()` and the caller assembles the texture.
|
|
176
|
+
* Encode one image source to raw compressed bytes. This is the encoder's
|
|
177
|
+
* native, engine-agnostic output. `compressTexture()` and
|
|
178
|
+
* `encodeToTexture()` (both in `gputex/three`) call it and then wrap the
|
|
179
|
+
* bytes into a `CompressedTexture`; callers targeting another engine feed
|
|
180
|
+
* `data` into that engine's compressed-texture upload directly.
|
|
180
181
|
*/
|
|
181
|
-
encodeToBytes(source: EncoderImageSource, { flipY, quality }?: {
|
|
182
|
+
encodeToBytes(source: EncoderImageSource, { flipY, quality, withGpuTime, }?: {
|
|
182
183
|
flipY?: boolean;
|
|
183
184
|
quality?: EncodeQuality;
|
|
185
|
+
withGpuTime?: boolean;
|
|
184
186
|
}): Promise<EncodeBytesResult>;
|
|
185
|
-
/**
|
|
186
|
-
* Assemble a `CompressedTexture` from pre-encoded mip levels. Called
|
|
187
|
-
* by `compressTexture()` after it has run each level through
|
|
188
|
-
* `encodeToBytes()`. Centralised here so the single-level and mipped
|
|
189
|
-
* paths share the same format / colour-space / wrap settings.
|
|
190
|
-
*
|
|
191
|
-
* `levels[0]` is the base level; its padded dimensions become the
|
|
192
|
-
* texture's overall size. Filter setup assumes at least 2 levels →
|
|
193
|
-
* trilinear; 1 level → bilinear.
|
|
194
|
-
*/
|
|
195
|
-
buildMippedTexture(levels: readonly EncodeBytesResult[], { colorSpace }?: EncodeCallOptions): CompressedTexture;
|
|
196
187
|
}
|
|
197
188
|
|
|
198
189
|
declare class BC1Encoder extends Encoder {
|
|
@@ -203,8 +194,8 @@ declare class BC1Encoder extends Encoder {
|
|
|
203
194
|
get supportsSrgb(): boolean;
|
|
204
195
|
get supportsQuality(): boolean;
|
|
205
196
|
wgslSource(): string;
|
|
197
|
+
wgslSourceFastF16(): string | null;
|
|
206
198
|
gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
|
|
207
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
208
199
|
}
|
|
209
200
|
|
|
210
201
|
declare class BC5Encoder extends Encoder {
|
|
@@ -217,7 +208,6 @@ declare class BC5Encoder extends Encoder {
|
|
|
217
208
|
wgslSource(): string;
|
|
218
209
|
wgslSourceFastF16(): string;
|
|
219
210
|
gpuTextureFormat(): GPUTextureFormat;
|
|
220
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
221
211
|
}
|
|
222
212
|
|
|
223
213
|
declare class BC7Encoder extends Encoder {
|
|
@@ -230,7 +220,6 @@ declare class BC7Encoder extends Encoder {
|
|
|
230
220
|
wgslSource(): string;
|
|
231
221
|
wgslSourceFastF16(): string;
|
|
232
222
|
gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
|
|
233
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
234
223
|
}
|
|
235
224
|
|
|
236
225
|
declare class ASTC4x4Encoder extends Encoder {
|
|
@@ -243,18 +232,6 @@ declare class ASTC4x4Encoder extends Encoder {
|
|
|
243
232
|
wgslSource(): string;
|
|
244
233
|
wgslSourceFastF16(): string;
|
|
245
234
|
gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
|
|
246
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** One encoded mip level. The fields both encoder backends already produce. */
|
|
250
|
-
interface EncodedLevel {
|
|
251
|
-
/** Logical (pre-padding) dimensions, surfaced on the texture's userData. */
|
|
252
|
-
width: number;
|
|
253
|
-
height: number;
|
|
254
|
-
/** Block-aligned dimensions the compressed `data` actually covers. */
|
|
255
|
-
paddedWidth: number;
|
|
256
|
-
paddedHeight: number;
|
|
257
|
-
data: Uint8Array;
|
|
258
235
|
}
|
|
259
236
|
|
|
260
237
|
/** Raw RGBA8 pixel data (e.g. a CPU-generated mip level). */
|
|
@@ -307,8 +284,6 @@ declare abstract class WebGLBlockEncoder {
|
|
|
307
284
|
abstract get supportsSrgb(): boolean;
|
|
308
285
|
/** GLSL ES 3.00 fragment-shader source. */
|
|
309
286
|
abstract fragSource(): string;
|
|
310
|
-
/** Three.js `CompressedPixelFormat` constant; sRGB is carried by colorSpace. */
|
|
311
|
-
abstract threeTextureFormat(): CompressedPixelFormat;
|
|
312
287
|
protected _buildProgram(): void;
|
|
313
288
|
/** Release the GL program + VAO. The shared context itself is left intact. */
|
|
314
289
|
destroy(): void;
|
|
@@ -328,10 +303,6 @@ declare abstract class WebGLBlockEncoder {
|
|
|
328
303
|
encodeToBytes(source: WebGLEncoderImageSource, { flipY }?: {
|
|
329
304
|
flipY?: boolean;
|
|
330
305
|
}): WebGLEncodeBytesResult;
|
|
331
|
-
/** Wrap pre-encoded levels into a CompressedTexture. Shared with the WebGPU path. */
|
|
332
|
-
buildMippedTexture(levels: readonly EncodedLevel[], { colorSpace }?: {
|
|
333
|
-
colorSpace?: 'srgb' | 'linear';
|
|
334
|
-
}): CompressedTexture;
|
|
335
306
|
}
|
|
336
307
|
|
|
337
308
|
declare class BC1WebGLEncoder extends WebGLBlockEncoder {
|
|
@@ -339,7 +310,6 @@ declare class BC1WebGLEncoder extends WebGLBlockEncoder {
|
|
|
339
310
|
get bytesPerBlock(): number;
|
|
340
311
|
get supportsSrgb(): boolean;
|
|
341
312
|
fragSource(): string;
|
|
342
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
343
313
|
}
|
|
344
314
|
|
|
345
315
|
declare class BC5WebGLEncoder extends WebGLBlockEncoder {
|
|
@@ -347,7 +317,6 @@ declare class BC5WebGLEncoder extends WebGLBlockEncoder {
|
|
|
347
317
|
get bytesPerBlock(): number;
|
|
348
318
|
get supportsSrgb(): boolean;
|
|
349
319
|
fragSource(): string;
|
|
350
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
351
320
|
}
|
|
352
321
|
|
|
353
322
|
declare class BC7WebGLEncoder extends WebGLBlockEncoder {
|
|
@@ -355,7 +324,6 @@ declare class BC7WebGLEncoder extends WebGLBlockEncoder {
|
|
|
355
324
|
get bytesPerBlock(): number;
|
|
356
325
|
get supportsSrgb(): boolean;
|
|
357
326
|
fragSource(): string;
|
|
358
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
359
327
|
}
|
|
360
328
|
|
|
361
329
|
declare class ASTC4x4WebGLEncoder extends WebGLBlockEncoder {
|
|
@@ -363,7 +331,6 @@ declare class ASTC4x4WebGLEncoder extends WebGLBlockEncoder {
|
|
|
363
331
|
get bytesPerBlock(): number;
|
|
364
332
|
get supportsSrgb(): boolean;
|
|
365
333
|
fragSource(): string;
|
|
366
|
-
threeTextureFormat(): CompressedPixelFormat;
|
|
367
334
|
}
|
|
368
335
|
|
|
369
336
|
/**
|
|
@@ -438,96 +405,6 @@ interface WebGLFormatSelection {
|
|
|
438
405
|
}
|
|
439
406
|
declare function selectWebGLFormat(caps: WebGLCapabilities, hint: TextureHint, options?: SelectFormatOptions): WebGLFormatSelection;
|
|
440
407
|
|
|
441
|
-
/**
|
|
442
|
-
* Everything `compressTexture()` can take as an image source. A superset
|
|
443
|
-
* of `EncoderImageSource` (see Encoder.ts) that also accepts URL strings
|
|
444
|
-
* and Blob / File objects — the common cases in a web app.
|
|
445
|
-
*/
|
|
446
|
-
type CompressTextureSource = string | Blob | File | ImageBitmap | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageData;
|
|
447
|
-
interface CompressOptions {
|
|
448
|
-
/** How the texture will be used. Drives format selection. Default 'color'. */
|
|
449
|
-
hint?: TextureHint;
|
|
450
|
-
/** Pick the sRGB or linear variant of the chosen format. Default 'srgb'. */
|
|
451
|
-
colorSpace?: 'srgb' | 'linear';
|
|
452
|
-
/** Flip the image vertically before encoding. Default true (matches Three.js convention). */
|
|
453
|
-
flipY?: boolean;
|
|
454
|
-
/** Generate a full mip chain down to 1×1 on the CPU, encode every level. */
|
|
455
|
-
mipmaps?: boolean;
|
|
456
|
-
/**
|
|
457
|
-
* Encode quality / speed trade-off. 'fast' (default) is ~2–4× faster for a
|
|
458
|
-
* ≤0.36 dB PSNR cost; 'high' runs the exhaustive search (output identical to
|
|
459
|
-
* the CPU reference encoders; for BC1, a principal-axis seed + iterative
|
|
460
|
-
* refit). No effect on the WebGL fallback (which always uses the fast
|
|
461
|
-
* encoders).
|
|
462
|
-
*/
|
|
463
|
-
quality?: EncodeQuality;
|
|
464
|
-
/** Reuse an existing device (e.g. Three.js's renderer device) instead
|
|
465
|
-
* of creating a new one. WebGPU path only. When provided, the encoder
|
|
466
|
-
* never destroys it. */
|
|
467
|
-
device?: GPUDevice;
|
|
468
|
-
adapter?: GPUAdapter;
|
|
469
|
-
}
|
|
470
|
-
interface CompressResult {
|
|
471
|
-
/** CompressedTexture on a compressed path; Texture on RGBA8 fallback. */
|
|
472
|
-
texture: Texture | CompressedTexture;
|
|
473
|
-
/** The compressed format selected, or null when we fell back to RGBA8. */
|
|
474
|
-
format: TextureFormat | null;
|
|
475
|
-
/** True iff we returned an uncompressed Texture because no encoder fit. */
|
|
476
|
-
fallbackUncompressed: boolean;
|
|
477
|
-
/**
|
|
478
|
-
* Which backend produced the result. 'webgpu' = compute path, 'webgl' =
|
|
479
|
-
* fragment-shader fallback, 'none' = uncompressed RGBA8.
|
|
480
|
-
*/
|
|
481
|
-
backend: 'webgpu' | 'webgl' | 'none';
|
|
482
|
-
/**
|
|
483
|
-
* True iff the chosen format is ASTC and the hint was 'normal'. The
|
|
484
|
-
* caller must apply the (R, W) → (x, y) swizzle in the material — ASTC
|
|
485
|
-
* has no 2-channel mode, so normal maps ride the RGBA path.
|
|
486
|
-
*/
|
|
487
|
-
astcNormalRemap: boolean;
|
|
488
|
-
width: number;
|
|
489
|
-
height: number;
|
|
490
|
-
mipLevels: number;
|
|
491
|
-
/** Wall-clock time of GPU encoding, summed across mip levels. */
|
|
492
|
-
encodeMs: number;
|
|
493
|
-
/** Release the encoder's internal GPU resources. No-op if `device` was
|
|
494
|
-
* passed in by the caller. */
|
|
495
|
-
destroy(): void;
|
|
496
|
-
}
|
|
497
|
-
declare function compressTexture(source: CompressTextureSource, options?: CompressOptions): Promise<CompressResult>;
|
|
498
|
-
|
|
499
|
-
declare class GputexLoader extends Loader<Texture> {
|
|
500
|
-
/** Format-selection hint. Default 'color'. */
|
|
501
|
-
hint: TextureHint;
|
|
502
|
-
/** Pick the sRGB or linear variant of the chosen format. Default 'srgb'. */
|
|
503
|
-
colorSpace: 'srgb' | 'linear';
|
|
504
|
-
/** Flip the image vertically before encoding. Default true (matches Three.js convention). */
|
|
505
|
-
flipY: boolean;
|
|
506
|
-
/** Generate + encode a full mip chain. Default false. */
|
|
507
|
-
mipmaps: boolean;
|
|
508
|
-
/** Encode quality / speed trade-off. Default 'fast' (~2–4× faster, ≤0.36 dB). */
|
|
509
|
-
quality: EncodeQuality;
|
|
510
|
-
/**
|
|
511
|
-
* Optional pre-existing WebGPU device. Reusing the renderer's device
|
|
512
|
-
* avoids spinning up a second WebGPU context for encoding.
|
|
513
|
-
*/
|
|
514
|
-
device?: GPUDevice;
|
|
515
|
-
adapter?: GPUAdapter;
|
|
516
|
-
/**
|
|
517
|
-
* Most recent full encode result. Useful when the caller wants format
|
|
518
|
-
* / mipLevels / astcNormalRemap metadata without threading a separate
|
|
519
|
-
* callback through `load()`. Cleared when a new load starts.
|
|
520
|
-
*/
|
|
521
|
-
lastResult: CompressResult | null;
|
|
522
|
-
/**
|
|
523
|
-
* THREE.Loader contract: returns void, drives callbacks. `loadAsync`
|
|
524
|
-
* (inherited from the base class) wraps this with Promise semantics.
|
|
525
|
-
* Errors routed through `manager.itemError` so the LoadingManager's
|
|
526
|
-
* aggregate state stays accurate.
|
|
527
|
-
*/
|
|
528
|
-
load(url: string, onLoad?: (texture: Texture) => void, _onProgress?: (event: ProgressEvent) => void, onError?: (err: unknown) => void): void;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
408
|
/** One level of a mip chain. 4 bytes per pixel (RGBA8). */
|
|
532
409
|
interface MipLevel {
|
|
533
410
|
data: Uint8ClampedArray;
|
|
@@ -553,4 +430,4 @@ declare function generateMipChain(level0: MipLevel): MipLevel[];
|
|
|
553
430
|
*/
|
|
554
431
|
declare function padToBlockMultiple(level: MipLevel): MipLevel;
|
|
555
432
|
|
|
556
|
-
export { ASTC4x4Encoder, ASTC4x4WebGLEncoder, BC1Encoder, BC1WebGLEncoder, BC5Encoder, BC5WebGLEncoder, BC7Encoder, BC7WebGLEncoder, type Capabilities, type
|
|
433
|
+
export { ASTC4x4Encoder, ASTC4x4WebGLEncoder, BC1Encoder, BC1WebGLEncoder, BC5Encoder, BC5WebGLEncoder, BC7Encoder, BC7WebGLEncoder, type Capabilities, type EncodeBytesResult, type EncodeCallOptions, type EncodeQuality, Encoder, type EncoderConstructor, type EncoderImageSource, type EncoderOptions, type ExtensionProvider, type FeatureProvider, type FormatSelection, type FormatVariant, type MipLevel, type RawPixelSource, type SelectFormatOptions, TextureFormat, type TextureHint, WebGLBlockEncoder, type WebGLCapabilities, type WebGLEncodeBytesResult, type WebGLEncoderConstructor, type WebGLEncoderImageSource, type WebGLEncoderOptions, type WebGLFormatSelection, WebGPUFeature, createWebGLContext, detectCapabilities, detectWebGLCapabilities, generateMipChain, getSharedWebGLContext, isWebGLAvailable, padToBlockMultiple, selectFormat, selectWebGLFormat };
|