gputex 0.3.3 → 0.3.4

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 CHANGED
@@ -46,7 +46,7 @@ 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 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.
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 fast paths use projection assignment and 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
 
@@ -77,17 +77,24 @@ material.map = texture
77
77
 
78
78
  `quality` trades encode speed against compression accuracy:
79
79
 
80
- - **`'fast'` (default)** — a bounding-box endpoint seed plus projection-based
81
- index assignment (each pixel is projected onto the colinear endpoint line in
82
- O(1) instead of searching every palette entry) with a single fused
83
- least-squares refit (accepted per block only when it lowers the error), and
84
- the block bits packed with straight-line constant shifts. On GPUs that
85
- report the `shader-f16` feature the whole fast path (all four formats, BC1
86
- included) runs in f16 — the f32 path is the automatic fallback. Net vs
87
- `'high'` on an Apple GPU: roughly **10–30× faster** depending on format,
88
- for a PSNR cost of **≤0.65 dB on smooth/flat content** (imperceptible) and
89
- up to a few dB on adversarial high-frequency noise, where the bbox seed
90
- trails `'high'`'s exhaustive search. See the benchmark table below.
80
+ - **`'fast'` (default)** — a principal-axis endpoint seed (per-block
81
+ covariance power-iteration — unlike a bbox diagonal it follows
82
+ anti-correlated channels, worth **+2–4 dB on normal-map-like content**)
83
+ plus projection-based index assignment (each pixel is projected onto the
84
+ colinear endpoint line in O(1) instead of searching every palette entry),
85
+ and the block bits packed with straight-line constant shifts. The formats
86
+ with coarse 4-level palettes (BC1, ASTC) and BC5 add a least-squares
87
+ endpoint refit accepted per block only when it lowers the error; BC7's
88
+ 16-level mode-6 palette makes the refit redundant on a principal-axis seed
89
+ (≤0.05 dB), so its fast path skips it and stays the cheapest per pixel. On
90
+ GPUs that report the `shader-f16` feature the whole fast path (all four
91
+ formats, BC1 included) runs in f16 — the f32 path is the automatic
92
+ fallback. Net vs `'high'` on an Apple GPU: roughly **10–30× faster**
93
+ depending on format, for a PSNR cost of **≤0.35 dB on the test cards**
94
+ (BC7 within 0.1 dB of `'high'`; ASTC fast actually measures slightly above
95
+ it) and up to a few dB on adversarial high-frequency noise, where any
96
+ single-line seed trails `'high'`'s exhaustive search. See the benchmark
97
+ table below.
91
98
  - **`'high'`** — exhaustive endpoint search (farthest-pair seed, full nearest
92
99
  search, p-bit search); matches the CPU reference encoders block-for-block
