gputex 0.6.0 → 0.7.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 CHANGED
@@ -83,13 +83,19 @@ a bbox diagonal it follows anti-correlated channels, worth **+2–4 dB on
83
83
  normal-map-like content**) plus projection-based index assignment (each
84
84
  pixel is projected onto the colinear endpoint line in O(1) instead of
85
85
  searching every palette entry), and the block bits packed with
86
- straight-line constant shifts. The formats with coarse 4-level palettes
87
- (BC1, ASTC) add up to two least-squares endpoint refit rounds accepted per
88
- block only when they lower the error, and BC5 one; BC7's 16-level mode-6
89
- palette makes the refit redundant on a principal-axis seed (≤0.05 dB), so
90
- it skips it and stays the cheapest per pixel. On GPUs that report the
91
- `shader-f16` feature everything runs in f16 — the f32 shaders are the
92
- automatic fallback.
86
+ straight-line constant shifts. BC1's coarse 4-level palette adds up to two
87
+ least-squares endpoint refit rounds, solved from per-pass projection
88
+ moments and accepted per block only when they lower the error; its
89
+ near-flat blocks skip the line fit and take the endpoint pair whose ⅔/⅓
90
+ interpolant lands nearest the block colour (direct 565 rounding is up to 4
91
+ levels off — worth up to +3.9 dB on maps with flat regions). BC5 refits
92
+ once; BC7's 16-level mode-6 palette makes the refit redundant on a
93
+ principal-axis seed (≤0.05 dB). ASTC spends every one of its 128 bits: a
94
+ wide-span opaque block gets 16 weight levels with 192-level (trit-coded)
95
+ endpoints, a small-span one exact 8-bit endpoints with 8 levels,
96
+ exactly-grayscale blocks a luminance-only mode with 32 levels. On GPUs that
97
+ report the `shader-f16` feature everything runs in f16 — the f32 shaders
98
+ are the automatic fallback.
93
99
 
94
100
  ETC2 is the exception to the endpoint-line story: its blocks are per-subblock
95
101
  base colours shifted by scalar modifier tables. The encoder exploits the
@@ -109,12 +115,12 @@ stay f32 (they overflow f16), so the two modules produce byte-identical
109
115
  output — f16 buys register pressure on mobile GPUs, not different
110
116
  results.
111
117
 
112
- On the repo's test cards this lands within **≤0.1 dB** of the exhaustive
113
- per-block reference encoders (BC5 matches the reference exactly; ASTC and
114
- BC1-on-normal-maps measure slightly above it), trailing only on adversarial
115
- high-frequency noise, where any single-line seed loses to an exhaustive
116
- search — while encoding an order of magnitude faster. See the benchmark
117
- table below.
118
+ On the repo's test textures this lands within a few tenths of a dB of the
119
+ per-block CPU reference encoders (`gputex/testing`) and above them on
120
+ several (BC5 matches exactly; BC1 on flat content, BC7 and ASTC on some
121
+ maps measure above), trailing only on adversarial high-frequency noise,
122
+ where any single-line seed loses to an exhaustive search — while encoding
123
+ an order of magnitude faster. See the benchmark table below.
118
124
 
119
125
  #### SVG sources
120
126
 
@@ -331,39 +337,46 @@ readback) rather than a round trip per level.
331
337
 
332
338
  ## Benchmarks
333
339
 
334
- Measured with the repo's GPU test suite (see below) on an Apple Silicon GPU
335
- (`metal-3`) in Chrome, encoding a 2048×2048 image. **GPU pass** is the compute
336
- shader alone (WebGPU timestamp queries, median of 20 runs); end-to-end wall
337
- time adds ~2–4 ms of image upload + result readback regardless of format.
338
- Each encoder caches its GPU resources (source texture, output/staging
339
- buffers, bind group) across encodes, so repeated encodes — including mip
340
- chains — skip per-call allocation: in an interleaved A/B this cuts BC7
341
- end-to-end wall time by ~10% at 512², ~20% at 1024–2048² and ~35% at 4096².
340
+ Measured on an Apple Silicon GPU (`metal-3`, M3) in Chrome with the `/eval`
341
+ dev page: per-dispatch compute time from timestamp queries over batches of
342
+ back-to-back dispatches, all variants interleaved in one session, encoding
343
+ the procedural 2048×2048 benchmark image. **GPU pass** is the compute
344
+ shader alone.
342
345
 
