gputex 0.3.3 → 0.4.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 +64 -67
- package/dist/index.d.ts +16 -38
- package/dist/index.js +589 -350
- package/dist/three.d.ts +3 -15
- package/dist/three.js +595 -360
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,14 +46,14 @@ alpha or a normal map.
|
|
|
46
46
|
|
|
47
47
|
## WebGL fallback
|
|
48
48
|
|
|
49
|
-
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
|
|
49
|
+
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 two backends are not byte-identical (the WebGPU shaders use f16 where available), but they implement the same algorithms at the same quality level and the resulting `CompressedTexture` looks the same under either renderer.
|
|
50
50
|
|
|
51
51
|
The fallback chain is **WebGPU → WebGL2 → uncompressed RGBA8**. The `backend` field on the result (`'webgpu' | 'webgl' | 'none'`) tells you which path ran.
|
|
52
52
|
|
|
53
53
|
Notes on the WebGL path:
|
|
54
54
|
|
|
55
55
|
- It needs the matching WebGL2 compressed-texture extension to be sampleable: `EXT_texture_compression_bptc` (BC7), `EXT_texture_compression_rgtc` (BC5), `WEBGL_compressed_texture_astc` (ASTC), or `WEBGL_compressed_texture_s3tc` (BC1). Selection mirrors the WebGPU side, with BC1 added as a broadly-available last resort for **opaque** colour when neither BPTC nor ASTC is present.
|
|
56
|
-
-
|
|
56
|
+
- The `device` / `adapter` options apply to the WebGPU path only.
|
|
57
57
|
- All encoding happens on one shared, off-screen WebGL2 context; nothing is drawn to a visible canvas.
|
|
58
58
|
|
|
59
59
|
## Usage
|
|
@@ -67,31 +67,33 @@ const { texture, format } = await compressTexture('/cobblestone.avif', {
|
|
|
67
67
|
hint: 'color', // 'color' | 'colorWithAlpha' | 'normal'
|
|
68
68
|
colorSpace: 'srgb',
|
|
69
69
|
mipmaps: true,
|
|
70
|
-
quality: 'fast', // 'fast' (default) | 'high'
|
|
71
70
|
})
|
|
72
71
|
|
|
73
72
|
material.map = texture
|
|
74
73
|
```
|
|
75
74
|
|
|
76
|
-
####
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
#### The encoding algorithm
|
|
76
|
+
|
|
77
|
+
There is a single encode mode, built to be both fast and high quality: a
|
|
78
|
+
principal-axis endpoint seed (per-block covariance power-iteration — unlike
|
|
79
|
+
a bbox diagonal it follows anti-correlated channels, worth **+2–4 dB on
|
|
80
|
+
normal-map-like content**) plus projection-based index assignment (each
|
|
81
|
+
pixel is projected onto the colinear endpoint line in O(1) instead of
|
|
82
|
+
searching every palette entry), and the block bits packed with
|
|
83
|
+
straight-line constant shifts. The formats with coarse 4-level palettes
|
|
84
|
+
(BC1, ASTC) add up to two least-squares endpoint refit rounds accepted per
|
|
85
|
+
block only when they lower the error, and BC5 one; BC7's 16-level mode-6
|
|
86
|
+
palette makes the refit redundant on a principal-axis seed (≤0.05 dB), so
|
|
87
|
+
it skips it and stays the cheapest per pixel. On GPUs that report the
|
|
88
|
+
`shader-f16` feature everything runs in f16 — the f32 shaders are the
|
|
89
|
+
automatic fallback.
|
|
90
|
+
|
|
91
|
+
On the repo's test cards this lands within **≤0.1 dB** of the exhaustive
|
|
92
|
+
per-block reference encoders (BC5 matches the reference exactly; ASTC and
|
|
93
|
+
BC1-on-normal-maps measure slightly above it), trailing only on adversarial
|
|
94
|
+
high-frequency noise, where any single-line seed loses to an exhaustive
|
|
95
|
+
search — while encoding an order of magnitude faster. See the benchmark
|
|
96
|
+
table below.
|
|
95
97
|
|
|
96
98
|
#### SVG sources
|
|
97
99
|
|
|
@@ -252,35 +254,26 @@ const tex = buildCompressedTexture([bytes], TextureFormat.BC7_SRGB)
|
|
|
252
254
|
Measured with the repo's GPU test suite (see below) on an Apple Silicon GPU
|
|
253
255
|
(`metal-3`) in Chrome, encoding a 2048×2048 image. **GPU pass** is the compute
|
|
254
256
|
shader alone (WebGPU timestamp queries, median of 20 runs); end-to-end wall
|
|
255
|
-
time adds ~
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
|
262
|
-
|
|
|
263
|
-
| BC1 |
|
|
264
|
-
|
|
|
265
|
-
| BC5 |
|
|
266
|
-
|
|
|
267
|
-
| BC7 |
|
|
268
|
-
|
|
|
269
|
-
| ASTC 4×4 |
|
|
270
|
-
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
¹ vs the old f32 fast shader, which was the only BC1 fast path before. The
|
|
276
|
-
BC1 rows were measured with old and new pipelines interleaved in one session
|
|
277
|
-
(the most noise-robust method); the others are cross-run suite medians.
|
|
278
|
-
|
|
279
|
-
Per-quadrant PSNR on the committed 512² test card is equal to or better than
|
|
280
|
-
the previous fast encoders everywhere (flat tiles bit-identical, gradients
|
|
281
|
-
+0.01 dB, noise −0.01 dB); the `high` paths still match the CPU reference
|
|
282
|
-
encoders. Timestamps are quantised to 100 µs by Chrome, so sub-millisecond
|
|
283
|
-
figures are ±0.05–0.1 ms.
|
|
257
|
+
time adds ~2–4 ms of image upload + result readback regardless of format.
|
|
258
|
+
Each encoder caches its GPU resources (source texture, output/staging
|
|
259
|
+
buffers, bind group) across encodes, so repeated encodes — including mip
|
|
260
|
+
chains — skip per-call allocation: in an interleaved A/B this cuts BC7
|
|
261
|
+
end-to-end wall time by ~10% at 512², ~20% at 1024–2048² and ~35% at 4096².
|
|
262
|
+
|
|
263
|
+
| Format | Shader | GPU pass |
|
|
264
|
+
| -------- | ------------- | ----------- |
|
|
265
|
+
| BC1 | f16 (default) | **0.26 ms** |
|
|
266
|
+
| BC1 | f32 | 0.46 ms |
|
|
267
|
+
| BC5 | f16 (default) | **0.26 ms** |
|
|
268
|
+
| BC5 | f32 | 0.26 ms |
|
|
269
|
+
| BC7 | f16 (default) | **0.26 ms** |
|
|
270
|
+
| BC7 | f32 | 0.59 ms |
|
|
271
|
+
| ASTC 4×4 | f16 (default) | **0.26 ms** |
|
|
272
|
+
| ASTC 4×4 | f32 | 0.56 ms |
|
|
273
|
+
|
|
274
|
+
Timestamps are quantised to 100 µs by Chrome and Apple GPU clock states swing
|
|
275
|
+
timings by ~2×, so sub-millisecond figures are indicative (±0.1 ms); compare
|
|
276
|
+
variants only within a single session.
|
|
284
277
|
|
|
285
278
|
## Testing
|
|
286
279
|
|
|
@@ -294,28 +287,32 @@ bun run --filter gputex build # build the library the example consumes
|
|
|
294
287
|
cd example && bunx next dev # then open http://localhost:3000/test
|
|
295
288
|
```
|
|
296
289
|
|
|
290
|
+
A second page, `/bench`, measures median end-to-end `encodeToBytes()` wall
|
|
291
|
+
time per format across image sizes (256²–4096²) — the numbers that matter
|
|
292
|
+
for runtime streaming, where host overhead dominates small textures
|
|
293
|
+
(results on `window.__GPUTEX_BENCH__`).
|
|
294
|
+
|
|
297
295
|
The page runs three groups against the live WebGPU device and renders
|
|
298
296
|
PASS/FAIL tables (machine-readable copy on `window.__GPUTEX_TESTS__`):
|
|
299
297
|
|
|
300
|
-
- **Correctness** —
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
aggregate PSNR
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
encodes near-losslessly) must not exceed `'high'`'s error by more than a
|
|
313
|
-
small per-format limit.
|
|
298
|
+
- **Correctness** — determinism (same input twice → identical bytes) and the
|
|
299
|
+
clamp-to-edge padding path: a non-multiple-of-4 image must land within a
|
|
300
|
+
couple of dB of the exhaustive CPU reference encode (`gputex/testing`) — a
|
|
301
|
+
padding bug craters it.
|
|
302
|
+
- **Quality** — GPU output is CPU-decoded and validated on the FULL 1024²
|
|
303
|
+
test cards (every tile stresses a different failure mode) with two gates,
|
|
304
|
+
for both the f16 and (force-disabled-f16) f32 shaders: aggregate PSNR must
|
|
305
|
+
beat per-format thresholds pinned ~0.15 dB under the measured baseline,
|
|
306
|
+
and — because a handful of catastrophically wrong blocks barely moves
|
|
307
|
+
aggregate PSNR — the worst _easy_ block (one the exhaustive CPU reference
|
|
308
|
+
encodes near-losslessly) must not exceed the reference's error by more
|
|
309
|
+
than a small per-format limit.
|
|
314
310
|
- **Performance** — the benchmark table above: wall + GPU-pass time per
|
|
315
|
-
format ×
|
|
311
|
+
format × shader variant.
|
|
316
312
|
|
|
317
313
|
The `gputex/testing` entry point exports the CPU reference
|
|
318
|
-
encoders/decoders (`encodeBC7Mode6Block`, `decodeASTC4x4Block`, …)
|
|
314
|
+
encoders/decoders (`encodeBC7Mode6Block`, `decodeASTC4x4Block`, …) — the
|
|
315
|
+
exhaustive per-block yardstick the GPU shaders are gated against — so any
|
|
319
316
|
consumer can run the same validation.
|
|
320
317
|
|
|
321
318
|
## Requirements
|
package/dist/index.d.ts
CHANGED
|
@@ -49,19 +49,9 @@ interface EncoderOptions {
|
|
|
49
49
|
*/
|
|
50
50
|
disableF16?: boolean;
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
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.
|
|
58
|
-
*/
|
|
59
|
-
type EncodeQuality = 'fast' | 'high';
|
|
60
52
|
interface EncodeCallOptions {
|
|
61
53
|
/** Tags the output color space. Forced 'linear' for encoders with supportsSrgb=false. */
|
|
62
54
|
colorSpace?: 'srgb' | 'linear';
|
|
63
|
-
/** Encode quality / speed trade-off. Default 'fast'. */
|
|
64
|
-
quality?: EncodeQuality;
|
|
65
55
|
}
|
|
66
56
|
/**
|
|
67
57
|
* Result of a raw bytes-only encode. This is the encoder's native output: the
|
|
@@ -126,19 +116,18 @@ declare abstract class Encoder {
|
|
|
126
116
|
readonly adapter?: GPUAdapter;
|
|
127
117
|
readonly ownsDevice: boolean;
|
|
128
118
|
readonly disableF16: boolean;
|
|
129
|
-
protected _module: GPUShaderModule;
|
|
130
|
-
protected _moduleF16: GPUShaderModule | null;
|
|
131
|
-
protected _pipelineF16: GPUComputePipeline | null;
|
|
132
119
|
protected _pipeline: GPUComputePipeline;
|
|
133
|
-
|
|
120
|
+
private _cachedSrcTex;
|
|
121
|
+
private _cachedSrcW;
|
|
122
|
+
private _cachedSrcH;
|
|
123
|
+
private _cachedDst;
|
|
124
|
+
private _cachedStaging;
|
|
125
|
+
private _cachedParams;
|
|
126
|
+
private _lastParams;
|
|
127
|
+
private _cachedBindGroup;
|
|
128
|
+
private _resourcesBusy;
|
|
134
129
|
constructor({ device, adapter, ownsDevice, disableF16 }: EncoderOptions);
|
|
135
130
|
protected _buildPipeline(): void;
|
|
136
|
-
/**
|
|
137
|
-
* Pipeline for a given quality level. Encoders that don't declare a
|
|
138
|
-
* `QUALITY_HIGH` override (`supportsQuality === false`, e.g. BC1) ignore the
|
|
139
|
-
* argument and reuse the single pipeline. Specialised pipelines are cached.
|
|
140
|
-
*/
|
|
141
|
-
protected _getPipeline(quality: EncodeQuality): GPUComputePipeline;
|
|
142
131
|
destroy(): void;
|
|
143
132
|
/** Short lowercase identifier used in GPU object labels and errors. */
|
|
144
133
|
abstract get label(): string;
|
|
@@ -149,20 +138,14 @@ declare abstract class Encoder {
|
|
|
149
138
|
/** Whether this format has an sRGB variant. Default true. */
|
|
150
139
|
get supportsSrgb(): boolean;
|
|
151
140
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*/
|
|
156
|
-
get supportsQuality(): boolean;
|
|
157
|
-
/**
|
|
158
|
-
* Optional f16 WGSL for the 'fast' path. Used only when the device reports the
|
|
159
|
-
* `shader-f16` feature; the format's f32 `wgslSource()` is the fallback and
|
|
160
|
-
* `'high'` always uses it. Returns null when there's no f16 variant.
|
|
141
|
+
* Optional f16 WGSL variant. Used only when the device reports the
|
|
142
|
+
* `shader-f16` feature; the format's f32 `wgslSource()` is the automatic
|
|
143
|
+
* fallback. Returns null when there's no f16 variant.
|
|
161
144
|
*/
|
|
162
145
|
wgslSourceFastF16(): string | null;
|
|
163
|
-
/** Whether the f16
|
|
146
|
+
/** Whether the f16 shader is both available and supported on this device. */
|
|
164
147
|
protected get _useF16(): boolean;
|
|
165
|
-
/** WGSL compute-shader source. */
|
|
148
|
+
/** WGSL compute-shader source (f32; the fallback when f16 is unavailable). */
|
|
166
149
|
abstract wgslSource(): string;
|
|
167
150
|
/** e.g. 'bc1-rgba-unorm-srgb'. */
|
|
168
151
|
abstract gpuTextureFormat(opts: FormatVariant): GPUTextureFormat;
|
|
@@ -179,9 +162,8 @@ declare abstract class Encoder {
|
|
|
179
162
|
* bytes into a `CompressedTexture`; callers targeting another engine feed
|
|
180
163
|
* `data` into that engine's compressed-texture upload directly.
|
|
181
164
|
*/
|
|
182
|
-
encodeToBytes(source: EncoderImageSource, { flipY,
|
|
165
|
+
encodeToBytes(source: EncoderImageSource, { flipY, withGpuTime }?: {
|
|
183
166
|
flipY?: boolean;
|
|
184
|
-
quality?: EncodeQuality;
|
|
185
167
|
withGpuTime?: boolean;
|
|
186
168
|
}): Promise<EncodeBytesResult>;
|
|
187
169
|
}
|
|
@@ -192,7 +174,6 @@ declare class BC1Encoder extends Encoder {
|
|
|
192
174
|
get label(): string;
|
|
193
175
|
get bytesPerBlock(): number;
|
|
194
176
|
get supportsSrgb(): boolean;
|
|
195
|
-
get supportsQuality(): boolean;
|
|
196
177
|
wgslSource(): string;
|
|
197
178
|
wgslSourceFastF16(): string | null;
|
|
198
179
|
gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
|
|
@@ -204,7 +185,6 @@ declare class BC5Encoder extends Encoder {
|
|
|
204
185
|
get label(): string;
|
|
205
186
|
get bytesPerBlock(): number;
|
|
206
187
|
get supportsSrgb(): boolean;
|
|
207
|
-
get supportsQuality(): boolean;
|
|
208
188
|
wgslSource(): string;
|
|
209
189
|
wgslSourceFastF16(): string;
|
|
210
190
|
gpuTextureFormat(): GPUTextureFormat;
|
|
@@ -216,7 +196,6 @@ declare class BC7Encoder extends Encoder {
|
|
|
216
196
|
get label(): string;
|
|
217
197
|
get bytesPerBlock(): number;
|
|
218
198
|
get supportsSrgb(): boolean;
|
|
219
|
-
get supportsQuality(): boolean;
|
|
220
199
|
wgslSource(): string;
|
|
221
200
|
wgslSourceFastF16(): string;
|
|
222
201
|
gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
|
|
@@ -228,7 +207,6 @@ declare class ASTC4x4Encoder extends Encoder {
|
|
|
228
207
|
get label(): string;
|
|
229
208
|
get bytesPerBlock(): number;
|
|
230
209
|
get supportsSrgb(): boolean;
|
|
231
|
-
get supportsQuality(): boolean;
|
|
232
210
|
wgslSource(): string;
|
|
233
211
|
wgslSourceFastF16(): string;
|
|
234
212
|
gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
|
|
@@ -463,4 +441,4 @@ interface RasterizeSvgOptions {
|
|
|
463
441
|
*/
|
|
464
442
|
declare function rasterizeSvg(source: string | Blob, options?: RasterizeSvgOptions): Promise<ImageBitmap>;
|
|
465
443
|
|
|
466
|
-
export { ASTC4x4Encoder, ASTC4x4WebGLEncoder, BC1Encoder, BC1WebGLEncoder, BC5Encoder, BC5WebGLEncoder, BC7Encoder, BC7WebGLEncoder, type Capabilities, type EncodeBytesResult, type EncodeCallOptions,
|
|
444
|
+
export { ASTC4x4Encoder, ASTC4x4WebGLEncoder, BC1Encoder, BC1WebGLEncoder, BC5Encoder, BC5WebGLEncoder, BC7Encoder, BC7WebGLEncoder, type Capabilities, type EncodeBytesResult, type EncodeCallOptions, Encoder, type EncoderConstructor, type EncoderImageSource, type EncoderOptions, type ExtensionProvider, type FeatureProvider, type FormatSelection, type FormatVariant, type MipLevel, type PreferredFormat, type RasterizeSvgOptions, type RawPixelSource, type SelectFormatOptions, type SvgRasterSize, TextureFormat, type TextureHint, WebGLBlockEncoder, type WebGLCapabilities, type WebGLEncodeBytesResult, type WebGLEncoderConstructor, type WebGLEncoderImageSource, type WebGLEncoderOptions, type WebGLFormatSelection, WebGPUFeature, createWebGLContext, detectCapabilities, detectWebGLCapabilities, generateMipChain, getSharedWebGLContext, isWebGLAvailable, padToBlockMultiple, rasterizeSvg, selectFormat, selectWebGLFormat };
|