93
100
  (byte-identical on >96% of blocks; the rest are equal-error FP tie-breaks,
@@ -252,35 +259,30 @@ const tex = buildCompressedTexture([bytes], TextureFormat.BC7_SRGB)
252
259
  Measured with the repo's GPU test suite (see below) on an Apple Silicon GPU
253
260
  (`metal-3`) in Chrome, encoding a 2048×2048 image. **GPU pass** is the compute
254
261
  shader alone (WebGPU timestamp queries, median of 20 runs); end-to-end wall
255
- time adds ~3–4 ms of image upload + result readback regardless of format.
256
- "Before" is the shader generation prior to the 2026-07 optimization pass
257
- (projection-based index assignment everywhere, on-the-fly bit packing, an f16
258
- BC1 fast shader, straight-line block assembly).
259
-
260
- | Format | Quality | Shader | GPU pass before | GPU pass after | Speedup |
261
- | -------- | -------------- | ------ | --------------- | -------------- | --------- |
262
- | BC1 | fast (default) | f16 | — (had no f16) | **0.26 ms** | **2.8×**¹ |
263
- | BC1 | fast | f32 | 0.72 ms | 0.33 ms | 2.2× |
264
- | BC5 | fast (default) | f16 | 0.33 ms | **0.20 ms** | 1.7× |
265
- | BC5 | fast | f32 | 0.79 ms | 0.39 ms | 2.0× |
266
- | BC7 | fast (default) | f16 | 1.38 ms | **0.52 ms** | 2.7× |
267
- | BC7 | fast | f32 | 2.65 ms | 1.70 ms | 1.6× |
268
- | ASTC 4×4 | fast (default) | f16 | 0.33 ms | **0.20 ms** | 1.7× |
269
- | ASTC 4×4 | fast | f32 | 0.72 ms | 0.66 ms | 1.1× |
270
- | BC1 | high | f32 | 2.9 ms | 3.0 ms | unchanged |
271
- | BC5 | high | f32 | 3.4 ms | 3.4 ms | unchanged |
272
- | BC7 | high | f32 | 14.1 ms | 14.1 ms | unchanged |
273
- | ASTC 4×4 | high | f32 | 2.6 ms | 2.4 ms | unchanged |
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.
262
+ time adds ~2–4 ms of image upload + result readback regardless of format.
263
+ Each encoder caches its GPU resources (source texture, output/staging
264
+ buffers, bind group) across encodes, so repeated encodes — including mip
265
+ chains — skip per-call allocation: in an interleaved A/B this cuts BC7
266
+ end-to-end wall time by ~10% at 512², ~20% at 1024–2048² and ~35% at 4096².
267
+
268
+ | Format | Quality | Shader | GPU pass |
269
+ | -------- | -------------- | ------ | ----------- |
270
+ | BC1 | fast (default) | f16 | **0.26 ms** |
271
+ | BC1 | fast | f32 | 0.46 ms |
272
+ | BC5 | fast (default) | f16 | **0.26 ms** |
273
+ | BC5 | fast | f32 | 0.26 ms |
274
+ | BC7 | fast (default) | f16 | **0.26 ms** |
275
+ | BC7 | fast | f32 | 0.59 ms |
276
+ | ASTC 4×4 | fast (default) | f16 | **0.26 ms** |
277
+ | ASTC 4×4 | fast | f32 | 0.56 ms |
278
+ | BC1 | high | f32 | 2.7 ms |
279
+ | BC5 | high | f32 | 1.0 ms |
280
+ | BC7 | high | f32 | 10.4 ms |
281
+ | ASTC 4×4 | high | f32 | 1.6 ms |
282
+
283
+ Timestamps are quantised to 100 µs by Chrome and Apple GPU clock states swing
284
+ timings by ~2×, so sub-millisecond figures are indicative (±0.1 ms); compare
285
+ variants only within a single session.
284
286
 
285
287
  ## Testing
286
288
 
@@ -294,6 +296,11 @@ bun run --filter gputex build # build the library the example consumes
294
296
  cd example && bunx next dev # then open http://localhost:3000/test
295
297
  ```
296
298
 
299
+ A second page, `/bench`, measures median end-to-end `encodeToBytes()` wall
300
+ time per format across image sizes (256²–4096²) at fast quality — the
301
+ numbers that matter for runtime streaming, where host overhead dominates
302
+ small textures (results on `window.__GPUTEX_BENCH__`).
303
+
297
304
  The page runs three groups against the live WebGPU device and renders
298
305
  PASS/FAIL tables (machine-readable copy on `window.__GPUTEX_TESTS__`):
299
306
 
package/dist/index.d.ts CHANGED
@@ -126,13 +126,24 @@ declare abstract class Encoder {
126
126
  readonly adapter?: GPUAdapter;
127
127
  readonly ownsDevice: boolean;
128
128
  readonly disableF16: boolean;
129
- protected _module: GPUShaderModule;
129
+ protected _module: GPUShaderModule | null;
130
130
  protected _moduleF16: GPUShaderModule | null;
131
131
  protected _pipelineF16: GPUComputePipeline | null;
132
132
  protected _pipeline: GPUComputePipeline;
133
133
  protected _pipelineCache: Map<EncodeQuality, GPUComputePipeline>;
134
+ private _cachedSrcTex;
135
+ private _cachedSrcW;
136
+ private _cachedSrcH;
137
+ private _cachedDst;
138
+ private _cachedStaging;
139
+ private _cachedParams;
140
+ private _lastParams;
141
+ private _bindGroupCache;
142
+ private _resourcesBusy;
134
143
  constructor({ device, adapter, ownsDevice, disableF16 }: EncoderOptions);
135
144
  protected _buildPipeline(): void;
145
+ /** The f32 module, parsed on first use (see `_module`). */
146
+ protected _ensureModule(): GPUShaderModule;
136
147
  /**
137
148
  * Pipeline for a given quality level. Encoders that don't declare a
138
149
  * `QUALITY_HIGH` override (`supportsQuality === false`, e.g. BC1) ignore the