How to prevent duplicate loading of shared texture with GLTFLoader?

I have two GLB files (A.glb and B.glb) that both reference the same external shared texture between them. When loading A.glb and B.glb, though, the same texture is load twice and two separate WebGL textures are created.

Is there any way through something like a callback to prevent from GLTFLoader loading and creating a new texture and tell it to use the one that’s already been loaded?

cc @donmccurdy

Thanks!

I’d expect THREE.Cache.enabled = true; to do that. Perhaps there are cases involving ImageBitmapLoader and/or KTX2Loader that aren’t as simple — do you have an example that reproduces the issue?

do you have an example that reproduces the issue

Unfortunately not that I can share but it sounds like you understand the issue.

It looks like Cache.enabled = true would solve it in the simple case (which I didn’t realize so thanks!). It wouldn’t completely work with any kind of compressed texture though, like you mention. In that case it will cache the buffers but it will still decompress the texture again and create a second webgl texture.

I’m also not a huge fan of THREE.Cache (heh I suppose my question is turning in to “are there any other ways” :grimacing:) because it will cache everything and I’m already caching other objects and geometry in a manner that’s more efficient to clone. I’m also managing my own cache eviction strategy to avoid objects from being removed before we know we’re done with them.

I suppose one option might be to use LoadingManager.getHandler to get the image from the custom cache or otherwise load it if it’s not available:

const manager = new LoadingManager();
manager.addHandler( /\.png$/, {

    load( url, onLoad, onProgress, onError ) {

        if ( MyCustomCache.has( url ) ) {

            const tex = MyCustomCache.get( url );
            requestAnimationFrame( () => onLoad( tex );
            return tex;

        } else {

            return new TextureLoader( manager )
                .load( url, onLoad, onProgress, onError );

        }

    }

} );

Do you see any problems with that strategy? I’m also realizing that calling “clone” on a texture will probably result in a duplicate texture upload until #17949 or #17766 any, right? (maybe @Mugen87 knows more about that)

Thanks for your help!

BasisTextureLoader has a WeakMap caching the buffer->Texture result; as long as THREE.FileLoader returns the same buffer (which requires THREE.Cache, or a custom LoadingManager override as you suggest) it should resolve to the same Texture. That was done out of necessity — you can’t transfer the same ArrayBuffer to a Web Worker twice. Because KTX2Loader doesn’t use Web Workers yet, it doesn’t have that same cache, but at some point it probably should.

I’m also realizing that calling “clone” on a texture will probably result in a duplicate texture upload until #17949 or #17766 any, right?

That’s correct, yeah. GLTFLoader will have to clone the texture internally if it uses a non-default UV transform (KHR_texture_transform) but otherwise it shouldn’t.

Do you see any problems with [the addHandler] strategy?

Not for external textures, at least. If the textures were embedded in a GLB you’d have a problem, since there’s no URI to match.

1 Like

Good to know about Basis Textures – thanks!

If the textures were embedded in a GLB you’d have a problem, since there’s no URI to match.

Heh I think as soon as a texture’s embedded in a GLB file I’m okay with not being able to cache it or share it with a different GLB that embeds the same contents. Cloning the whole object group works for the same file case anyway.