Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance improvements #65

Closed
FlorentMasson opened this issue Nov 15, 2019 · 21 comments
Closed

Performance improvements #65

FlorentMasson opened this issue Nov 15, 2019 · 21 comments
Labels
enhancement New feature or request selected Will be worked on next

Comments

@FlorentMasson
Copy link

I've been doing some performance analysis with chrome dev tools because fps was pretty low for my usage.

  1. PUID is really inefficient as it gets called a lot and iterate over the whole array of particles. Replacing the array with a Map looks like a better idea
export default {
    _id: 0,
    _uids: new Map(), //maps objects to IDs
    id: function (functionOrObject) {
        if (this._uids.has(functionOrObject))
            return this._uids.get(functionOrObject);

        const nid = 'PUID_' + this._id++;

        this._uids.set(functionOrObject, nid);

        return nid;
    }
};
  1. Setting rotation for Sprites in MeshRenderer consumes a lot of CPU for nothing... as rotating a Sprite has no effect (you need to rotate the material)
constructor(container, THREE) {
        [...]
        this._THREE = THREE;
    }
[...]
onParticleUpdate(particle) {
        if (particle.target) {
            particle.target.position.copy(particle.position);
            if (!(particle.target instanceof this._THREE.Sprite))
                particle.target.rotation.set(
                    particle.rotation.x,
                    particle.rotation.y,
                    particle.rotation.z
                );
            this.scale(particle);

            if (particle.useAlpha) {
                particle.target.material.opacity = particle.alpha;
                particle.target.material.transparent = true;
            }

            if (particle.useColor) {
                particle.target.material.color.copy(particle.color);
            }
        }
    }

(alternatively, could override the whole function in SpriteRenderer)

Could you fix these or do you need a PR @rohan-deshpande ?

@rohan-deshpande rohan-deshpande added the enhancement New feature or request label Nov 15, 2019
@rohan-deshpande
Copy link
Collaborator

Amazing stuff. I’ve not really gotten that deep into the performance of the library yet, I knew there would be bottlenecks though so thank you for finding these.

I will get to these this weekend. Can I ask for some screens / data from your dev tools investigations? Would love to see them.

@FlorentMasson
Copy link
Author

Here's a screen for the issue with PUID, which is really striking as it's the top1
image

Here's a screen about the rotation (it was more consuming on my other computer)
image

There are definitely some other performance improvements... right now the particles engine consumes 1/3 of the CPU for ~300 sprites

@rohan-deshpande rohan-deshpande added the selected Will be worked on next label Dec 10, 2019
@rohan-deshpande rohan-deshpande pinned this issue Dec 10, 2019
@rohan-deshpande
Copy link
Collaborator

Sorry for the delay @FlorentMasson, working on this right now! Will hopefully have a branch up this weekend, would be great if you could help me benchmark a bit if you have some time.

@FlorentMasson
Copy link
Author

Sure, let me know when it's ready.
FYI I've digged a little more through performance and the highest bottleneck right now is the number of drawcalls, there's 1 draw call per particle in Nebula
Here is an example with (I think) the right way to do: https://stemkoski.github.io/Three.js/Particle-Engine.html
Each emitter, is only a single mesh with 1 vertice per particle and a custom shader for rendering, which makes it 1 draw call per emitter and is very fast

@rohan-deshpande
Copy link
Collaborator

rohan-deshpande commented Dec 14, 2019

@FlorentMasson see #68, would be great to see how the benchmarks look after these changes!

@rohan-deshpande
Copy link
Collaborator

Sure, let me know when it's ready.
FYI I've digged a little more through performance and the highest bottleneck right now is the number of drawcalls, there's 1 draw call per particle in Nebula
Here is an example with (I think) the right way to do: https://stemkoski.github.io/Three.js/Particle-Engine.html
Each emitter, is only a single mesh with 1 vertice per particle and a custom shader for rendering, which makes it 1 draw call per emitter and is very fast

@FlorentMasson I've created #71 to track this other improvement

@rohan-deshpande
Copy link
Collaborator

@FlorentMasson do you mind providing the test case you were looking at as well as how you're running the chrome performance profiler? I am not seeing as much of a granular view, are you running against unbuilt/minified code?

@FlorentMasson
Copy link
Author

Here is the emitter I have been using:

        var trailEmitterMaterial = new THREE.SpriteMaterial({
            map: new THREE.TextureLoader().load("https://i.postimg.cc/vB5z8vjY/Cloud.png"),
            blending: THREE.NormalBlending,
            fog: true,
        });
        var trailEmitterStartColor = '#80FC3C';
        var trailEmitterEndColor = '#F9FFAD';

        function createSprite() {
            var clone = trailEmitterMaterial.clone();
            clone.rotation = Math.random() * 180 * Math.PI / 180;
            return new THREE.Sprite(clone);
        }

        var emitter = new Nebula.Emitter()
                        .setRate(new Nebula.Rate(4, 1 / 60))
                        .setInitializers([
                        new Nebula.Body(createSprite()),
                        new Nebula.Life(0.6),
                        new Nebula.Radius(0.7),
                    ])
                        .setBehaviours([
                        new Nebula.Alpha(0.4, 0),
                        new Nebula.Color(trailEmitterStartColor, trailEmitterEndColor),
                        new Nebula.Scale(0.4, 0.6),
                        new Nebula.Rotate(0, 0, 90),
                      ])
                        .setPosition(position)
                        .emit();

