gputex 0.3.4 → 0.5.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/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ declare const TextureFormat: {
6
6
  readonly BC7_SRGB: "BC7_SRGB";
7
7
  readonly ASTC_4x4: "ASTC_4x4";
8
8
  readonly ASTC_4x4_SRGB: "ASTC_4x4_SRGB";
9
+ readonly ETC2_RGB8: "ETC2_RGB8";
10
+ readonly ETC2_RGB8_SRGB: "ETC2_RGB8_SRGB";
9
11
  };
10
12
  type TextureFormat = (typeof TextureFormat)[keyof typeof TextureFormat];
11
13
  declare const WebGPUFeature: {
@@ -33,6 +35,31 @@ interface Capabilities {
33
35
  }
34
36
  declare function detectCapabilities(adapter: FeatureProvider): Capabilities;
35
37
 
38
+ /** One level of a mip chain. 4 bytes per pixel (RGBA8). */
39
+ interface MipLevel {
40
+ data: Uint8ClampedArray;
41
+ width: number;
42
+ height: number;
43
+ }
44
+ /**
45
+ * Produce the full mip chain from a level-0 image. The chain goes down
46
+ * to a 1×1 level — the standard OpenGL / WebGPU convention — so the
47
+ * caller gets `floor(log2(max(w, h))) + 1` levels total.
48
+ *
49
+ * Levels whose logical dimensions are below the encoder's 4×4 block
50
+ * grid are still produced here at their true logical size; padding up
51
+ * to a single block is the encoder's job, not ours.
52
+ */
53
+ declare function generateMipChain(level0: MipLevel): MipLevel[];
54
+ /**
55
+ * Pad a mip level up to a multiple of 4 in each dimension using clamp-
56
+ * to-edge sampling. Used before handing sub-4×4 levels to a block-
57
+ * compression encoder, which requires at least one full block per level.
58
+ *
59
+ * If the input is already block-aligned this returns the input unchanged.
60
+ */
61
+ declare function padToBlockMultiple(level: MipLevel): MipLevel;
62
+
36
63
  /**
37
64
  * Anything `GPUQueue.copyExternalImageToTexture` accepts. Matches the
38
65
  * WebGPU spec's CopyExternalImageSource set.
@@ -49,32 +76,26 @@ interface EncoderOptions {
49
76
  */
50
77
  disableF16?: boolean;
51
78
  }
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
79
  interface EncodeCallOptions {
61
80
  /** Tags the output color space. Forced 'linear' for encoders with supportsSrgb=false. */
62
81
  colorSpace?: 'srgb' | 'linear';
63
- /** Encode quality / speed trade-off. Default 'fast'. */
64
- quality?: EncodeQuality;
65
82
  }
66
83
  /**
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`.
84
+ * One level of encoded output: the compressed block bytes plus logical and
85
+ * block-aligned dimensions. This is the encoder's native output shape, with
86
+ * no Three.js (or any engine) involvement — feed `data` into whatever
87
+ * renderer's compressed-texture upload you like, or use
88
+ * `buildCompressedTexture()` from `gputex/three`.
71
89
  */
72
- interface EncodeBytesResult {
90
+ interface EncodedLevelBytes {
73
91
  width: number;
74
92
  height: number;
75
93
  paddedWidth: number;
76
94
  paddedHeight: number;
77
95
  data: Uint8Array;
96
+ }
97
+ /** Result of a raw bytes-only single-image encode. */
98
+ interface EncodeBytesResult extends EncodedLevelBytes {
78
99
  encodeMs: number;
79
100
  /**
80
101
  * GPU-side compute-pass time in ms, measured with timestamp queries.
@@ -85,6 +106,16 @@ interface EncodeBytesResult {
85
106
  */
86
107
  gpuMs?: number;
87
108
  }
109
+ /** Result of a whole-chain encode — see `Encoder.encodeMipChainToBytes()`. */
110
+ interface EncodeMipChainResult {
111
+ /** Encoded levels in the order given; `levels[0]` is the base level. */
112
+ levels: EncodedLevelBytes[];
113
+ /** Wall-clock time for the whole chain: uploads → dispatches → readback. */
114
+ encodeMs: number;
115
+ /** GPU compute time for the whole chain's single compute pass (see
116
+ * `EncodeBytesResult.gpuMs` for caveats). */
117
+ gpuMs?: number;
118
+ }
88
119
  interface FormatVariant {
89
120
  colorSpace: 'srgb' | 'linear';
90
121
  }
@@ -126,55 +157,87 @@ declare abstract class Encoder {
126
157
  readonly adapter?: GPUAdapter;
127
158
  readonly ownsDevice: boolean;
128
159
  readonly disableF16: boolean;
129
- protected _module: GPUShaderModule | null;
130
- protected _moduleF16: GPUShaderModule | null;
131
- protected _pipelineF16: GPUComputePipeline | null;
132
- protected _pipeline: GPUComputePipeline;
133
- protected _pipelineCache: Map<EncodeQuality, GPUComputePipeline>;
160
+ protected _pipelineReady: Promise<GPUComputePipeline>;
161
+ protected _prepPipelineReady: Promise<GPUComputePipeline> | null;
134
162
  private _cachedSrcTex;
135
163
  private _cachedSrcW;
136
164
  private _cachedSrcH;
165
+ private _cachedSrcSource;
166
+ private _cachedSrcFlipY;
137
167
  private _cachedDst;
138
168
  private _cachedStaging;
139
169
  private _cachedParams;
140
170
  private _lastParams;
141
- private _bindGroupCache;
171
+ private _cachedBindGroup;
172
+ private _cachedPrepPlanes;
173
+ private _cachedPrepBindGroup;
142
174
  private _resourcesBusy;
175
+ /** Set when the active shader declares a @binding(3) sampler (the BC5
176
+ * kernels read texels through textureGather + clamp-to-edge). */
177
+ private _usesSampler;
178
+ private _sampler;
179
+ private _chainSig;
180
+ private _chainTextures;
181
+ private _chainPrepPlanes;
182
+ private _chainPrepBindGroups;
183
+ private _chainParams;
184
+ private _chainBindGroups;
185
+ private _chainDst;
186
+ private _chainStaging;
187
+ private _chainBusy;
143
188
  constructor({ device, adapter, ownsDevice, disableF16 }: EncoderOptions);
144
189
  protected _buildPipeline(): void;
145
- /** The f32 module, parsed on first use (see `_module`). */
146
- protected _ensureModule(): GPUShaderModule;
147
- /**
148
- * Pipeline for a given quality level. Encoders that don't declare a
149
- * `QUALITY_HIGH` override (`supportsQuality === false`, e.g. BC1) ignore the
150
- * argument and reuse the single pipeline. Specialised pipelines are cached.
151
- */
152
- protected _getPipeline(quality: EncodeQuality): GPUComputePipeline;
153
190
  destroy(): void;
154
191
  /** Short lowercase identifier used in GPU object labels and errors. */
155
192
  abstract get label(): string;
156
193
  /** 8 for BC1/BC4, 16 for BC5/BC7/ASTC 4×4. */
157
194
  abstract get bytesPerBlock(): number;
195
+ /**
196
+ * Pipeline-creation override constants for the active shader, or
197
+ * undefined for none. Subclasses expose quality/behaviour toggles this
198
+ * way so the OFF state is dead-coded by the shader compiler instead of
199
+ * branched at runtime.
200
+ */
201
+ protected pipelineConstants(): Record<string, number> | undefined;
158
202
  /** WGSL `@workgroup_size` dimensions. Default 8×8×1. */
159
203
  get workgroupSize(): readonly [number, number, number];
160
204
  /** Whether this format has an sRGB variant. Default true. */
161
205
  get supportsSrgb(): boolean;
162
206
  /**
163
- * Whether the shader declares a `QUALITY_HIGH` pipeline-overridable constant
164
- * (i.e. has distinct fast/high search paths). Default false (e.g. a stub or a
165
- * format with a single path); BC1/BC5/BC7/ASTC override it to true.
207
+ * Format of the single-shot source texture the encode pass samples.
208
+ * Encoders that read only a channel subset can narrow it to cut DRAM
209
+ * traffic on the bandwidth-bound compute pass (BC5 reads rg8unorm — half
210
+ * the bytes of rgba8). Must be valid as a copyExternalImageToTexture
211
+ * destination and renderable. Chain encodes keep rgba8unorm regardless
212
+ * (their inputs are RGBA mip levels / texture views); the WGSL is
213
+ * format-agnostic (`texture_2d<f32>`), so mixing is byte-identical.
166
214
  */
167
- get supportsQuality(): boolean;
215
+ protected get srcTextureFormat(): GPUTextureFormat;
168
216
  /**
169
- * Optional f16 WGSL for the 'fast' path. Used only when the device reports the
170
- * `shader-f16` feature; the format's f32 `wgslSource()` is the fallback and
171
- * `'high'` always uses it. Returns null when there's no f16 variant.
217
+ * Optional f16 WGSL variant. Used only when the device reports the
218
+ * `shader-f16` feature; the format's f32 `wgslSource()` is the automatic
219
+ * fallback. Returns null when there's no f16 variant.
172
220
  */
173
221
  wgslSourceFastF16(): string | null;
174
- /** Whether the f16 fast path is both available and supported on this device. */
222
+ /** Whether the f16 shader is both available and supported on this device. */
175
223
  protected get _useF16(): boolean;
176
- /** WGSL compute-shader source. */
224
+ /** WGSL compute-shader source (f32; the fallback when f16 is unavailable). */
177
225
  abstract wgslSource(): string;
226
+ /** WGSL for the preparation pass, or null when the encoder reads the
227
+ * RGBA8 source directly. */
228
+ protected wgslPrepSource(): string | null;
229
+ /** Formats and sizes of the prepared planes for a padded source size. */
230
+ protected prepPlanes(paddedWidth: number, paddedHeight: number): {
231
+ format: GPUTextureFormat;
232
+ width: number;
233
+ height: number;
234
+ }[];
235
+ /** Workgroup counts for the prep dispatch. */
236
+ protected prepDispatch(blocksX: number, blocksY: number): [number, number];
237
+ /** Create the prepared-plane textures for one padded source size. */
238
+ private _createPrepPlanes;
239
+ /** Bind group for one prep dispatch. */
240
+ private _createPrepBindGroup;
178
241
  /** e.g. 'bc1-rgba-unorm-srgb'. */
179
242
  abstract gpuTextureFormat(opts: FormatVariant): GPUTextureFormat;
180
243
  /**
@@ -190,11 +253,59 @@ declare abstract class Encoder {
190
253
  * bytes into a `CompressedTexture`; callers targeting another engine feed
191
254
  * `data` into that engine's compressed-texture upload directly.
192
255
  */
193
- encodeToBytes(source: EncoderImageSource, { flipY, quality, withGpuTime, }?: {
256
+ encodeToBytes(source: EncoderImageSource, { flipY, withGpuTime }?: {
194
257
  flipY?: boolean;
195
- quality?: EncodeQuality;
196
258
  withGpuTime?: boolean;
197
259
  }): Promise<EncodeBytesResult>;
260
+ /**
261
+ * Encode a whole mip chain in ONE GPU submission. A per-level
262
+ * `encodeToBytes()` loop costs a full CPU↔GPU round trip per level (a
263
+ * 1024² chain is 11 levels → 11 `mapAsync` waits with the GPU idle in
264
+ * between); this path uploads every level, records one dispatch per level
265
+ * into a single compute pass, copies all outputs into one staging buffer
266
+ * and maps it once.
267
+ *
268
+ * `levels` are raw RGBA8 pixels in base-to-tail order; sizes don't have to
269
+ * halve level-to-level (each level is padded and clamped independently,
270
+ * exactly like `encodeToBytes`). Uploads use `writeTexture` — the direct
271
+ * raw-bytes path, which also sidesteps the broken
272
+ * `copyExternalImageToTexture` devices (see workarounds.ts). There is no
273
+ * flip option: bake any vertical flip into level 0 before generating the
274
+ * chain, as `compressTexture()` does.
275
+ */
276
+ encodeMipChainToBytes(levels: readonly MipLevel[], { withGpuTime }?: {
277
+ withGpuTime?: boolean;
278
+ }): Promise<EncodeMipChainResult>;
279
+ /**
280
+ * Encode every mip level of a GPU-resident texture in one submission —
281
+ * the zero-CPU-pixels counterpart of `encodeMipChainToBytes()`. Pair it
282
+ * with `generateGpuMipChain()` (gpuMipgen.ts): upload the image once,
283
+ * box-filter the chain on the GPU, then encode straight from the
284
+ * texture's mip views. Pixels never transit the CPU between the source
285
+ * image and the compressed-bytes readback.
286
+ *
287
+ * `srcTex` must be `rgba8unorm` with TEXTURE_BINDING usage; level 0's
288
+ * dimensions are taken from the texture and lower levels follow the
289
+ * standard floor-halving chain. Encoded output is identical to feeding
290
+ * the equivalent CPU chain to `encodeMipChainToBytes()`.
291
+ */
292
+ encodeMipChainFromTexture(srcTex: GPUTexture, { withGpuTime }?: {
293
+ withGpuTime?: boolean;
294
+ }): Promise<EncodeMipChainResult>;
295
+ /** Block-grid geometry + packed output offsets for a chain of levels.
296
+ * `byteSpan` is both the dst buffer size and the readback copy size (a
297
+ * multiple of 4: byteLen is a multiple of bytesPerBlock ≥ 8, offsets are
298
+ * CHAIN_ALIGN-ed). */
299
+ 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. */
302
+ private _submitChainAndRead;
303
+ /** Create the query set + resolve/staging buffers for one timed
304
+ * submission, or null when the device lacks 'timestamp-query'. */
305
+ private _createTiming;
306
+ /** Read back a timed submission's pass duration (ms) and destroy the
307
+ * timing objects. Timestamps are u64 nanoseconds. */
308
+ private _readTimingMs;
198
309
  }
199
310
 
200
311
  declare class BC1Encoder extends Encoder {
@@ -203,7 +314,6 @@ declare class BC1Encoder extends Encoder {
203
314
  get label(): string;
204
315
  get bytesPerBlock(): number;
205
316
  get supportsSrgb(): boolean;
206
- get supportsQuality(): boolean;
207
317
  wgslSource(): string;
208
318
  wgslSourceFastF16(): string | null;
209
319
  gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
@@ -215,19 +325,29 @@ declare class BC5Encoder extends Encoder {
215
325
  get label(): string;
216
326
  get bytesPerBlock(): number;
217
327
  get supportsSrgb(): boolean;
218
- get supportsQuality(): boolean;
328
+ protected get srcTextureFormat(): GPUTextureFormat;
219
329
  wgslSource(): string;
220
330
  wgslSourceFastF16(): string;
221
331
  gpuTextureFormat(): GPUTextureFormat;
222
332
  }
223
333
 
334
+ interface BC7EncoderOptions extends EncoderOptions {
335
+ /**
336
+ * Emit BC7 mode 4 on decorrelated blocks (normal maps, channel-packed
337
+ * atlases): a large quality win there, at up to ~1.5× encode time on
338
+ * exactly that content. See the header note for the measurements.
339
+ */
340
+ adaptiveMode4?: boolean;
341
+ }
224
342
  declare class BC7Encoder extends Encoder {
225
343
  static readonly requiredFeature: GPUFeatureName;
226
344
  static readonly textureFormats: readonly TextureFormat[];
345
+ private readonly adaptiveMode4;
346
+ constructor(opts: BC7EncoderOptions);
347
+ protected pipelineConstants(): Record<string, number> | undefined;
227
348
  get label(): string;
228
349
  get bytesPerBlock(): number;
229
350
  get supportsSrgb(): boolean;
230
- get supportsQuality(): boolean;
231
351
  wgslSource(): string;
232
352
  wgslSourceFastF16(): string;
233
353
  gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
@@ -239,7 +359,17 @@ declare class ASTC4x4Encoder extends Encoder {
239
359
  get label(): string;
240
360
  get bytesPerBlock(): number;
241
361
  get supportsSrgb(): boolean;
242
- get supportsQuality(): boolean;
362
+ wgslSource(): string;
363
+ wgslSourceFastF16(): string;
364
+ gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
365
+ }
366
+
367
+ declare class ETC2Encoder extends Encoder {
368
+ static readonly requiredFeature: GPUFeatureName;
369
+ static readonly textureFormats: readonly TextureFormat[];
370
+ get label(): string;
371
+ get bytesPerBlock(): number;
372
+ get supportsSrgb(): boolean;
243
373
  wgslSource(): string;
244
374
  wgslSourceFastF16(): string;
245
375
  gpuTextureFormat({ colorSpace }: FormatVariant): GPUTextureFormat;
@@ -391,16 +521,26 @@ type TextureHint = 'color' | 'colorWithAlpha' | 'normal';
391
521
  /**
392
522
  * Optional format preference — a wish, not a demand. Applied when the
393
523
  * device supports the format and the hint is compatible; otherwise
394
- * selection proceeds normally (BC7 → ASTC → null). Currently only
524
+ * selection proceeds normally (BC7 → ASTC → ETC2 → null). Currently only
395
525
  * 'bc1': half the memory of BC7 (0.5 vs 1 byte/pixel) for opaque
396
526
  * colour, at visibly lower quality on smooth content.
397
527
  */
398
528
  type PreferredFormat = 'bc1';
529
+ /**
530
+ * Memory/fidelity trade-off for opaque colour textures. 'high' (default)
531
+ * picks the 8-bpp formats (BC7 / ASTC 4×4); 'low' picks the 4-bpp ones
532
+ * (BC1 / ETC2 RGB8) when the adapter supports one. Hints that the 4-bpp
533
+ * formats can't carry ('colorWithAlpha', 'normal') ignore this and keep
534
+ * the 'high' selection.
535
+ */
536
+ type FormatQuality = 'high' | 'low';
399
537
  interface SelectFormatOptions {
400
538
  /** Pick the sRGB variant when the format has one. Default 'srgb'. */
401
539
  colorSpace?: 'srgb' | 'linear';
402
540
  /** Prefer a specific format when supported. See `PreferredFormat`. */
403
541
  preferredFormat?: PreferredFormat;
542
+ /** Memory/fidelity trade-off. See `FormatQuality`. Default 'high'. */
543
+ quality?: FormatQuality;
404
544
  }
405
545
  interface FormatSelection {
406
546
  /** null = no compressed path on this adapter; caller should fall back. */
@@ -426,30 +566,21 @@ interface WebGLFormatSelection {
426
566
  }
427
567
  declare function selectWebGLFormat(caps: WebGLCapabilities, hint: TextureHint, options?: SelectFormatOptions): WebGLFormatSelection;
428
568
 
429
- /** One level of a mip chain. 4 bytes per pixel (RGBA8). */
430
- interface MipLevel {
431
- data: Uint8ClampedArray;
432
- width: number;
433
- height: number;
434
- }
569
+ /** Matches mipgen's chain length: floor(log2(max(w, h))) + 1 levels. */
570
+ declare function gpuMipLevelCount(width: number, height: number): number;
435
571
  /**
436
- * Produce the full mip chain from a level-0 image. The chain goes down
437
- * to a 1×1 level — the standard OpenGL / WebGPU convention — so the
438
- * caller gets `floor(log2(max(w, h))) + 1` levels total.
572
+ * Upload `source` and generate its full mip chain on the GPU. Returns an
573
+ * `rgba8unorm` texture with `gpuMipLevelCount` levels whose dimensions
574
+ * follow the standard floor-halving chain; feed it to
575
+ * `Encoder.encodeMipChainFromTexture()`. The caller owns the texture —
576
+ * destroy it after encoding.
439
577
  *
440
- * Levels whose logical dimensions are below the encoder's 4×4 block
441
- * grid are still produced here at their true logical size; padding up
442
- * to a single block is the encoder's job, not ours.
578
+ * The returned promise resolves once the work is submitted (not completed);
579
+ * queue ordering makes the levels visible to any later submission.
443
580
  */
444
- declare function generateMipChain(level0: MipLevel): MipLevel[];
445
- /**
446
- * Pad a mip level up to a multiple of 4 in each dimension using clamp-
447
- * to-edge sampling. Used before handing sub-4×4 levels to a block-
448
- * compression encoder, which requires at least one full block per level.
449
- *
450
- * If the input is already block-aligned this returns the input unchanged.
451
- */
452
- declare function padToBlockMultiple(level: MipLevel): MipLevel;
581
+ declare function generateGpuMipChain(device: GPUDevice, source: ImageBitmap | ImageData | HTMLCanvasElement | OffscreenCanvas, { flipY }?: {
582
+ flipY?: boolean;
583
+ }): Promise<GPUTexture>;
453
584
 
454
585
  /**
455
586
  * Target raster size for an SVG source. A number scales the SVG so its
@@ -474,4 +605,4 @@ interface RasterizeSvgOptions {
474
605
  */
475
606
  declare function rasterizeSvg(source: string | Blob, options?: RasterizeSvgOptions): Promise<ImageBitmap>;
476
607
 
477
- 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 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 };
608
+ export { ASTC4x4Encoder, ASTC4x4WebGLEncoder, BC1Encoder, BC1WebGLEncoder, BC5Encoder, BC5WebGLEncoder, BC7Encoder, type BC7EncoderOptions, BC7WebGLEncoder, type Capabilities, ETC2Encoder, type EncodeBytesResult, type EncodeCallOptions, type EncodeMipChainResult, type EncodedLevelBytes, Encoder, type EncoderConstructor, type EncoderImageSource, type EncoderOptions, type ExtensionProvider, type FeatureProvider, type FormatQuality, 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, generateGpuMipChain, generateMipChain, getSharedWebGLContext, gpuMipLevelCount, isWebGLAvailable, padToBlockMultiple, rasterizeSvg, selectFormat, selectWebGLFormat };