Massive Performance Update on three.quarks

In last three.quarks update, the newest BatchParticleRenderer batches particle systems with the same textures and shader settings. It avoids creating and destroying buffers dynamically and reduces hundreds of drawcalls in our game.

new BatchParticleRender handles 100 Muzzle flash, 600 particle systems with 144hz
new BatchParticleRender handles 100 Muzzle flash, 600 particle systems with 144hz

By investigating on staroyale.io's Google Analytics records, we find out our game didn't performed well on machines with old browsers and GPU. That's why we started our performance optimization recently. The part we focus on today is our WebGL particle system three.quarks, which is built in house.

Before the latest performance, for each particle system, three.quarks uses an InstancedBufferGeometry to render it. It works fine for the situation which has many particles but a few systems. But in most games, each special effect is usually composed by multiple systems. And real games have hundreds of special effect running at the same time such as muzzle flashes, projectile effect, hit impact effect and so on. Because each InstancedBufferGeometry will creates bunch of VertexBuffers and cost a draw call per frame. The lag of particle systems becomes quickly unbearable.

In last three.quarks update, the newest BatchParticleRenderer solves this issue by batching particle systems that has same textures and shader settings. In update loop, it updates all the particle in the systems with same settings to a single set of buffers. It avoids creating and destroying buffers dynamically and reduces hundreds of drawcalls in our game.

After this update, we also find another bottleneck we have on our particle system is caused by one of the last updates on WebGLRenderer in three.js. If a material is transparent and double sided, the renderer will set material needs update to true and render it twice. The biggest problem here is by setting needsUpdate to true. It auto increments the version of the material and forces the renderer to generate new Program cache by calling this extremely expensive function getProgramCachedKey, which occupies 30% of the total scripting time in our test.

By removing this piece of code in official three.js, we never hit any bottleneck of particle systems in our game anymore. For better performance, consider use our version of three.js for your performance demanding 3d application.

    if ( material.transparent === true && material.side === DoubleSide ) {
        material.side = BackSide;
        material.needsUpdate = true;
        _this.renderBufferDirect( camera, scene, geometry, material, object, group );
        material.side = FrontSide;material.needsUpdate = true;
        _this.renderBufferDirect( camera, scene, geometry, material, object, group );
        material.side = DoubleSide;
    }