vg-renderer icon indicating copy to clipboard operation
vg-renderer copied to clipboard

viewID at context creation is too restrictive

Open hugoam opened this issue 5 years ago • 8 comments

I'm just now looking into multi-window rendering, so for the first time I will need more than one UI, and it would be much more convenient if viewID was specified when submitting instead of at context creation. I'll change my local copy and try this, but if you want to start thinking about it and giving me your thoughts :)

hugoam avatar Mar 21 '19 12:03 hugoam

Looking into this further, I discovered that there are hard limitations that prevent to use the same vg::Context to render to two different windows during a same render frame. Mostly it seems to be due to the buffers that are stored "globally" for one given begin/end call pair, so if you call those twice in a row, the buffers of the second batch get sent to both views.

I understand that this is probably not the simplest thing to change, so I made it work with one vg::Context per render window, but I believe that's a waste of resources, I'd like to be able to render to say 12 windows, and I don't want to have 12 vg::Context (I should be able to share everything that's common: images, fonts, etc and not multiply those by 12)... So I'll look into the changes needed to do that a bit later, if it's only about storing N buffers instead of 1, I should be able to make it work.

hugoam avatar Mar 21 '19 15:03 hugoam

You are right. I haven't thought about that.

One of the things in my TODO list is some kind of layer system. I haven't thought about it in detail but the general idea was to be able to render to different layers and when submitting, layers will be drawn back to front. E.g.

vg::beginFrame(ctx, ...)
vg::setLayer(ctx, 0);
drawShape0(ctx);

vg::setLayer(ctx, 1);
drawShape1(ctx);

vg::setLayer(ctx, 0); // Rebind layer 0 again.
drawShape2(ctx);

vg::endFrame(ctx); // Render order should be: Layer[0]: Shape0, Shape2, Layer[1]: Shape1

In this case Shape1 will be rendered on top of Shape2 because it's drawn into layer 1.

This requires different vertex and index buffers per layer. So if this is implemented, you can attach a separate viewID to each layer and then let endFrame() submit everything in order.

Hope it makes sense. As I said, it's still an idea, so I don't know if it's as simple as it sounds to implement. If you happen to attempt to make the changes you suggested, maybe you should think about that too.

jdryg avatar Mar 21 '19 15:03 jdryg

That could do it, yep. Once there is a concept of separate destination buffers internally, it should be easy to implement what I need, whether explicitly with layers, or whether allowing multiple "frames" per "frame". I'll look into it :)

hugoam avatar Mar 21 '19 16:03 hugoam

Just a note to feed your thoughts: this will require in any case, to be able to pass the viewID in one of those steps. Currently I put it in endFrame, but if you want layers it could make sense to declare the viewID per layer.

(Now that I think of it, layers could be great, because it might be a cheaper version of shape caching, when you just want to redraw a whole layer with no modifications. I feel it might better suit a retained UI system)

hugoam avatar Mar 21 '19 16:03 hugoam

Just a note to feed your thoughts: this will require in any case, to be able to pass the viewID in one of those steps. Currently I put it in endFrame, but if you want layers it could make sense to declare the viewID per layer.

Yes. The number of layers and their view IDs can either be passed as args to the createContext() function or as members of the ContextConfig struct .

The Layer struct should include a viewID, an array of DrawCommands, an array of clip DrawCommands, an array of VertexBuffers/GPUVertexBuffers pairs and an IndexBuffer/GPUIndexBuffer pair. The corresponding members of the Context struct should be removed and replaced by an array of Layers.

Note that State (matrices, scissors, etc) should probably be shared between layers (in other words, it'll be the responsibility of the client to reset the state to a known value before rendering to a different layer).

(Now that I think of it, layers could be great, because it might be a cheaper version of shape caching, when you just want to redraw a whole layer with no modifications. I feel it might better suit a retained UI system)

The main problem is the font atlas. You cannot guarantee that the atlas will be the same between frames. This is the reason why shape caching doesn't work with text (text commands are always submitted seperately from shape commands in clCacheRender).

jdryg avatar Mar 21 '19 16:03 jdryg

I just pushed the first version of the layers API to the layers branch. Initial tests with 2 layers, drawing to the same framebuffer, seems to work in my case (1 layer for the level geometry/schematic + 1 layer for the UI).

Try it out when you find some time and let me know if anything is wrong.

A few notes:

  • The number of layers and their view IDs are specified at context creation time.
  • The State stack is shared between layers.
  • Images, image patterns, gradients and fonts (+atlas) are shared between layers.
  • Layers are submitted in order (from 0 to N-1).
  • Remember to disable clearing bgfx views shared between layers multiple times.

I don't really have a use for this at the moment, so until I (or you) have the chance to properly test it out, it will stay in a separate branch.

jdryg avatar May 15 '19 13:05 jdryg

That sounds really great ! I don't need it anymore for multi-window because I'm not doing multi-window at all in the end, but this should prove very useful to not redraw everything every frame. Would the design allow to not redraw a layer but still submit it ? Thinking about it that probably might require to make the buffer a regular vertex buffer and not transient, so not a trivial change, but maybe this could be done by setting a "cached" option on the layer ? Now that most of the layer API work is done, I might play with this myself.

hugoam avatar May 17 '19 12:05 hugoam

Would the design allow to not redraw a layer but still submit it ?

Currently no, this isn't allowed. In order to be able to do that I either need to allocate separate vertex buffers per layer (currently all layers share the same pool of VBs/IBs), or keep a command list per layer which will be submitted on endFrame(). Whichever way is used, there should also be a function to let the user clear a layer on demand (currently it's automatically "cleared" in beginFrame()).

Thinking about it that probably might require to make the buffer a regular vertex buffer and not transient, so not a trivial change, but maybe this could be done by setting a "cached" option on the layer ?

That might work with the command list option I mentioned above, but it still won't be exactly what you described. E.g. it won't cache bgfx vertex buffers but tesselated paths (similar to how cached command lists work). In other words, copying data between vg VBs and bgfx VBs will still be required.

Now that most of the layer API work is done, I might play with this myself.

Unfortunately, there are some things I haven't figured out yet about layers. E.g. should the state be shared between layers? Should command lists allow changing a layer (clSetLayer())? If not, it won't work correctly with beginCommandList()/endCommandList() functions you requested some time ago. If yes, then the command list per layer option I mentioned above gets even more complex.

And while I'm thinking about those things, I'm also thinking if it would be better to make the API stateless (no transformXXXX() or scissor related functions, just a setState()) and let the user specify the state for every path, similar to how bgfx works.

If you happen to try the code, report back any problems with the current version. Since you don't need it anymore for multi-window rendering, please describe any other potential use case you might think of.

Thanks :)

jdryg avatar May 17 '19 14:05 jdryg