343
346
  | Format | Shader | GPU pass |
344
347
  | -------- | ------------- | ----------- |
345
- | BC1 | f16 (default) | **0.26 ms** |
346
- | BC1 | f32 | 0.46 ms |
347
- | BC5 | f16 (default) | **0.26 ms** |
348
- | BC5 | f32 | 0.26 ms |
349
- | BC7 | f16 (default) | **0.26 ms** |
350
- | BC7 | f32 | 0.59 ms |
351
- | ASTC 4×4 | f16 (default) | **0.26 ms** |
352
- | ASTC 4×4 | f32 | 0.56 ms |
348
+ | BC1 | f16 (default) | **0.33 ms** |
349
+ | BC1 | f32 | 0.50 ms |
350
+ | BC5 | f16 (default) | **0.14 ms** |
351
+ | BC5 | f32 | 0.15 ms |
352
+ | BC7 | f16 (default) | **0.19 ms** |
353
+ | BC7 | f32 | 0.52 ms |
354
+ | ASTC 4×4 | f16 (default) | **0.17 ms** |
355
+ | ASTC 4×4 | f32 | 0.28 ms |
353
356
  | ETC2 | f16 + f32 | 0.20 ms |
354
357
 
355
- The ETC2 figure is the interleaved `/ab` harness measurement (batched
356
- dispatches, clock-stable). On a 100 GB/s part just reading the 2048² RGBA8
357
- source costs ~0.15 ms, so the entire selection algorithm adds ~30% on top
358
- of touching the bytes. Two faster variants live in git history and were
359
- deliberately not shipped: a two-pass 2 B/px prepared source (encode pass
358
+ End-to-end `encodeToBytes()` wall time adds the upload and the readback.
359
+ Each encoder caches its GPU resources (source texture, output/staging
360
+ buffers, bind groups) across encodes, and outputs above ~3 MB are encoded
361
+ in row bands — one submission and staging buffer per ~2 MB of output, so
362
+ the readback of one band (a GPU-process copy that dominated large encodes)
363
+ overlaps the compute of the next — with the result array pre-faulted while
364
+ the GPU works. In an interleaved A/B against the single-submission
365
+ readback this cuts wall time by 23–33% at 4096² and 5–25% at 2048²
366
+ (bytes identical); a fresh 4096² encode is then dominated by the ~8 ms
367
+ `copyExternalImageToTexture` upload.
368
+
369
+ On a 100 GB/s part just reading the 2048² RGBA8 source costs ~0.15 ms, so
370
+ BC5/BC7/ASTC/ETC2 sit within ~1.3× of simply touching the bytes; BC1's
371
+ refit rounds keep it ALU-bound. Two faster ETC2 variants live in git
372
+ history and were deliberately not shipped: a two-pass 2 B/px prepared source (encode pass
360
373
  0.115 ms, but the prep pass is also bandwidth-bound and cannot overlap, so
361
374
  the per-texture total regressed) and an O(1) hedged table pick (−3% for
362
375
  −0.5 dB — a poor trade against the scored search).
363
376
 
364
- Timestamps are quantised to 100 µs by Chrome and Apple GPU clock states swing
365
- timings by ~2×, so sub-millisecond figures are indicative (±0.1 ms); compare
366
- variants only within a single session.
377
+ Single-dispatch timestamps are coarse and Apple GPU clock states swing
378
+ timings by up to ~2× across page loads, so compare variants only within a
379
+ single session, interleaved (as `/eval` and `/ab` do).
367
380
 
368
381
  ## Testing
369
382
 
@@ -382,6 +395,19 @@ time per format across image sizes (256²–4096²) — the numbers that matter
382
395
  for runtime streaming, where host overhead dominates small textures
383
396
  (results on `window.__GPUTEX_BENCH__`).
384
397
 
398
+ For shader work, `/eval` compares WGSL variants on speed AND quality in one
399
+ session: `example/scripts/ab-sync.sh` snapshots the working-tree shaders
400
+ (`<fmt>_work`) and git HEAD's (`<fmt>_head`) into `example/public/ab/`, and
401
+ `/eval?shaders=bc7_f16_head,bc7_f16_work` times them interleaved and scores
402
+ each image through the GPU's own hardware decoder (the encoded blocks are
403
+ copied into a real compressed texture and sampled), reporting PSNR, blocks
404
+ that got worse/better, the worst regression on blocks the first variant
405
+ encodes near-losslessly, and byte-identical coverage — over the full
406
+ 1K/2K/4K texture corpus in seconds (`window.__GPUTEX_EVAL__`). For host-side
407
+ changes, `example/scripts/ab-lib.sh` builds git HEAD's library and the
408
+ working tree side by side (`/ab/gputex_head.js`, `/ab/gputex_work.js`) so
409
+ both can be driven in one page with alternating calls.
410
+
385
411
  The page runs three groups against the live WebGPU device and renders
