gputex 0.6.0 → 0.8.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 +86 -55
- package/dist/index.d.ts +15 -7
- package/dist/index.js +533 -587
- package/dist/testing.d.ts +8 -5
- package/dist/testing.js +194 -36
- package/dist/three.js +533 -587
- package/package.json +1 -1
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.
|
|
87
|
-
|
|
88
|
-
block only when they lower the error
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
@@ -97,24 +103,26 @@ algebra of that scalar shift — table and index selection depend only on each
|
|
|
97
103
|
texel's luma-sum difference from the base, exactly (modulo decode clamping) —
|
|
98
104
|
so the whole 8-table × 4-modifier search collapses to a handful of scalar
|
|
99
105
|
threshold tests against a two-candidate table shortlist, with subblock error
|
|
100
|
-
constants and the flip preselect computed O(1) from quadrant sums
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
constants and the flip preselect computed O(1) from quadrant sums (exactly
|
|
107
|
+
gray blocks, which give the preselect nothing to go on, score both flips on
|
|
108
|
+
a one-channel path). A closed-form least-squares fit of ETC2's planar mode
|
|
109
|
+
(which rescues the smooth gradients ETC1-style blocks band on) completes
|
|
110
|
+
the block, driven by the same estimates. There is no base-colour refit
|
|
111
|
+
(~0.2 dB on photographic content for ≥13% GPU). The kernel reads the source
|
|
112
|
+
through `textureGather` and keeps every per-texel quantity in registers
|
|
113
|
+
with constant indexing (numbers below). Its f16 module is EXACT-VALUE: lumas,
|
|
114
|
+
D values and thresholds are integers (or halves) f16 represents exactly,
|
|
115
|
+
while the sums and estimates stay f32 (they overflow f16), so the two
|
|
116
|
+
modules produce byte-identical output wherever the sampler's unorm
|
|
117
|
+
conversion is exact (verified on Apple) — f16 buys register space, not
|
|
118
|
+
different results.
|
|
119
|
+
|
|
120
|
+
On the repo's test textures this lands within a few tenths of a dB of the
|
|
121
|
+
per-block CPU reference encoders (`gputex/testing`) and above them on
|
|
122
|
+
several (BC5 matches exactly; BC1 on flat content, BC7 and ASTC on some
|
|
123
|
+
maps measure above), trailing only on adversarial high-frequency noise,
|
|
124
|
+
where any single-line seed loses to an exhaustive search — while encoding
|
|
125
|
+
an order of magnitude faster. See the benchmark table below.
|
|
118
126
|
|
|
119
127
|
#### SVG sources
|
|
120
128
|
|
|
@@ -331,39 +339,49 @@ readback) rather than a round trip per level.
|
|
|
331
339
|
|
|
332
340
|
## Benchmarks
|
|
333
341
|
|
|
334
|
-
Measured
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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².
|
|
342
|
+
Measured on an Apple Silicon GPU (`metal-3`, M3) in Chrome with the `/eval`
|
|
343
|
+
dev page: per-dispatch compute time from timestamp queries over batches of
|
|
344
|
+
back-to-back dispatches, all variants interleaved in one session, encoding
|
|
345
|
+
the procedural 2048×2048 benchmark image. **GPU pass** is the compute
|
|
346
|
+
shader alone.
|
|
342
347
|
|
|
343
348
|
| Format | Shader | GPU pass |
|
|
344
349
|
| -------- | ------------- | ----------- |
|
|
345
|
-
| BC1 | f16 (default) | **0.
|
|
346
|
-
| BC1 | f32 | 0.
|
|
347
|
-
| BC5 | f16 (default) | **0.
|
|
348
|
-
| BC5 | f32 | 0.
|
|
349
|
-
| BC7 | f16 (default) | **0.
|
|
350
|
-
| BC7 | f32 | 0.
|
|
351
|
-
| ASTC 4×4 | f16 (default) | **0.
|
|
352
|
-
| ASTC 4×4 | f32 | 0.
|
|
353
|
-
| ETC2 | f16
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
the
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
350
|
+
| BC1 | f16 (default) | **0.33 ms** |
|
|
351
|
+
| BC1 | f32 | 0.50 ms |
|
|
352
|
+
| BC5 | f16 (default) | **0.14 ms** |
|
|
353
|
+
| BC5 | f32 | 0.15 ms |
|
|
354
|
+
| BC7 | f16 (default) | **0.19 ms** |
|
|
355
|
+
| BC7 | f32 | 0.52 ms |
|
|
356
|
+
| ASTC 4×4 | f16 (default) | **0.17 ms** |
|
|
357
|
+
| ASTC 4×4 | f32 | 0.28 ms |
|
|
358
|
+
| ETC2 | f16 (default) | **0.14 ms** |
|
|
359
|
+
| ETC2 | f32 | 0.15 ms |
|
|
360
|
+
|
|
361
|
+
End-to-end `encodeToBytes()` wall time adds the upload and the readback.
|
|
362
|
+
Each encoder caches its GPU resources (source texture, output/staging
|
|
363
|
+
buffers, bind groups) across encodes, and outputs above ~3 MB are encoded
|
|
364
|
+
in row bands — one submission and staging buffer per ~2 MB of output, so
|
|
365
|
+
the readback of one band (a GPU-process copy that dominated large encodes)
|
|
366
|
+
overlaps the compute of the next — with the result array pre-faulted while
|
|
367
|
+
the GPU works. In an interleaved A/B against the single-submission
|
|
368
|
+
readback this cuts wall time by 23–33% at 4096² and 5–25% at 2048²
|
|
369
|
+
(bytes identical); a fresh 4096² encode is then dominated by the ~8 ms
|
|
370
|
+
`copyExternalImageToTexture` upload.
|
|
371
|
+
|
|
372
|
+
On a 100 GB/s part just reading the 2048² RGBA8 source costs ~0.14 ms, so
|
|
373
|
+
BC5/BC7/ASTC sit within ~1.3× of simply touching the bytes and ETC2 at it;
|
|
374
|
+
BC1's refit rounds keep it ALU-bound. On real textures (which Apple's
|
|
375
|
+
lossless framebuffer compression makes cheaper to read) and at 1024², where
|
|
376
|
+
the source stays cached, the ETC2 kernel is ALU-exposed again: 0.035–0.04 ms
|
|
377
|
+
at 1024², 0.13–0.15 ms at 2048² and 0.49–0.57 ms at 4096² across the corpus.
|
|
378
|
+
A two-pass 2 B/px prepared-source ETC2 variant lives in git history and was
|
|
379
|
+
not shipped: its prep pass is also bandwidth-bound and cannot overlap, so
|
|
380
|
+
the per-texture total regressed.
|
|
381
|
+
|
|
382
|
+
Single-dispatch timestamps are coarse and Apple GPU clock states swing
|
|
383
|
+
timings by up to ~2× across page loads, so compare variants only within a
|
|
384
|
+
single session, interleaved (as `/eval` and `/ab` do).
|
|
367
385
|
|
|
368
386
|
## Testing
|
|
369
387
|
|
|
@@ -382,6 +400,19 @@ time per format across image sizes (256²–4096²) — the numbers that matter
|
|
|
382
400
|
for runtime streaming, where host overhead dominates small textures
|
|
383
401
|
(results on `window.__GPUTEX_BENCH__`).
|
|
384
402
|
|
|
403
|
+
For shader work, `/eval` compares WGSL variants on speed AND quality in one
|
|
404
|
+
session: `example/scripts/ab-sync.sh` snapshots the working-tree shaders
|
|
405
|
+
(`<fmt>_work`) and git HEAD's (`<fmt>_head`) into `example/public/ab/`, and
|
|
406
|
+
`/eval?shaders=bc7_f16_head,bc7_f16_work` times them interleaved and scores
|
|
407
|
+
each image through the GPU's own hardware decoder (the encoded blocks are
|
|
408
|
+
copied into a real compressed texture and sampled), reporting PSNR, blocks
|
|
409
|
+
that got worse/better, the worst regression on blocks the first variant
|
|
410
|
+
encodes near-losslessly, and byte-identical coverage — over the full
|
|
411
|
+
1K/2K/4K texture corpus in seconds (`window.__GPUTEX_EVAL__`). For host-side
|
|
412
|
+
changes, `example/scripts/ab-lib.sh` builds git HEAD's library and the
|
|
413
|
+
working tree side by side (`/ab/gputex_head.js`, `/ab/gputex_work.js`) so
|
|
414
|
+
both can be driven in one page with alternating calls.
|
|
415
|
+
|
|
385
416
|
The page runs three groups against the live WebGPU device and renders
|
|
386
417
|
PASS/FAIL tables (machine-readable copy on `window.__GPUTEX_TESTS__`):
|
|
387
418
|
|
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
|
|
168
|
+
private _cachedStagings;
|
|
169
169
|
private _cachedParams;
|
|
170
|
-
private
|
|
171
|
-
private
|
|
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
|
|
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
|
-
/**
|
|
301
|
-
*
|
|
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'. */
|