NASA-AMMOS / 3DTilesRendererJS

Renderer for 3D Tiles in Javascript using three.js
https://nasa-ammos.github.io/3DTilesRendererJS/example/bundle/mars.html
Apache License 2.0
1.63k stars 290 forks source link

Small optimizations: pre upload geometry, textures before rendering with them #490

Open gkjohnson opened 9 months ago

gkjohnson commented 9 months ago

First measure the potential performance impact of these upload times. They may be small / not worthwhile considering the size of geometry and textures in the typical tile. But this could skip the waiting on render before upload.

Nmzik commented 6 months ago

@gkjohnson Do you mean by "pre-uploading" sending data (geometry and textures) bufferData() compressedTexImage2D() to the GPU driver? Or do you simply mean calling initTexture() in threeJS on each texture as soon as GLTF is parsed and before re-rendering the scene?

Regarding uploading to the GPU driver, uploading geometry is mostly fine for me (around 0.60ms is spent on bufferData - each tile geometry is around 3MB of vertex data).

However, regarding textures, I've encountered a completely different result - over 20ms is spent on uploading a 4096x4096 KTX2 texture to WebGL (on Windows, essentially on DirectX 11). The issue is, I haven't found a proper way in threeJS to upload part of a texture to the preallocated buffer (for example, 2MB of the texture per frame) and when the texture is uploaded to the buffer (after a few frames), create a texture (webGL object) and copy asynchronously from the buffer to the texture. DataTexture in threeJS seems to upload the texture fully onto the GPU, which can cause stalls. Partial updates are not supported for compressed textures (correct me if I'm wrong). The only solution I've found is to create a PBO and do it this way. (But again, this requires a threeJS patch/PR).

My apologies if I misunderstood your issue.

gkjohnson commented 6 months ago

Do you mean by "pre-uploading" sending data (geometry and textures) bufferData() compressedTexImage2D() to the GPU driver? Or do you simply mean calling initTexture() in threeJS on each texture as soon as GLTF is parsed and before re-rendering the scene?

Originally I was imagining it might be possible to asynchronously uploading the geometry and textures and then only render once they have been uploaded so no blocking on rendering occurs (similar to compileAsync for materials) but this likely isn't how data upload can work in WebGL 2.

What we can do, though, is only upload 1 texture per frame or a certain amount of texture MB to limit the amount of stalling due to uploading at a time, which should help at least some.

However, regarding textures, I've encountered a completely different result - over 20ms is spent on uploading a 4096x4096 KTX2 texture to WebGL (on Windows, essentially on DirectX 11).

4K textures may not be ideal for something like 3d tiles where they're being uploaded and disposed consistently for this reason. If I recall correctly in our Mars 3d tiles our texture dimensions are limited to 256 and mipmaps are disabled to save time on generation with the understanding that as the camera gets closer and the texture becomes more visible, multiple new tiles and therefore more texture resolution will become visible.

Likewise using textures that are compatible with being loaded as an ImageBitmap (KTX2 is not I don't believe) can help improve upload times as far as I know. Hopefully there's still more we can do to decrease the hiccups on upload but even so there's a balance to strike between asset download sizes and performance of GPU upload.

The issue is, I haven't found a proper way in threeJS to upload part of a texture to the preallocated buffer

I don't know about how it works with compressed textures but with my recent copyTextureToTexture PR images and data textures and be progressively uploaded by copying chunks of an image at a time to a GPU-bound texture. If uploading one texture per frame still isn't good enough then it may be possible to progressively upload texture data.

Very happy to have some help thinking through some of these problems. It would be great to get something like the Google Earth example loading more smoothly with fewer frame drops!