386
412
  PASS/FAIL tables (machine-readable copy on `window.__GPUTEX_TESTS__`):
387
413
 
package/dist/index.d.ts CHANGED
@@ -165,10 +165,10 @@ declare abstract class Encoder {
165
165
  private _cachedSrcSource;
166
166
  private _cachedSrcFlipY;
167
167
  private _cachedDst;
168
- private _cachedStaging;
168
+ private _cachedStagings;
169
169
  private _cachedParams;
170
- private _lastParams;
171
- private _cachedBindGroup;
170
+ private _lastParamsKey;
171
+ private _cachedBindGroups;
172
172
  private _cachedPrepPlanes;
173
173
  private _cachedPrepBindGroup;
174
174
  private _resourcesBusy;
@@ -182,8 +182,9 @@ declare abstract class Encoder {
182
182
  private _chainPrepBindGroups;
183
183
  private _chainParams;
184
184
  private _chainBindGroups;
185
+ private _chainBandBindGroups;
185
186
  private _chainDst;
186
- private _chainStaging;
187
+ private _chainStagings;
187
188
  private _chainBusy;
188
189
  constructor({ device, adapter, ownsDevice, disableF16 }: EncoderOptions);
189
190
  protected _buildPipeline(): void;
@@ -295,10 +296,17 @@ declare abstract class Encoder {
295
296
  /** Block-grid geometry + packed output offsets for a chain of levels.
296
297
  * `byteSpan` is both the dst buffer size and the readback copy size (a
297
298
  * multiple of 4: byteLen is a multiple of bytesPerBlock ≥ 8, offsets are
298
- * CHAIN_ALIGN-ed). */
299
+ * CHAIN_ALIGN-ed). `bands` splits the base level into row bands. */
299
300
  private _chainGeometry;
300
- /** Shared chain-encode tail: one compute pass with a dispatch per level,
301
- * one submit, one staging readback sliced into per-level byte arrays. */
301
+ /** One uniform buffer, one write: level i's { blocksX, blocksY, width,
302
+ * height, y0 = 0 } at slot i, then base-level band b at slot
303
+ * levels + b (y0 = the band's first block row). Slots are CHAIN_ALIGN
304
+ * apart. */
305
+ private _createChainParams;
306
+ /** Shared chain-encode tail: one submission per base-level row band —
307
+ * band b dispatches its rows of level 0, the last band also every tail
308
+ * level — each read back through its own staging buffer as it lands,
309
+ * then sliced into per-level byte arrays. */
302
310
  private _submitChainAndRead;
303
311
  /** Create the query set + resolve/staging buffers for one timed
304
312
  * submission, or null when the device lacks 'timestamp-query'. */