How to improve texture first-render performance

Hi

My animation is slow when CanvasTextures first come into view but then is fast, presumably after some kind of lazy-processing has finished. Please help me understand what causes this and how I might improve the performance.

  • I have about 50 Sprites with a CanvasTexture.
  • The Canvas is 64 pixels high and as wide as needed for up to ~30 characters of text (power of 2).
  • When I move the camera in my scene, there is a lag when this text first comes into the camera’s viewing frustum (as long as 1 second to draw some frames, maybe 5 seconds total additional lag).
  • After all the text has “loaded”, the performance is good.
  • Setting texture.generateMipmaps = false; (and texture.minFilter = LinearFilter;) does not improve the performance (though visibly does affect rendering).
  • Increasing/decreasing the width/height of the texture by a factor of 4 does not make a noticeable difference.
  • Using an indentical dummy texture instead of the text does improve performance.

This is on a 5-year old budget phone, Snapdragon 400, Adreno 305. No noticeable lag on an iPhone 5s or my laptop.

What causes this lag? Can I force the processing to happen before the first render? This surprises me for only 50 textures but I’m a graphics newbie. Should I put all the text into a single CanvasTexture and use UV mapping on each Sprite? Is there some kind of fixed cost per texture loaded into the GPU?

Thanks for any help.

It’s the image decode and GPU upload overhead that happens right before the first usage of a texture. You can try to use WebGLRenderer.initTexture(), a new method that will be available with the next release R110 at the end of October. The method ensures to decode/upload the texture data before your actually use it (see WebGLRenderer: Added .initTexture(). by Mugen87 · Pull Request #17697 · mrdoob/three.js · GitHub).

Otherwise yo have to render the canvas texture once right at the beginning of your app. This will lead to the same effect like using WebGLRenderer.initTexture().

Another option is the usage of ImageBitmap. You can create it with your canvas via createImageBitmap() and then use it as your input for CanvasTexture. The following example illustrates this workflow:

https://threejs.org/examples/webgl_loader_imagebitmap

This will also noticeable decrease the decode overhead.

3 Likes

Thanks for your helpful comments @Mugen87.

Re WebGLRenderer.initTexture (or rendering the textures once at the beginning), okay yes I might try that when R110 is out. Although if I am really am experiencing 5 seconds of total delay, eager loading is still not ideal (but yes this answers specifically what I asked!)

Re ImageBitmap, I’m not sure I understand this. I tried replacing new CanvasTexture(canvas) with new CanvasTexture(await createImageBitmap(canvas, ...)) but there is no difference as far as I can see. Isn’t the image within a canvas already in the most efficient format (raw array basically, doesn’t need decoding)? Or have I missed the point? Does using the image bitmap loader cause an eager render?

I will probably now try putting all the text into a single CanvasTexture (“sprite sheet”?) but I didn’t want to waste time on this if there is no reason for it to work. Any idea if this makes sense? Maybe it’s easier than I think so I will update later.

Can you try to demonstrate the performance problem with a live example? In this way, it will be easier to analyze the issue.

For what it’s worth, https://jsfiddle.net/8puwzbj6, but running that fiddle on my phone did not give noticeable lag, so it just demonstrates fairly accurately how I’m creating the text sprites. Just to reiterate I notice the lag on a 5 year old budget phone (and not on my laptop). Maybe that performance is simply to be expected.

I know it’s hard to see this from the screenshot but the the green bars represent the GPU overhead when the upload of your 100 textures is performed. However, this processing isn’t noticeable on my iMac and not on my three year old Pixel 1. But yeah, older phones might have problem.

Thanks, interesting to see.

Just reporting back on the effect of loading all the text into a few 1024x1024 textures (sprite sheets) and using UV mapping. (This also required changing from a Sprite/SpriteMaterial to a simple BufferGeometry/MeshBasicMaterial/Mesh and using mesh.quaternion.copy(camera.quaternion) because I couldn’t work out how to do UV mapping with a Sprite.)

YES the performance lag was completely gone using the sprite sheet. FPS was the same in both cases after the initial lag. But that initial lag was roughly 5 seconds total for about 50 textures typically 64x256 in size on old mobile hardware. Hope that’s useful to someone.

1 Like