Skip to content

fix(textures): enable compression extension before upload (fixes GL 1280)#88

Merged
chiefcll merged 1 commit into
mainfrom
fix/compressed-texture-extension-enable
Jun 9, 2026
Merged

fix(textures): enable compression extension before upload (fixes GL 1280)#88
chiefcll merged 1 commit into
mainfrom
fix/compressed-texture-extension-enable

Conversation

@chiefcll

@chiefcll chiefcll commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Problem

Users on 1.4.0–1.4.3 hit WebGL: INVALID_ENUM (1280) and a blank/failed texture when loading .ktx (S3TC/ETC/ETC2) or .pvr (PVRTC) compressed textures. ASTC was unaffected.

Root cause

In WebGL, a compressed format enum is only valid in compressedTexImage2D after its owning extension has been enabled via getExtensiongetExtension is the enable; there is no separate call.

uploadKTX/uploadPVR never call getExtension. They relied on the boot-time getWebGlExtensions() probe, which called getExtension on every compression extension as a side effect. #60 (1.4.0) correctly found the probe's result cache was dead and removed the whole function — but the getExtension calls themselves were load-bearing. uploadASTC survived only because it re-queries its own extension.

Result: the first KTX/PVR upload throws 1280. Not fixed by forceWebGL2 (#70) — WebGL2 still requires getExtension for these formats.

Fix

  • Enable the owning extension at the point of use for all three upload paths (resolved by GL internal format), throwing a clear "...not supported by this device" error instead of leaking a silent 1280.
  • Candidate extension-name lists hoisted to module constants — the resolver allocates nothing per upload. (getExtension is CPU-side + browser-cached and upload is not a per-frame path, so no runtime cache is warranted.)

Why it shipped: missing VRT coverage

tx-compression.ts existed but was a manual-only example (no automation/snapshot), so CI never exercised it. This PR promotes it to an automated VRT and commits a certified snapshot.

Also: two hung-test fixes

The VRT runner has no per-test timeout, so a never-resolving wait hangs the whole capture/compare run:

  • tx-compression: an unsupported format on the running GPU (e.g. ETC1 under headless SwiftShader) surfaces as a failed texture that never fires loaded. Added a timeout backstop so the snapshot captures whatever the device produced.
  • texture-free-reload: automation() ran after idle had already fired, so waitUntilIdle() waited forever. Force a final frame + short delay instead.

Tests

  • New src/core/lib/textureCompression.test.ts — 8 deterministic unit tests asserting getExtension precedes compressedTexImage2D on every path, the WebKit-prefixed PVRTC fallback, and a clear throw (no upload) when unsupported.
  • pnpm build, pnpm test, lint all clean.

🤖 Generated with Claude Code

…280)

A compressed format enum is only valid in compressedTexImage2D after its
owning extension has been enabled via getExtension; otherwise the driver
rejects it with GL_INVALID_ENUM (1280).

KTX (S3TC/ETC/ETC2) and PVR (PVRTC) uploads never called getExtension —
they relied on the boot-time getWebGlExtensions() probe (removed in #60,
1.4.0) incidentally enabling every compression extension. #60 saw the
probe's result cache was dead and removed the whole function, not realizing
the getExtension() calls themselves were load-bearing. ASTC survived only
because uploadASTC re-queried its extension itself. Result: the first KTX or
PVR upload throws 1280 on 1.4.0–1.4.3.

Enable the owning extension at the point of use (resolved by GL internal
format) for all three paths, throwing a clear "not supported by this device"
error instead of leaking a silent 1280. Candidate name lists are hoisted to
module constants so the resolver allocates nothing per upload.

Also fix two VRT examples that hung the (timeout-less) snapshot runner:
- tx-compression: promote to an automated VRT (it was manual-only, which is
  why this regression shipped). A format the GPU lacks now surfaces as a
  failed texture that never fires 'loaded', so wait with a timeout backstop.
- texture-free-reload: 'idle' had already fired by the time automation()
  ran, so waitUntilIdle() waited forever; force a final frame instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chiefcll chiefcll merged commit 43caf0c into main Jun 9, 2026
1 check passed
@chiefcll chiefcll deleted the fix/compressed-texture-extension-enable branch June 9, 2026 20:23
chiefcll added a commit that referenced this pull request Jun 9, 2026
…280) (#88)

A compressed format enum is only valid in compressedTexImage2D after its
owning extension has been enabled via getExtension; otherwise the driver
rejects it with GL_INVALID_ENUM (1280).

KTX (S3TC/ETC/ETC2) and PVR (PVRTC) uploads never called getExtension —
they relied on the boot-time getWebGlExtensions() probe (removed in #60,
1.4.0) incidentally enabling every compression extension. #60 saw the
probe's result cache was dead and removed the whole function, not realizing
the getExtension() calls themselves were load-bearing. ASTC survived only
because uploadASTC re-queried its extension itself. Result: the first KTX or
PVR upload throws 1280 on 1.4.0–1.4.3.

Enable the owning extension at the point of use (resolved by GL internal
format) for all three paths, throwing a clear "not supported by this device"
error instead of leaking a silent 1280. Candidate name lists are hoisted to
module constants so the resolver allocates nothing per upload.

Also fix two VRT examples that hung the (timeout-less) snapshot runner:
- tx-compression: promote to an automated VRT (it was manual-only, which is
  why this regression shipped). A format the GPU lacks now surfaces as a
  failed texture that never fires 'loaded', so wait with a timeout backstop.
- texture-free-reload: 'idle' had already fired by the time automation()
  ran, so waitUntilIdle() waited forever; force a final frame instead.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant