Hi All,
In early August a pull request arrived that provided a novel extensions to the VSG's DescriptorBuffer support to provide multi-buffering to avoid updates overwriting data that is being rendered by a previously submitted frames rendering:
I've sat on this PR as while it solved a problem it adds a bit of complexity (two extra classes) and require extra knowledge from user, and decided to see if I could find an alternative approach that was simpler and wider applicability.
The first branch of this work was pretty straight forward - to add Vulkan's dynamic descriptor buffer support directly into vsg::DescriptorBuffer/BindDescriptorSet(s), this is a useful Vulkan feature that was missing, but ironcally for this thread it's not really for dynamic data, but just a way of reusing vkDescritors/Sets so you don't need to use so many. This block of work was checked in 18 days ago:
Normally I'd put together an example to test and illustrate it but as I have broken arm have had keep work pretty minimal, so you'll have to make do with Sasha Williams Vulkan example using this vk feature:
If folks fancy porting this to the VSG then please do :-)
The next branch of work was to look the essence of the problem of handling data that is updated on the CPU and needs to be copied to the GPU in a way that avoids writing and reading data across frames, i.e updates from frame i don't overwrite the data being rendered by frame i-1.
One way of tackling this problem is to put a wait on fence in the main frame loop so the CPU thread doesn't get too far ahead of the rendering on the GPU. The vsgclip example used this approach, but this adds a little complexity and slows the frame rate as you are explicitly stopping the multi-threaded capability of having separate CPU and GPU cores.
The other approach is do multi-buffering, this is typically more complicated to implement, the PR that kicked off this work takes this approach. For performance it's better, but I felt there was a "more general approach" that could be done by the core VSG to provide this functionality.
Exactly what this "more general approach" wasn't obvious so it took me several goes at designing different approaches, I tried implements bits but for several weeks nothing gelled so I'd get a few days in then decide it wasn't good enough.
Only have full use of my left hand is part of why I haven't converged to a solution more quickly. Even brain storming with pen and paper was slow going as writing with the wrong hand takes such an effort it rather blocks free flow of ideas.
About a week an approach that might work well started to gel and the implementation while very slow still did steadily build something concrete and coherent enough to the fulfil the intangible sense of a design and implementation being "right".
Late yesterday I got it working with a modified vsgclip and vsgcomputevertex example, and today I added vsgdynamictexture_cs to use the new approach. I have checked the required changes into the VulkanSceneGraph and vsgExample DynamicData branches:
Github compare against vsgExamples master reports:
Showing
with
13 additions
and 84 deletions.
So lots of code removed and a small addition, this means it's much simpler for end users to now implement dynamically update data associated with vsg::DescriptorBuffer and vertex arrays associated with vsg::Geoemetry/VertexIndexDraw/BinVertexBuffers/BindIndexBuffers
If you want to update vsg::Data dynamically you now have to add a data->getLayout().dynamic flag to true, prior to call viewer::compile() so that when the rendering backend (RecordAndSubmitTask) is configured it can record all the vsg::BufferInfo that have dynamic vsg::Data.
Then during the Viewer::recordAndSubmit() call all the dynamic BufferInfo vsg::Data that has it's modifiedCount updated since last copy is copied to a staging buffer and then a set of vkCmdCopyBuffer are then recorded to dedicated vkCommandBuffer and submitted prior to the main rendering submission with the later waiting on a semaphore singled by the transfer submission. The staging buffer and associated resources are mulit-buffered to address the issue of write and read data collisions. All this complexity is handled for you so no application level work required.
All you need to is update your vsg::Data and call data->dirty() to update the modificationCount. Have a look at the changes to vsgExamples linked above to see it in action. For example vsgcomputevertex in scene graph set up does:
// setting we pass to the compute shader each frame to provide animation of vertex data.
auto computeScale = vsg::vec4Array::create(1);
computeScale->getLayout().dynamic = true;
computeScale->set(0, vsg::vec4(1.0, 1.0, 1.0, 0.0));
And then in main frame loop:
double frameTime = std::chrono::duration<float, std::chrono::seconds::period>(viewer->getFrameStamp()->time - viewer->start_point()).count();
computeScale->at(0).z = sin(frameTime);
computeScale->dirty();
The computeScale->getLayout().dynamic = true; and computeScale->dirty(); are the only additional code you need to make sure that data will get synced.
This new mechanism means you want nee to explicitly copy the data yourself - so no need for a vsg::CopyAndReleaseBuffer node and all the extras that surround it's use, which is where all the code deletions were achieved with the vsgExamples changes.
I need to stop now as my wrist is protesting.
I still more refinement work to do, and some variable/class renaming such vsg::Data::Layout and it's new bool dynamic. The overall approach I'm am happy with so worth wider community testing before I consider merging with master.
So please dive in with testing.
Cheers,
Robert.