Hi, I found a bug with GLB export under NullEngine on the server. It worked correctly in v8, but after upgrading to v9 the following error appears:
Error: Failed to read pixels from texture <name>.
at GetTextureDataAsync
I traced the regression to an optimization: GetCachedImageAsync in glTFMaterialExporter.ts (and a parallel version in usdzExporter.ts which we already merged).
Root Cause
Both functions contain this guard:
if (internalTexture.invertY) {
return null;
}
invertY defaults to true for every texture loaded from a URL — so this guard always fires, returns null, and forces fallback to GetTextureDataAsync. That function reads pixels from the GPU, which requires a WebGL context. Under NullEngine there is no GPU → exception.
The intent of the cached path was precisely to avoid GPU readback (faster, works without WebGL). The invertY guard defeats its own purpose.
Why the Guard is Wrong
-
_bufferalways stores the original source bytes of the image file — before any GPU upload -
invertYis a WebGL upload-time operation that has no bearing on the source bytes -
For export purposes (embedding a PNG/JPEG blob in a glTF/USDZ archive), the original bytes are exactly what we want
-
A glTF/USDZ viewer handles texture orientation itself
Ironically, the GPU fallback path may produce worse results: it reads back pixels after the Y-flip, yielding an upside-down image in the archive.
Fix
Make the invertY guard context-aware:
if (internalTexture.invertY) {
// On a real engine, the GPU has the texture stored flipped (UNPACK_FLIP_Y_WEBGL),
// while the glTF loader uploads with invertY=false. Falling back to GPU readback
// produces bytes that round-trip correctly. NullEngine has no GPU readback path,
// so the cached URL bytes are the only option.
const engine = babylonTexture.getScene()?.getEngine();
if (!(engine instanceof NullEngine)) {
return null;
}
}
This preserves correct GPU readback behavior on WebGL engines while allowing NullEngine to use the cached bytes.
PR Status
Both fixes have been submitted:
Implementation: Both GLB and USDZ export now work correctly under NullEngine using the arek-3d/Babylon.js fork.
Note: USDZ didn’t work on server even in v8, but GLB worked properly. See this thread regarding USDZ server-side support.