Ideas why r116 may cause outline pass to affect performance?

When attaching outline pass to an object, I see a large performance drop in Three.js r116

While r115 I did not have these issues

Any ideas why this may be happening? Here is my setup, it largely follows the example on the three js site.

export function setup(element: HTMLElement, farmId: string, debug=false, shadowsEnabled=false) {

    const renderer = setupRenderer()
    const scene = new Scene()
    const camera = setupCamera()

    const [composer, outlinePass] = setupPostProcessing(renderer, camera, scene)
    const [mapControls] = setupControls(renderer, camera)        

    scene.add(dragControls)
    scene.add(lights)

    setupRenderloop(scene, camera, mapControls, composer, stats)
    setupResizeHandler(renderer, camera, composer)
    setupActionController(element, farm, camera, outlinePass, dragControls)

    element.appendChild(renderer.domElement)  
}

function setupRenderer(): WebGLRenderer {
    const renderer = new WebGLRenderer({ antialias: true })
    
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.gammaFactor = 2.2    
    renderer.physicallyCorrectLights = true
    renderer.outputEncoding = sRGBEncoding
    renderer.toneMapping = ACESFilmicToneMapping
    renderer.setClearColor(Colors.SKY, 1)

    renderer.shadowMap.enabled = true
    renderer.shadowMap.type = PCFShadowMap
    
    return renderer
}

function setupPostProcessing(
    renderer: WebGLRenderer, camera: Camera, scene: Scene
): [EffectComposer, OutlinePass] {
    const composer = new EffectComposer(renderer)
    const renderPass = new RenderPass(scene, camera)
    const outlinePass = new OutlinePass(new Vector2(window.innerWidth, window.innerHeight), scene, camera)

    outlinePass.edgeThickness = 2
    outlinePass.edgeStrength = 10
    outlinePass.visibleEdgeColor.set(0xffffff)
    outlinePass.hiddenEdgeColor.set(0x190a05)
    composer.addPass(renderPass)
    composer.addPass(outlinePass)
    renderPass.renderToScreen = true

    return [composer, outlinePass]
}

function setupRenderloop(
    scene: Scene, camera: PerspectiveCamera, mapControls: MapControls, composer: EffectComposer, stats: Stats
) {
    const onAnimationFrameHandler = () => {
        
        if (stats) stats.begin()
        
        mapControls.update()
        composer.render()
        updateTextSprites(scene, camera)
        
        if (stats) stats.end()

        window.requestAnimationFrame(onAnimationFrameHandler)
    }

    window.requestAnimationFrame(onAnimationFrameHandler)
}

I’ve omitted some code that does not pertain to this issue (like setupControls).

1 Like

Is there a chance you could profile a few frames using chrome’s dev tools profiler, and post a screenshot here? I think i noticed some odd behavior in the last couple of versions.

Is this what you’re looking for?

Edit: another screenshot drilling in a bit further

yes thank you!

The calls that i’m curious about are these getProgramInfoLog. What value is this set to?

  renderer.debug.checkShaderErrors

My knowledge here is limited to make a guess, but it seems that some cache or something is getting busted and that acquiring a shader is taking either too long or is happening too frequently. Could you confirm that getProgramInfoLog either takes less time or doesnt appear when you turn off the outline pass or run in 115?

I’m not sure if I’m not looking in the right spot but this is the r115 build

This looks like a slower frame that is doing a lot of matrix work. This makes sense. If you collapse it a bit to show several frames, i expect we won’t see the getProgramInfoLog call. To be clear, the bottom portion of the screen shot is not super relevant (the list of calls), what’s of interest is the chart, (colored bars). Can you post another one, with the frame a bit wider (right now it’s covering just one, try to make it wider so that it covers at least two).

If one of these “Animation Frame Fired” can be less than 16 seconds, you will get 60fps. If it takes 100ms, you will get 10fps. We’re looking at the difference between the versions.

Gotcha, here’s 3 frames

This is insightful as to how to debug a three.js app, thanks for your help on this!

Let’s back-off for a second.

The problem is that something is forcing a material recompile on every frame, let’s try to narrow it down a bit.

If you don’t add the OutlinePass to the EffectComposer, does it still have a performance stall?
If you comment renderer.outputEncoding, does it solve the problem?

I’ve tested OutlinePass in r116, but I can’t seem to recreate this.

2 Likes

The renderer.outputEncoding seems to be causing the issue, thank you for the tip. Why may this be? And why would it only affect me for r116?

I know your issue is related to this PR, which was filed to fix an important bug with encoding.

However, I must admit I don’t see what exactly is different in your example that would justify this. As I do not get this behavior when testing with dev OutlinePass example, even when altering the encoding.

@Mugen87 Any ideas here?

1 Like

Maybe I’ve not seen it but is there are live example that demonstrates the issue?

@baze I’m really interested in understanding what is happening here, this could potentially be a bug with three.js internal workflow. If you could provide a live example, as Mugen requested, it would really help us here.

1 Like

I have been facing the same symptoms: when updating to r116 (and also r118 now), I’ve seen a massive performance hit in my project, which is using the OutlinePass to highlight meshes when the user hovers the scene with their mouse.

After a lot of investigation, it turns out that the problem does not seem to reside in the OutlinePass or even the EffectComposer, but rather in the Raycaster! I am in fact using raycasting on mousemove to check which mesh (if any) to highlight with the OutlinePass. It turns out that with the exact same conditions (same code, same scene, …), raycaster.intersectObjects(scene.children, true) took a few milliseconds (<10) before the update, and is now in the hundreds of milliseconds after the update to r116/r118.

This of course freezes my thread, and drops the frame rate and reactiveness drastically when I’m hovering on my meshes, and everything thus feels super slow.

I hope this helps!

That is indeed strange. If you can manage do demonstrate this performance drop in a live example, I will have a look.

At the official repo, I did not see such performance regression related to raycasting since r116.

I’ll try to get some kind of live example, but this might be a wee bit difficult as my current project is pretty massive, so I’d need to write something from scratch.

In any case, I thought I’d point out that I was updating from r100 (because of this issue), so I wouldn’t be able to be sure that the change came at r116 exactly.

I have now made minimal examples using the Raycaster with r100 and r118, and they show exactly the same performance… There’s still something that I’m missing :frowning:

Investigations continue.

This turned out to be due to the fact that meshes in my scene also had wireframes added to them (to enable for a “show wireframe” mode). Apparently, the wireframe line meshes did not use to be taken into account by the raycasting and now they are, propulsing the number of hits from ~10 to ~100 000, hence the drastic drop in performance.

This also seem that it might be the case with LightHelpers and TransformControls? Could it simply be that the raycaster used to only hit with visible objects, and now it hits with everything in the scene? Would there be any way to say that the hits should only be with visible objects?

Does that make sense?

Okay, things get more clear now. Starting with r114, THREE.Raycaster also honors invisible 3D objects in intersection tests. You can use the new property Raycaster.layers for selectively ignoring 3D objects during raycasting.

This was changed since a certain amount of users wanted to perform intersections tests even with invisible objects. The usage of layers allows to decouple the visibility from the actual intersection test.

So to solve your issue, you can do the following. Configure the raycaster like so:

raycaster.layers.set( 1 );

And now enable all objects that should be honored by raycasting via:

object.layers.enable( 1 );

By default, all objects are included which your performance drop.

4 Likes