Hello all,
Firstly, thank you all who have contributed to this project, it is nothing short of amazing.
I have been using Three.js for my little photography project, and I’m at a point where I would like to trim as much fat as possible.
Since camera raw files come in different formats that range from 10-bit to 16-bit, at first, I’ve been loading them as Uint16Array, then doing a conversion + normalization + adding alpha, and getting back a Float32Array. Then, creating a THREE.DataTexture with RGBAFormat and FloatType.
With some photos reaching 10 000px on the long edge, RAM usage becomes a problem, since I can’t seem to find a way to Garbage Collect quickly enough. So it all ends up storing a couple of copies of the same array in different states.
I try to ‘null’ the arrays as soon as I don’t need them, but that still relies on GC.
Currently, I’ve been experimenting with this:
Which helped me shave off quite a bit of RAM-usage off the peak, at a cost of longer loading times (creating and sampling 3 textures instead of 1).
So, I was wondering, maybe someone has come across any other ‘tricks’ that would allow me to use 16-bit data for textures? Or maybe a way to do the Uint16 RGB → Float32 RGBA conversion more efficiently with respect to peak RAM usage?
I’ve seen some applications using Tiling and IndexedDB storage, but before I attempt to go down that route, I wanted to ping you all and maybe get some lower-hanging-fruit ideas.
You can use 16 bit integer, or 16 bit float (HalfFloat), directly as a texture source. If it’s a single channel you can use RedFormat or LuminanceFormat iirc.
If you do need to transform it.. you could make your loader load in chunks and process a chunk at a time, convert it, and then throw away the source chunk.. and when you’ve loaded and converted all the chunks.. concat them.
In theory you shouldn’t need the float16 conversion, and could use uint16 directly from the RAW file with THREE.UnsignedShortType and texture.normalized= true (I’m assuming you are using mipmaps and wouldn’t want integer values), but that’s a newer option in three.js and not one I’d tested at the time.
***
A trick someone shared with me a while back was to transfer large arrays to a Web Worker (the Web Worker doesn’t actually need to do anything, it can just sit there) when you’re done with them, so that GC happens off the main thread. I don’t know if that would help to avoid a crash if you’re running out of RAM, but it should reduce the stalls associated with Major GC events.
Weirdly, I could not get uint16 to work when I tried it. Double-checked, and I’m getting an internalformat warning (with no picture). Should I be setting internalformat manually?
texture = new THREE.DataTexture(rgba, width, height, THREE.RGBAFormat, THREE.UnsignedShortType)
&
texture = new THREE.DataTexture(rgb, width, height, THREE.RGBFormat, THREE.UnsignedShortType)
Returned
WebGL warning: texStorage(Multisample)?: internalformat: Invalid enum value RGB WebGL warning: texSubImage: The specified TexImage has not yet been specified.
or
WebGL warning: texStorage(Multisample)?: internalformat: Invalid enum value RGBA WebGL warning: texSubImage: The specified TexImage has not yet been specified.
Regarding uint16 → HalfFloat conversion, and please correct me if I’m wrong because my understanding is still very superficial, but isn’t that a precision loss?
Thanks again for the replies, they also help me to get a rough direction of where I need to be going.
I’m not sure of the current state of supporting RGB (vs. RGBA), so just to get things working with uint16 unorm, I’d want to get RGBA working first.
You’re correct that uint16 to float16 represents some precision loss, though… I’m not sure if that’s still meaningfully true if the source data for the uint16 texture was 10- or 12-bit.
Very cool, grabbed the three.js off GitHub, can confirm that 16-bit RGBA now works with texture.normalized = true.
Sadly, the GL extension necessary for that is not supported in Firefox.
It seems that there won’t be a workaround that avoids that one per-element array operation (which is quite costly time-wise), it’s either adding Alpha channel, splitting the array or converting to different array type. Or a mix of those.
Anyway, thanks again for the detailed information! This has been very useful to me.