revery-ui / revery

:zap: Native, high-performance, cross-platform desktop apps - built with Reason!
https://www.outrunlabs.com/revery/
MIT License
8.06k stars 196 forks source link

feat(api): CanvasContext - Add createLayer / drawLayer methods #1004

Closed bryphe closed 3 years ago

bryphe commented 3 years ago

This adds supporting for creating new GPU-backed or CPU-backed surfaces that we can render to, cache, and re-draw later - adding a new method createLayer and drawLayer on the CanvasContext.

This lets us create a layer from an existing CanvasContext.t:

          let layer =
            CanvasContext.createLayer(
              ~width=128l,
              ~height=128l,
              canvasContext,
            );

... then draw to it:

          CanvasContext.drawCircle(
            ~x=32.,
            ~y=32.,
            ~radius=16.,
            ~paint,
            layer,
          );

Then, we can draw the layer on the current canvas:

          // And then draw the layer a bunch of places!
          CanvasContext.drawLayer(~layer, ~x=300., ~y=300., canvasContext);
          CanvasContext.drawLayer(~layer, ~x=264., ~y=264., canvasContext);
          CanvasContext.drawLayer(~layer, ~x=264., ~y=300., canvasContext);
          CanvasContext.drawLayer(~layer, ~x=300., ~y=264., canvasContext);

So it gets repeated multiple times: image

The motivation for this change is ultimately to improve performance - right now, Revery's rendering model is to redraw the entire screen, every time something changes. This is OK for small applications, but for larger applications - like Onivim:

image

It doesn't make sense for us to redraw the file explorer when the editor cursor moves... or for us to redraw the minimap when we scroll in the file explorer. In other words - we're redrawing elements way more often than we should be!

The next step is to provide a compositing primitive - a <Layer /> component - that can render to one of these surfaces, and then cache until it actually needs to render. In the case of Onivim, we could wrap parts of the UI in this primitive - the editor surface, the file explorer, the status bar, etc - so that we are only re-rendering the parts of the screen that are relevant.

It's a lot like how the browser creates separate layers based on some properties (ie, if you use transform, the browser will create a layer that can be independently animated). This is just a bit more explicit and requires the developer to opt-in.

Still need to figure out the exact API, but it'd be something like: <Layer style renderIf={Condition((!=), [state1,state2,...] /> - kind of like the condition we use for the effect hook.

github-actions[bot] commented 3 years ago

I have updated your lock dirs and formatted the code. Please @bryphe pull the last commit before pushing any more changes.