Skip to content

editor: texture references — source stamping + reference mode in GLB export#532

Merged
wass08 merged 1 commit into
mainfrom
feat/bake-texture-references
Jul 22, 2026
Merged

editor: texture references — source stamping + reference mode in GLB export#532
wass08 merged 1 commit into
mainfrom
feat/bake-texture-references

Conversation

@wass08

@wass08 wass08 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Bake v2 exporter side (private-editor plan editor-baked-glb-client-capture, Decisions section). Textures in a baked scene always have a server-known source, so the export no longer needs to ship their pixels:

  • Source stamping at loadviewer/lib/texture-reference.ts stamps a validated pascalTextureRef ({v, kind, src, map, colorSpace, imageIndex?}) onto texture.userData at the three load sites: preset/library materials (materials.ts, resolving to library-material for storage-bucket URLs or app-material for static-catalog /material/… URLs), the legacy getTexture path (project-asset), and item GLB textures (item-glb with the glTF image index via parser.associations, including the KHR_texture_basisu source indirection).
  • exportSceneToGlb gains textures: 'embed' | 'reference' (default embed, byte-for-byte current behavior). Reference mode swaps stamped textures for 1×1 canvas-backed placeholders (skipping the WebGPUTextureUtils blit — the export speedup) while preserving sampler state/transforms, and writes the ref to BOTH textures[i].extras and images[j].extras via a GLTFExporter writeTexture plugin.
  • Callers: the bake page (BakeExporter) passes reference; the Download GLB button stays embed (external consumers need pixels).

Origin validation is env-derived (NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_ASSETS_CDN_URL default) and fails closed — no env, no stamps, everything falls back to embed. Pairs with the private-editor packer PR that resolves these refs in the bake LOD chain (attach original KTX2, no decode/re-encode).

How to test

  1. bun test packages/viewer/src/lib/texture-reference.test.ts packages/editor/src/lib/glb-export.test.ts — 21 tests cover stamp resolution per kind, origin rejection, placeholder swap, shared-texture dedup, and identical texture/image extras.
  2. In the standalone editor (bun dev), paint a wall and use Download GLB — output is unchanged (embed mode; textures render in any glTF viewer).
  3. End-to-end with the private-editor packer branch (feat/bake-v2): run a local bake of a painted scene and check the [bake:textures] log line — referenced textures resolve, rejected: 0, and lod0 textures are byte-identical to the catalog KTX2 files. (Verified on the Wawa House local copy: 31 refs, raw 9.79 → 4.79 MB.)

Screenshots / screen recording

N/A — non-visual change (export/bake internals; Download GLB output is byte-for-byte unchanged in default mode).

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (plan editor-baked-glb-client-capture.md in private-editor carries the contract + decisions)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Medium Risk
Touches GLB export and bake capture paths; default embed mode preserves prior behavior, but reference mode must stay aligned with the downstream bake packer contract.

Overview
Adds pascalTextureRef metadata on textures at load time so baked GLBs can point at server-known KTX2/sources instead of embedding pixels.

Stamping (texture-reference.ts): validated refs (kind, src, map, colorSpace, optional imageIndex for item GLBs) are written to texture.userData from library/preset materials, project-asset maps, and item GLB loads (image index from glTF parser associations). Non-Pascal URLs are left unstamped.

GLB export gains textures: 'embed' | 'reference' (default embed — unchanged for Download GLB). Reference mode replaces stamped maps with shared 1×1 placeholders (keeping UV/sampler state), registers a GLTFExporter plugin to copy the same ref into textures[].extras and images[].extras, and BakeExporter now exports with textures: 'reference' for smaller/faster bakes resolved downstream by the packer.

Reviewed by Cursor Bugbot for commit 472db40. Bugbot is set up for automated code reviews on this repo. Configure here.

kind: 'project-asset',
src: resolvedUrl,
slot: 'map',
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong stamp kind for custom textures

Medium Severity

In getTexture, stampPascalTextureRef is always called with kind: 'project-asset', so only project-assets storage URLs can be stamped. Preset loads use kind: 'material', which also accepts materials-bucket and assets-CDN /material/… URLs. Custom material.texture.url values on those paths never get pascalTextureRef, so reference bakes embed full texture data instead of packer-resolvable refs.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 62276ae. Configure here.

…ode in GLB export

Bake v2 exporter side: library/preset, legacy scene-material, and item-GLB
textures get a validated pascalTextureRef stamped at load (origin-checked,
env-derived). Material-ish sources resolve to 'library-material' (storage
bucket) or 'app-material' (static catalog on the assets CDN) by URL shape.
exportSceneToGlb grows a textures: 'embed' | 'reference' option — reference
mode swaps stamped textures for 1x1 canvas-backed placeholders (skipping the
WebGPU blit) and writes the ref to both texture and image extras via a
GLTFExporter writeTexture plugin. Download GLB stays embed; the bake page
passes reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wass08
wass08 force-pushed the feat/bake-texture-references branch from 62276ae to 472db40 Compare July 22, 2026 12:28

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

There are 4 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 472db40. Configure here.

} catch {
cachedStorageOrigin = null
}
return cachedStorageOrigin

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storage origin cached permanently

Medium Severity

pascalStorageOrigin caches the first resolved value, including null when NEXT_PUBLIC_SUPABASE_URL is missing or invalid. Later calls never re-read the env, so storage-backed stampPascalTextureRef / getPascalTextureRef checks can stay disabled for the whole JS realm even after the variable becomes available (e.g. test order or late config).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 472db40. Configure here.

) as LoadedItemGltf
return useMemo(() => {
stampItemTextureReferences(gltf, url)
return gltf

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item refs skip CDN paths

Medium Severity

stampItemTextureReferences passes the resolveCdnUrl load URL into stampPascalTextureRef as item-glb src, but validation only accepts Supabase storage URLs under /storage/v1/object/public/items/. App-relative paths like /items/…/model.glb become assets-CDN URLs, so item textures never get pascalTextureRef and reference bakes embed full item texture pixels.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 472db40. Configure here.

imageDef.extras = {
...imageDef.extras,
pascalTextureRef: ref,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shared image extras overwritten

Medium Severity

In reference mode, writeTextureReferenceExtras writes pascalTextureRef onto the shared glTF images[] entry for every stamped map. Identical 1×1 placeholder pixels are often deduplicated to one image index, so each writeTexture overwrites imageDef.extras and only the last ref remains while earlier textures[] entries may still point at that image.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 472db40. Configure here.

@wass08
wass08 merged commit 0fba611 into main Jul 22, 2026
2 checks passed
@wass08
wass08 deleted the feat/bake-texture-references branch July 22, 2026 12:33
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