Loading multiple KTX2 files causes random crash - Quest2 Oculus browser - NOT on Firefox

On Quest2 Oculus browser I am loading KTX2 files as in below snippet and apply them as texture. After having loaded and
disposed (texture.dispose()) several unique ones, at random moments the below wasm exception appears. Using THREE 130.

Anyone got the same and found a solution? I suppose an update if the basis transcoder libs (setTranscoderPath) could do the trick but which one? Or a work-around maybe?

With Firefox Reality on Quest2 everything is 100% normal !

Loader

var ktx2Loader = new THREE.KTX2Loader();
ktx2Loader.setTranscoderPath( 'examples/js/libs/basis/' );
ktx2Loader.detectSupport( renderer );
ktx2Loader.load( 'diffuse.ktx2', function ( texture ) {
..bla..
});

WASM expception

abort @ da641868-08df-4458-a606-5dc5b194324f:14
_abort @ da641868-08df-4458-a606-5dc5b194324f:14
(anonymous) @ 001adf2e:0x55c
(anonymous) @ 001adf2e:0x19c9c
(anonymous) @ 001adf2e:0x240e9
transcodeUASTCImage @ VM91:25
transcodeLowLevel @ da641868-08df-4458-a606-5dc5b194324f:173
(anonymous) @ da641868-08df-4458-a606-5dc5b194324f:56
Promise.then (async)
onmessage @ da641868-08df-4458-a606-5dc5b194324f:51
da641868-08df-4458-a606-5dc5b194324f:71 RuntimeError: abort(undefined). Build with -s ASSERTIONS=1 for more info.
    at abort (da641868-08df-4458-a606-5dc5b194324f:14)
    at _abort (da641868-08df-4458-a606-5dc5b194324f:14)
    at <anonymous>:wasm-function[34]:0x55c
    at <anonymous>:wasm-function[151]:0x19c9c
    at <anonymous>:wasm-function[228]:0x240e9
    at Object.transcodeUASTCImage (eval at new_ (da641868-08df-4458-a606-5dc5b194324f:1), <anonymous>:25:10)
    at transcodeLowLevel (da641868-08df-4458-a606-5dc5b194324f:173)
    at da641868-08df-4458-a606-5dc5b194324f:56
(anonymous) @ da641868-08df-4458-a606-5dc5b194324f:71
Promise.then (async)
onmessage @ da641868-08df-4458-a606-5dc5b194324f:51

Out of memory

Also received this out of memory. I disposed as much as i can. not sure what sle i can call to free this memory…

failed to asynchronously prepare wasm: RangeError: WebAssembly.instantiate(): Out of memory: wasm memory
(anonymous) @ d1be675a-256d-4966-bd24-47aef484bbd6:14
Promise.then (async)
instantiateArrayBuffer @ d1be675a-256d-4966-bd24-47aef484bbd6:14
instantiateAsync @ d1be675a-256d-4966-bd24-47aef484bbd6:14
createWasm @ d1be675a-256d-4966-bd24-47aef484bbd6:14
(anonymous) @ d1be675a-256d-4966-bd24-47aef484bbd6:14
(anonymous) @ d1be675a-256d-4966-bd24-47aef484bbd6:89
init @ d1be675a-256d-4966-bd24-47aef484bbd6:86
onmessage @ d1be675a-256d-4966-bd24-47aef484bbd6:47

Are you reusing one KTX2Loader or creating multiple? What are the dimensions of these textures? Could you share a demo of this issue? Perhaps this is a bug and not a mistake in your code, but would need a way to reproduce the problem to investigate.

I played around with multiple loaders and single loader instance, but i see no difference.

What do you recommend: reuse a loader or create/dispose one per texture?

Resolution of the KTX2 files: 8192 x 2048px (aspect = 4, side-by-side stereo pano), size appr 8.5MB.
Going smaller in pixels or file size is not possible as it would compromise the quality.

Any comments here? E.g. does aspect ratio affect memory usage ?

When I managed to make a simple app to demo the exception I will share.

Thanks for helping!

I would suggest reusing a single KTX2Loader, as this will balance Web Workers more efficiently, but I’m not aware that using more would causes crashes. If using more, be sure to dispose them eventually to deallocate the Web Workers.

I expect 8K textures will not work on some mobile devices; whether this is an issue on Quest 2 I don’t know. It is a non-trivial memory cost. I would be curious if the crash can be reproduced with a more broadly compatible 2K or 4K texture, regardless of the visual quality.

Whoever comes around this post and is struggling with memory leaks, this is what I do to dispose:

  1. traverse nodes that need to be disposed:
    somegroup.traverse(function(node) {
      disposeNode(node);
    });

2, dispose the nodes

function disposeNode(node) {
  if (node.material) {
    if(node.material.map) {
      node.material.map.dispose();
      delete node.material.map;
    }
    node.material.dispose();
    delete node.material;
  }

  if (node.geometry) {
    for (const key in node.geometry.attributes) {
        node.geometry.deleteAttribute(key);
    }
    node.geometry.setIndex([]);
    node.geometry.dispose();
    delete node.geometry;
  }
}
  1. remove from scene graph
somegroup.remove(...somegroup.children);
somegroup.removeFromParent();
  1. and finally:

renderer.renderLists.dispose();

even when you call point 4 without closing and you are in the middle of an experience, this renderList will be repopulated without issues.

It might well be that point 1,2 are not required and only pt 3 and 4 are needed . Didnt test.

Advice is welcomed!

1 Like