gputex 0.0.3 → 0.0.5

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
@@ -14,7 +14,7 @@ pnpm add gputex
14
14
  bun add gputex
15
15
  ```
16
16
 
17
- `three` is a peer dependency (`>=0.170`).
17
+ `three` is a peer dependency (`>=0.180`).
18
18
 
19
19
  ## Formats
20
20
 
@@ -43,12 +43,12 @@ const { texture, format } = await compressTexture('/cobblestone.avif', {
43
43
  material.map = texture
44
44
  ```
45
45
 
46
- ### `WebGPUCompressedTextureLoader` — Three.js Loader
46
+ ### `GputexLoader` — Three.js Loader
47
47
 
48
48
  ```ts
49
- import { WebGPUCompressedTextureLoader } from 'gputex'
49
+ import { GputexLoader } from 'gputex'
50
50
 
51
- const loader = new WebGPUCompressedTextureLoader()
51
+ const loader = new GputexLoader()
52
52
  loader.hint = 'normal'
53
53
  loader.mipmaps = true
54
54
  const normalMap = await loader.loadAsync('/brick_normal.png')
@@ -57,14 +57,14 @@ material.normalMap = normalMap
57
57
 
58
58
  ### React Three Fiber
59
59
 
60
- The `WebGPUCompressedTextureLoader` works with R3F's `useLoader`:
60
+ The `GputexLoader` works with R3F's `useLoader`:
61
61
 
62
62
  ```tsx
63
63
  import { useLoader } from '@react-three/fiber'
64
- import { WebGPUCompressedTextureLoader } from 'gputex'
64
+ import { GputexLoader } from 'gputex'
65
65
 