I had just rebuilt the minified version (with your build script) with my changes included and used it as is. But to see the granular view, you have to place the .map file in the same directory so that chrome devtools can decode it properly (or include the source instead)

@rohan-deshpande
Copy link
Collaborator

Have you been able to try out the changes in #68? I’ve got a bunch of other perf improvements coming including a GPURenderer but would be great to see if that change reduced some of the expensive function calls you were seeing

@FlorentMasson
Copy link
Author

Not specifically #68 but as it's very close to my suggested changes I've been using it for a while. It helped reduce the CPU overhead, but not the number of GPU draw calls.
Because the performance was still not good enough (mainly because of draw calls), I've since switched to a custom made particle engine based on https://stemkoski.github.io/Three.js/Particle-Engine.html, ported to last three.js version and added a few needed features.

@rohan-deshpande
Copy link
Collaborator

I’ve added a GPURenderer which is in develop now which reduces the draw calls to 1, please try it out :)

@FlorentMasson
Copy link
Author

Wow, looks great! I hadn't seen it as it's in the develop branch.

2 caveats I encountered while using GL_POINTS (ie THREE.Points):

  • Points behave badly when rotated and zoomed in very big, there are leaks around the border
    You can see this in Stemkoski's star fountain example
    image
    It's worse with some sprite images, like a plain circle. A workaround is to add more margin around the sprite's image.

  • the maximum displayed sprite size (in pixels) is constrained by gl_aliased_point_size_range. It corresponds to "Aliased Point Size Range" on https://webglreport.com/. The bad thing is it depends on the device/GPU and can be very low (such as 64px on not-so-old macbook air or chromebook). So it can't work for big sprites on the camera, and should have a fallback with quads instead.

@rohan-deshpande
Copy link
Collaborator

rohan-deshpande commented Feb 14, 2020

Wow, looks great! I hadn't seen it as it's in the develop branch.

Yeah I only got it done recently, I'm about to do a big release which will also announce a fully featured GUI editor for making particle systems, so watch this space!

Those are interesting points about THREE.Points 😉, I wasn't aware of them. I can't really see what your image here is illustrating though, maybe it's worthwhile to raise an issue over in the Three repo?

On from this, if you wouldn't mind doing your previous perf benchmarks against #68 I would really appreciate it. If this does improve CPU perf I would like to merge it ASAP!

@FlorentMasson
Copy link
Author

It's more obvious from this position
There are leaks and cuts everywhere
image

I don't think three.js is the cause, I think it's more a limitation with how OpenGL renders points.

@davidpuetter
Copy link

Hey all, I'm also testing the GPURenderer and the performance improvement is incredible. I'm having issues with rendering however, seems like depth-testing isn't working.
chrome_ed6PRlcrc2
chrome_AVWCXcnV4f

System is created like so:

new Nebula.System.fromJSONAsync(json, THREE)
	.then(system => {
		this._system = system;
		this._emitter = this._system.emitters[0];
		this._emitter.emit();
		this._system.renderer = new Nebula.GPURenderer(this, THREE);
		this._system.addRenderer(this._system.renderer);
	});

Let me know if you want the Json data too.

@FlorentMasson
Copy link
Author

@davidpox have you changed the depthTest and depthWrite values for your THREE.SpriteMaterial?

@davidpuetter
Copy link

@FlorentMasson
I haven't changed it on my end. Default is true for both, correct?

@rohan-deshpande
Copy link
Collaborator

@davidpox Thank you for testing it out! Okay so you aren't doing anything wrong here at all as far as system creation is going. It should be a drop in replacement for the SpriteRenderer so if something is broken, I'll need to fix it.

Do you mind posting screens of swapping out the renderers so I can compare?

@davidpuetter
Copy link

@rohan-deshpande Sure :
chrome_5o2fDGAeHf
chrome_iD8jEOqr7P

Might also be worth noting that spine-ts/threejs objects correctly appear in front of the particles on both renderers, like so:
chrome_o4A9XAKmM8

@rohan-deshpande
Copy link
Collaborator

Okay yep, it's an issue, I will track this in a new issue and mention you there @davidpox thanks for reporting

@rohan-deshpande
Copy link
Collaborator

Resolved by #89 and #68

@rohan-deshpande rohan-deshpande unpinned this issue Nov 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request selected Will be worked on next
Projects
None yet
Development

No branches or pull requests

3 participants