I've just checked in a further improvement to how we can configure our pipelines to work with the new view dependent state - the addition of a vsg::ViewDescriptorSetLayout class that is a proxy to the View's ViewDependentState.descriptorSetLayout. The active Vulkan descriptorSetLayout is determined during the compile traversal. In combination with vsg::BindViewDescriptorSet you can now set up your pipelines at load/scene graph generation time without worrying about the specifics of light data that will be provided at runtime.
This means the setup is substantially cleaner and we're another step away from loaders and applications being hardwired for a fixed light source etc. Now the scene can provide as many lights as it requires, and the application can add any extra it wishes - such as headlights.
The approach I've taken should handle support for clip plans, eye linear text and shadows, and user extensions via configuration or subclassing of ViewDependentState. This will require further follow up work, but I feel the design side of view dependent state is now figured out so have a clearer path forward when tackling these features.
Changes to VSG and vsgExamples are Lights branches:
Items left to TODO
While the changes to the core VSG are inching closing to being ready to merge with master. I do have a few issues to resolve. The first is multi-buffering of descriptors in ViewDependentState to handle the fact that you can have multiple frames being record, submitted and running on the GPU, so it's if you update the light data during the record it could change the light data that's being used on the GPU, but what is being rendered is actually a frame from 1 or 2 frames ago, not the current one being recorded.
As the light data needs to be transformed from object space into eye space each frame inconsistencies each frame has to have it's own set of data, if you get the wrong frames light data then visually you see the influence of light sources moving around as you zoom in/out or pan. It's a bit like having a drunk viewer. One solution - and the one that the vsglights example deploys is to add waitForFences into the main loop:
// rendering main loop
while (viewer->advanceToNextFrame() && (numFrames < 0 || (numFrames--) > 0))
{
// pass any events into EventHandlers assigned to the Viewer
viewer->handleEvents();
viewer->update();
if (frameToWait > 0 && waitTimeout > 0)
{
viewer->waitForFences(frameToWait, waitTimeout);
}
viewer->recordAndSubmit();
viewer->present();
numFramesCompleted += 1.0;
}
While this works it does mean that we've got a wait which reduces the amount of parallelism that is possible and degrades performance, which is a big no no for performance oriented applications.
All is not lost, what I've done successfully before is to multi-buffer the DescriptorSet so each frame gets its own set of light data to work with. This afternoon I'll tackle this.
--
Another that I need to address is that once your shaders are set up to use the light data and there are no lights in the scene you get no lighting, so everything appears black, which technically correct doesn't look that great as a default behaviour :-)
I mentioned yesterday in this thread that we can make it more convenient to add a headlight to a view by providing a creatHeadlight() convenience function, however, this does address the default behaviour issue. While I can add to all the examples a little bit of vsg::View configuration code to add a headlight existing VSG applications will have black scenes with they are using the latest vsg::Builder or vsgXchange::Assimp that both now utilize the light data.
I could add a default headlight to the vsg::View in it's default constructor, but then if applications don't want this then they'd need to go in and remove it, which is really against how the VSG is generally intended to be used i.e. you aren't forced into a particular solution that you have to do extra work to undo if you don't want it.
What the VSG does already have is some convenience functions for setting CommandGraph and RenderGraph's with a View, creating the view automatically to set things up in a reasonable default way. The functions are:
/// convenience function that sets up RenderGraph inside primary CommandGraph to render the specified scene graph from the specified Camera view
extern VSG_DECLSPEC ref_ptr<CommandGraph> createCommandGraphForView(ref_ptr<Window> window, ref_ptr<Camera> camera, ref_ptr<Node> scenegraph, VkSubpassContents contents = VK_SUBPASS_CONTENTS_INLINE);
/// convenience function that sets up secondaryCommandGraph to render the specified scene graph from the specified Camera view
extern VSG_DECLSPEC ref_ptr<CommandGraph> createSecondaryCommandGraphForView(ref_ptr<Window> window, ref_ptr<Camera> camera, ref_ptr<Node> scenegraph, uint32_t subpass);
And
/// Convenience function that sets up RenderGraph and associated View to render the specified scene graph from the specified camera view.
/// Assigns the WindowResizeHandler to provide basic window resize handling.
extern VSG_DECLSPEC ref_ptr<RenderGraph> createRenderGraphForView(ref_ptr<Window> window, ref_ptr<Camera> camera, ref_ptr<Node> scenegraph, VkSubpassContents contents = VK_SUBPASS_CONTENTS_INLINE);
I'm thinking I can change these so they set up the vsg::View they create for you with a headlight subgraph. If you don't want this default behaviour you just simply create the View, CommandGraph and RenderGraph yourself. All the source code for these convenience functions is open source under the MIT licence so users are free to learn from this and adapt to their own requirements, or just set everything up explicitly as many of the vsgExamples already do.
Happy to take suggestions, if there are cleaner ways of managing this then I've very open to adopting them :-)