66
66
  function Scene() {
67
- const texture = useLoader(WebGPUCompressedTextureLoader, '/cobblestone.avif', loader => {
67
+ const texture = useLoader(GputexLoader, '/cobblestone.avif', loader => {
68
68
  loader.hint = 'color'
69
69
  loader.colorSpace = 'srgb'
70
70
  loader.mipmaps = true
@@ -84,11 +84,11 @@ For a reusable hook with metadata access:
84
84
  ```tsx
85
85
  import { useLayoutEffect } from 'react'
86
86
  import { useLoader } from '@react-three/fiber'
87
- import { WebGPUCompressedTextureLoader } from 'gputex'
87
+ import { GputexLoader } from 'gputex'
88
88
  import type { TextureHint } from 'gputex'
89
89
 
90
90
  function useGputex(url: string, options?: { hint?: TextureHint; colorSpace?: 'srgb' | 'linear'; mipmaps?: boolean }) {
91
- const texture = useLoader(WebGPUCompressedTextureLoader, url, loader => {
91
+ const texture = useLoader(GputexLoader, url, loader => {
92
92
  if (options?.hint !== undefined) loader.hint = options.hint
93
93
  if (options?.colorSpace !== undefined) loader.colorSpace = options.colorSpace
94
94
  if (options?.mipmaps !== undefined) loader.mipmaps = options.mipmaps
@@ -102,7 +102,7 @@ useGputex.preload = (
102
102
  url: string,
103
103
  options?: { hint?: TextureHint; colorSpace?: 'srgb' | 'linear'; mipmaps?: boolean },
104
104
  ) => {
105
- useLoader.preload(WebGPUCompressedTextureLoader, url, loader => {
105
+ useLoader.preload(GputexLoader, url, loader => {
106
106
  if (options?.hint !== undefined) loader.hint = options.hint
107
107
  if (options?.colorSpace !== undefined) loader.colorSpace = options.colorSpace
108
108
  if (options?.mipmaps !== undefined) loader.mipmaps = options.mipmaps
@@ -158,6 +158,10 @@ encoder.destroy()
158
158
  - `texture-compression-bc` (desktop) or `texture-compression-astc` (mobile) for compressed output
159
159
  - Falls back to uncompressed RGBA8 when neither is available
160
160
 
161
+ ## Device-specific workarounds
162
+
163
+ - Black texture on Google Pixel 10: `copyExternalImageToTexture` produces black textures on the Pixel 10's PowerVR DXT GPU (vendor `img-tec`, architecture `d-series`). Worked around by uploading via `writeTexture` with rasterised pixel data instead.
164
+
161
165
  ## Acknowledgements
162
166
 
163
167
  The concept of encoding images on the GPU on the fly via compute shaders was first introduced by [spark.js](https://ludicon.com/sparkjs/), which is a much more robust solution for users who can afford its license. GPUtex is not derived from Spark and its encoders have been implemented from scratch using official references, which have been ported to TypeScript, and then converted to WGSL via AI. For any serious production use of GPU-compressed textures, Spark is the recommended choice over GPUtex.
package/dist/index.d.ts CHANGED
@@ -274,7 +274,7 @@ interface CompressResult {
274
274
  }
275
275
  declare function compressTexture(source: CompressTextureSource, options?: CompressOptions): Promise<CompressResult>;
276
276
 
277
- declare class WebGPUCompressedTextureLoader extends Loader<Texture> {
277
+ declare class GputexLoader extends Loader<Texture> {
278
278
  /** Format-selection hint. Default 'color'. */
279
279
  hint: TextureHint;
280
280
  /** Pick the sRGB or linear variant of the chosen format. Default 'srgb'. */
@@ -329,4 +329,4 @@ declare function generateMipChain(level0: MipLevel): MipLevel[];
329
329
  */
330
330
  declare function padToBlockMultiple(level: MipLevel): MipLevel;
331
331
 
332
- export { ASTC4x4Encoder, BC1Encoder, BC5Encoder, BC7Encoder, type Capabilities, type CompressOptions, type CompressResult, type CompressTextureSource, type EncodeBytesResult, type EncodeCallOptions, type EncodeResult, Encoder, type EncoderConstructor, type EncoderImageSource, type EncoderOptions, type FeatureProvider, type FormatSelection, type FormatVariant, type MipLevel, type SelectFormatOptions, TextureFormat, type TextureHint, WebGPUCompressedTextureLoader, WebGPUFeature, compressTexture, detectCapabilities, generateMipChain, padToBlockMultiple, selectFormat };
332
+ export { ASTC4x4Encoder, BC1Encoder, BC5Encoder, BC7Encoder, type Capabilities, type CompressOptions, type CompressResult, type CompressTextureSource, type EncodeBytesResult, type EncodeCallOptions, type EncodeResult, Encoder, type EncoderConstructor, type EncoderImageSource, type EncoderOptions, type FeatureProvider, type FormatSelection, type FormatVariant, GputexLoader, type MipLevel, type SelectFormatOptions, TextureFormat, type TextureHint, WebGPUFeature, compressTexture, detectCapabilities, generateMipChain, padToBlockMultiple, selectFormat };
package/dist/index.js CHANGED
@@ -50,6 +50,25 @@ import {
50
50
  SRGBColorSpace,
51
51
  RepeatWrapping
52
52
  } from "three";
53
+
54
+ // src/workarounds.ts
55
+ function needsWriteTextureWorkaround(adapter) {
56
+ const { vendor, architecture } = adapter.info ?? {};
57
+ return vendor === "img-tec" && architecture === "d-series";
58
+ }
59
+ function uploadSourceTexture(device, srcTex, source, width, height, flipY, useWriteTexture) {
60
+ if (useWriteTexture && source instanceof ImageData) {
61
+ device.queue.writeTexture({ texture: srcTex }, source.data, { bytesPerRow: width * 4 }, [width, height, 1]);
62
+ } else {
63
+ device.queue.copyExternalImageToTexture({ source, flipY }, { texture: srcTex }, [
64
+ width,
65
+ height,
66
+ 1
67
+ ]);
68
+ }
69
+ }
70
+
71
+ // src/Encoder.ts
53
72
  var Encoder = class {
54
73
  /**
55
74
  * Subclasses set this to the WebGPU feature string the output texture
@@ -184,9 +203,11 @@ var Encoder = class {
184
203
  label: `${this.label}-src`,
185
204
  size: [paddedWidth, paddedHeight, 1],
186
205
  format: "rgba8unorm",
206
+ // RENDER_ATTACHMENT is required by copyExternalImageToTexture
207
+ // (internally a blit) even though we never render into this texture.
187
208
  usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
188
209
  });
189
- device.queue.copyExternalImageToTexture({ source, flipY }, { texture: srcTex }, [width, height, 1]);
210
+ uploadSourceTexture(device, srcTex, source, width, height, flipY, source instanceof ImageData);
190
211
  const dstBuffer = device.createBuffer({
191
212
  label: `${this.label}-dst`,
192
213
  size: outByteLen,
@@ -608,8 +629,16 @@ async function compressTexture(source, options = {}) {
608
629
  encoder = await selection.encoderClass.create();
609
630
  }
610
631
  try {
632
+ const needsWriteTexture = needsWriteTextureWorkaround(adapter);
611
633
  if (!mipmaps) {
612
- const bytes = await encoder.encodeToBytes(bitmap, { flipY });
634
+ let bytes;
635
+ if (needsWriteTexture) {
636
+ const level02 = bitmapToMipLevel(bitmap, flipY);
637
+ const imageData = mipLevelToImageData(level02);
638
+ bytes = await encoder.encodeToBytes(imageData);
639
+ } else {
640
+ bytes = await encoder.encodeToBytes(bitmap, { flipY });
641
+ }
613
642
  const tex2 = encoder.buildMippedTexture([bytes], { colorSpace });
614
643
  return {
615
644
  texture: tex2,
@@ -658,9 +687,9 @@ async function compressTexture(source, options = {}) {
658
687
  }
659
688
  }
660
689
 
661
- // src/CompressedTextureLoader.ts
690
+ // src/GputexLoader.ts
662
691
  import { Loader } from "three";
663
- var WebGPUCompressedTextureLoader = class extends Loader {
692
+ var GputexLoader = class extends Loader {
664
693
  /** Format-selection hint. Default 'color'. */
665
694
  hint = "color";
666
695
  /** Pick the sRGB or linear variant of the chosen format. Default 'srgb'. */
@@ -728,8 +757,8 @@ export {
728
757
  BC5Encoder,
729
758
  BC7Encoder,
730
759
  Encoder,
760
+ GputexLoader,
731
761
  TextureFormat,
732
- WebGPUCompressedTextureLoader,
733
762
  WebGPUFeature,
734
763
  compressTexture,
735
764
  detectCapabilities,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gputex",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist"