Framedrop when object appears in view

Hi,

Whenever my camera moves and an objects appears in the view frustum, I’m getting a large lag spike. I have tried running WebGLRenderer.compile() but to no avail. The jank seems less when I run WebGLRenderer.compile first but it’s not completely gone. The chrome profiler shows it’s taking a while to execute texImage2D. Does WebGLRenderer.compile not take care of texture loading or am I messing up somewhere?

1 Like

Hmm I found a fix. But I’m not sure if this is how I’m supposed to do it.
After calling WebGLRenderer.compile I now also call WebGLRenderer.renderBufferDirect() with all the required parameters for an object. That seems to trigger the texImage2D lag spike.
Actually texImage2D doesn’t take very long to execute but the profiler shows that the GPU is busy for a good 200ms per texture right after the texImage2D call.

I feel like I’m not doing this the right way though. If so, can someone clarify how this should be done instead?

Well, this did the job
scene.traverse(obj => obj.frustumCulled = false);

Still not sure if this is the best way to go about doing this but at least I don’t have to call renderBufferDirect with all its parameters. With frustumCulled set to false you also don’t have to call WebGLRenderer.compile() anymore. You might want to set frustumCulled back to true after rendering one frame though.

It’s in general no good approach to change parameters in a trial and error fashion and hope that performance gets better. Setting the frustumCulled property of all objects to false is basically bad for performance since it prevents view frustum culling which is an mechanism of the engine in order to improve rendering performance.

This usually indicates a decode overhead of the texture. Try to use ImageBitmapLoader to mitigate this issue. An other option to speedup the texture upload is the usage of a texture compression format. So instead of JPG or PNG you are using a standard like S3TC or ASTC. However, the usage of texture compression with WebGL is not easy because there is no format that is supported by all hardware platforms.

Of course you can also just lower the resolution and thus the size of your existing textures. Smaller textures means less data to decode and less bandwidth usage.

Thanks for the info! I’ll try that out. I’m loading all my assets as one packaged binary file so all the textures that are not attached to a gltf object are being loaded as arraybuffer. I’m creating a new Image() and passing it to the THREE.Texture constructor. I guess that’s part of the problem. I’ll try to pass on the image object url on to the ImageBitmapLoader and see if that makes a difference. Using a texture compression standard sounds like a good idea too.

I have tried both ImageBitmapLoader and TextureLoader but neither of them seem to solve the issue and the lag spike is still the same. I’ll wait with other compression formats since that’s going to take a bit more work. We’ve already lowered the resolution of the texture but if we lower it even further it will start to get really noticable.

I’m ok with there being a large framedrop but I would like to offset this to before we start rendering, WebGLRenderer.compile() doesn’t seem to do this.

The image below shows what’s causing the lag spike according to the Chrome profiler, is there any way to make sure this path gets called on page load? Setting frustumCulled to false for one frame seems to work but I agree that’s not a very clean solution.
image

Any chances to demonstrate the issue with a live example. I’m a bit surprised that the usage of ImageBitmap does not help at all.

Yeah sure, I’ll try to make a simple demo later today

alright here’s a simple demo:
texture loading lag spike.zip (2.3 MB)

You’ll have to host it locally before you can run it.

  • If you press the ‘cube visible’ button at the top, you’ll get a lag spike the first frame the cube is visible.
  • When you refresh the page and click the WebGLRenderer.compile() function first before clicking ‘cube visible’ you’ll still get a lag spike but it’s a bit shorter.
  • When you refresh and click ‘disable culling one frame’ and then ‘cube visible’ you won’t get a lag spike at all.

Anyway that’s the behaviour I’m seeing. Let me know if you can reproduce this.

Disabling view frustum culling ensures that the metallic cube is already rendered even when it’s not in the view. The removes the lag but your general performance is worse since you always render all objects, no matter if they are in your view frustum or not. So this approach is not recommended.

Using ImageBitmapLoader definitely improves the performance. You can see here the upload of your three textures (diffuse, normal and metalness map) without ImageBitmapLoader.

image

And now with ImageBitmapLoader. Notice how the decode overhead disappears. So the overall performance when rendering the cube for the first time is better.

image

Hmm interesting. I wasn’t getting these results with ImageBitmapLoader, though I haven’t tested it with the simple demo I sent earlier. Any chance I could see your implementation?

By the way, I’m disabling the view frustum culling only for one frame, so there shouldn’t be any additional overhead during the rendering of the rest of the frames, right?

That’s correct.

Unfortunately, I’ve already deleted my test code. But the good thing is you can use the following example as a code template: three.js webgl - loader - ImageBitmap

I’ve used a loading manager in order to wait until all three textures are loaded and then create the mesh in the respective onLoad() callback. Before you start using this approach in your code, it’s important to wait until the following PR is merged since I’ve found a little bug when using LoadingManager with ImageBitmapLoader ^^

Thanks, I managed to get the ImageBitmapLoader to work and it seems a bit faster, but not that much. Perhaps I didn’t implement it correctly? texture loading lag spike.zip (2.3 MB)

TextureLoader:
image

ImageBitmapLoader:
image

In this example only a few frames get dropped. But in our project the camera does a similar movement like this and there are a lot more materials and textures. So the framedrops are quite a lot more significant.

Ideally we would be able to preload these textures before the camera movement even happens. Is there any way to do this without hacks like frustumCulled = false?

Um, if WebGLRenderer.compile() does not help, it seems there is no way around the frustumCulled hack for you right now.

BTW: Your code looks good. But notice that you can use a single instance of ImageBitmapLoader. No need to create a single loader per request.

Alright I’ll keep it like this for now. If there’s anything I can do to add this sort of functionality to three.js, perhaps in WebGLRenderer.compile() or perhaps in a new function on the WebGLRenderer, feel free to let me know. I’d love to contribute :slight_smile:

Ah thanks! I’ll change that.