rive-app / rive-wasm

Wasm/JS runtime for Rive
MIT License
660 stars 46 forks source link

Expose synchronous render option on runtime object #340

Closed jozefchutka closed 4 months ago

jozefchutka commented 7 months ago

I am using v2.7.4 with canvas, and managed to follow https://help.rive.app/runtimes/overview/web-js/low-level-api-usage in order to create my own renderLoop. My use case is however different as I want to create snapshots at particular times of rive animation.

The problem with the current API is that in order to have canvas rendered, one has to "seek" (call all the necessary artboard, renderer and animator methods) followed by two requestAnimationFrame:

animator.animations.forEach(animation => {
    animation.advance(delta);
    animation.apply(1.0);
});

artboard.advance(delta);
renderer.clear();
renderer.save();
rive.alignRenderer();
artboard.draw(renderer);
renderer.restore();
renderer.flush();

animator.handleLooping();
animator.handleStateChanges();
animator.handleAdvancing(delta);

// at this <canvas> is empty
runtime.requestAnimationFrame(() => {
    // at this point <canvas> is still empty but runtime is to draw it soon 
    runtime.requestAnimationFrame(() => {
        // <canvas> is finally rendered
    })
})

The workaround I am using is customized rive.js where I just inserted my own render method:

...h.requestAnimationFrame=d.requestAnimationFrame.bind(d);h.cancelAnimationFrame=d...

replaced by

...h.requestAnimationFrame=d.requestAnimationFrame.bind(d);h.render=()=>d.fb();h.cancelAnimationFrame=d

with this change in place I can replace rAF-s and simply call

...advancing, clearing, saving, drawing...
animator.handleAdvancing(delta);

runtime.render()
// <canvas> is rendered

I would like to propose a new render() method exposed on runtime object, so one can request rendering synchronously without having to involve animation frame request.

zplata commented 7 months ago

Thanks for bringing this up! If I understand your problem right, it sounds much like the request here: https://github.com/rive-app/rive-wasm/issues/336#issuecomment-1811237753

Where we have a list of deferred render calls that get invoked only in our wrapped rAF loop. We've got some staged changes on our end to expose a call as something like resolveRenderer() or resolveRendererCalls() on the rive API so you can call this and the draw calls should be invoked at that time, rather than in the wrapped rAF. I think this might be what you'd also want with your solution, but feel free to correct me if I'm mistaken. With this it might look like:

artboard.advance(delta);
renderer.clear();
renderer.save();
rive.alignRenderer();
artboard.draw(renderer);
renderer.restore();
renderer.flush();

animator.handleLooping();
animator.handleStateChanges();
animator.handleAdvancing(delta);
// NEW
rive.resolveRenderer();

This change should land hopefully soon here! We had to shift briefly to some other priorities but we have some tested changes on our end in a branch to introduce this.

jozefchutka commented 7 months ago

Sounds like resolveRenderer should do the job. Looking forward to have it released

zplata commented 5 months ago

Hi sorry for the delay here, you can check out this API in v2.10.0. You just call rive.resolveAnimationFrame() at the end of the loop like in the snippet above. We're working on updating docs so that should reflect in the API descriptions soon, but feel free to check it out today.

jozefchutka commented 4 months ago

Hi, I have checked resolveAnimationFrame() in v2.10.3 and confirm it works. Thanks for adding it, feel free to close the issue.