Improvements to built in ShaderSet SPIR-V variants for building without GLSLang and refinements to shader composition

77 views
Skip to first unread message

Robert Osfield

unread,
Oct 24, 2022, 1:20:29 PM10/24/22
to vsg-users : VulkanSceneGraph Developer Discussion Group
Hi All,

Last week I began working on improving how well the VSG works when not built against GLSLang, and as part of this work made a range of related improvements to shader support.  Today I wrapped up the work and required checked in the changes to respective masters, the changes are:

    osg2vsg
    vsgXchange

The first change I made as part of this work was to improve the detection of trying to compile GLSL shaders to SPIR-V shaders, required by Vulkan, when the VSG hasn't been built against GLSLang so can't do this compile.  Previously the VSG application would just crash in this instance - be that running examples like vsgbuilder or vsgtext or loading a file using vsgXxchange::Assimp.  If there are instances where compilation is required but not supported the VSG will now emit a helpful console message and throw and exception.  The message is:

    VulkanSceneGraph not compiled with GLSLang, unable to compile shaders.

On desktop platforms GLSLang can typically be installed easily, or comes with VulkanSDK , but on embedded platforms users had to hunt down. build and install GLSLang and recompile the VSG to address this problem. but as previously the VSG just crash in the Vulkan driver knowing that lack of GLSLang was the issue required a bit of magic knowledge (i.e. reporting the problem and me spotting it as a possible solution :-)

As part of this better error reporting I add a vsg::Logger::fatal(..)/vsg::fatal(..) variant of the logging functionality.

The next improvement was significantly more involved, and had several twists and turns along the way due to Visual Studio internal compiler crashes - the improvements were focused on provided precompiled SPIR-V shaders as part of the built in ShaderSet so that rather than required compilation on the fly from GLSL -> SPIR-V the required variants can be just pulled from the built in variants when available.

The neat thing about being able to provide the SPIR-V shaders as part of the VSG build is that even when you don't have the VSG built against GLSLang features such as vsg::Text/vsg::Builder and vsgXchange::Assimp will now work out of the box for the most commonly used shader composition variants.  This means that when you run vsgtext. vsgbuiilder etc. rather than a crash, or warning it'll just work as nature intended :-)

When I originally wrote vsg::ShaderSet, as a reflection scheme for shaders/shader composition, I implemented the ShaderSet::variant map to enable reuse of different configurations of shader composition of the main shader stages associated with the ShaderSet.  This feature has worked well and included serialization support all intended to be stepping stone for future embedding of pre-compiled variants into a library/application, but to make this feature really shine required some further work - the development of a utility program that could select the required variants and then compile them and finally write the results to .cpp so they could be compiled into that application. 

I have now written this utility - vsgshaderset:


The most basic usage of this utility is to simple print out to console the features of a ShaderSet so you can see what shaders, uniforms and arrays that is supports and #define used to control the shader composition in the shaders, and the variants.  For instance try:

$vsgshaderset --text
info: Local text_ShaderSet(ref_ptr<const vsg::Options>(vsg::Options 0x7f0cdf747058))
shaderSet = ref_ptr<vsg::ShaderSet>(vsg::ShaderSet 0x7f0cdf747878)
stages.size() = 2
 ShaderStage {
   flags = 0
   stage = 1
   entryPointName = main
   module = ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x7f0cdf747aa0)
 }
 ShaderStage {
   flags = 0
   stage = 16
   entryPointName = main
   module = ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x7f0cdf747bc0)
 }

attributeBindings.size() = 6
 AttributeBinding {
   name = inPosition
   define =  
   location = 0
   format = 106
   data = ref_ptr<vsg::Data>(vsg::vec3Array 0x7f0cde746040)
 }
.... lots of info removed to fit in this post ....
Supported defines.size() = 3
  BILLBOARD
  CPU_LAYOUT
  GPU_LAYOUT

compiling shaderSet->variants.size() = 0
{
}
stages.size() = 0

There are also --flat, --phong, ---pbr ShaderSet built into the vsgshaderset that you can use by invoking these command line options, then also set the associated vsg::Options::shaderSet["text] etc. option.  These built-in ShaderSet were original found in the core VSG, but now have been moved into local .cpp files:

    text.cpp
    flat.cpp
    phong.cpp
    pbr.cpp

If you want to check what the VSG itself provides as built ins you you add the --vsg command line, so you can do:

    vsgshaderset --vsg --text

Which the tail of which now looks like (compare to the console output above w.r.t variants):

... much console info removed for berefity...
Supported defines.size() = 3
  BILLBOARD
  CPU_LAYOUT
  GPU_LAYOUT

compiling shaderSet->variants.size() = 4
{
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x7f93c8694c08) : CPU_LAYOUT  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x7f93c8694e18) : GPU_LAYOUT  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x7f93c8694f50) : BILLBOARD CPU_LAYOUT  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x7f93c8695088) : BILLBOARD GPU_LAYOUT  
}
stages.size() = 5
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x7f93c8694c68) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x7f93c8694ce0) 490
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x7f93c8694d40) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x7f93c8694db8) 1807
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x7f93c8694e78) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x7f93c8694ef0) 1373
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x7f93c8694fb0) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x7f93c8695028) 893
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x7f93c86950e8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x7f93c8695160) 1715

Here we can see the built-in version of the Text ShaderSet created by a call to vsg::createTextShaderSet(..) not only provides all the info about shaders, uniforms, arrays, defines but also adds 4 rebuilt shader composition variants "CPU_LAYOUT", "GPU_LAYOUT", "BILLBOARD CPU_LAYOUT" and "BILLBOARD GPU_LAYOUT". 

These defines are used by the text related shaders that can be found in vsgExamples/data/shaders/

    text.vert
    text.frag

Note how the #pragma import_defines (GPU_LAYOUT, CPU_LAYOUT, BILLBOARD) line in the text.vert map to the #ifdef usage in the shader, and also map the ShaderSet setup, and the console output, and the ShaderSet::variants that provide the SPIR-V compiled versions of the shaders with the appropriate branches in the GLSL shaders compiled in/out.

This post is already quite long with lots of stuff to digest so I'll create another follow up post to explain in more detail how you add your own variants to be compiled, and how to generate .cpp files that embed the compiled ShaderSet and variants in a form that you can use in your applications (and how it's used in the VSG itself.)

Cheers,
Rpbert.

Robert Osfield

unread,
Oct 24, 2022, 2:59:18 PM10/24/22
to vsg-...@googlegroups.com
To help with management of which defines to enable/disabled I changed the vsg::ShaderCompileSettings::define member variable from a std::vector<std::string> to a std::set<std::string>.  This change means when setting up the ShaderCompileSettings you'll need to use scs->defines.insert("MY_DEFINE") rather than scs->defines.push_back("MY_DEFINE"). 

Changes I made to vsgXchange, osg2vsg and vsgExamples to account for this change illustrate the type of changes you might need to apply if you are using ShaderSet in your own code:


Robert Osfield

unread,
Oct 25, 2022, 5:26:35 AM10/25/22
to vsg-users : VulkanSceneGraph Developer Discussion Group
When doing the work on text ShaderSet I spotted that the Options was being made available to the Text/TextGroup::setup(..) function which was preventing custom text ShaderSet set being passed in.  It just so happens I was asked this morning about how to disable depth test in Text so this was a good opportunity to illustrate this an test out setting up custom ShaderSet for text, so I modified the vsgtext and vsgtextgroup examples by adding the following:

   if (disableDepthTest)
    {
        // assign a custom StateSet to options->shaderSets so that subsequent TextGroup::setup(0, options) call will pass in our custom ShaderSet.
        auto shaderSet = options->shaderSets["text"] = vsg::createTextShaderSet(options);

        // create a DepthStencilState, disable depth test and add this to the ShaderSet::defaultGraphicsPipelineStates container so it's used when setting up the TextGroup subgraph
        auto depthStencilState = vsg::DepthStencilState::create();
        depthStencilState->depthTestEnable = VK_FALSE;
        shaderSet->defaultGraphicsPipelineStates.push_back(depthStencilState);
    }

To make sure this custom ShaderSet gets up you just need to make sure the Options object is passed into the text setup i.e

   text->setup(0, options);

The 0 is the minimum number of characters to allocate, unless you have dynamic text that changes length it's fine for this to be 0 as the length allocated is the maximum of this value and the number of text characters at the time of the setup call.

This trick will work with vsg::Builder and when loading data using vsgXchange::Assimp, so you can adjust depth test, stencilling, blending, rasterization etc.

Robert Osfield

unread,
Oct 25, 2022, 6:49:32 AM10/25/22
to vsg-users : VulkanSceneGraph Developer Discussion Group
A key aim of the work on finish this ShaderSet work was the pre compilation of all variants of GLSL shaders to SPIR-V and to build these as part of the library.  Figuring all the variants required is easy for something like the Text ShaderSet as there are only 3 defines in play, but for the PBR ShaderSet has 11 defines, with potentially potential permutations if off the scale (nearly 40 million!) so you can't pre-compile all of them :-)

The solution I have implemented is for the vsgshaderset utility program to load models on the command line and let the loading process query the appropriate ShaderSet for the variants it needs to load those models, and if you load a broad enough range of models you can enumerate all the main combinations. 

To set up variants for the PBR ShaderSet I used the glTF-Sample-Models sample set, loading all the .glb and .gltf into vsgshaderset by using the Unix find and xargs utilities to pass in the models to vsgshaderset, this is the command line:

     find . -name "*.glb" -o -name "*.gltf" | xargs vsgshaderset --pbr

This outputs the details of the final ShaderSet and variants to the console, the supported defines are listed as:

Supported defines.size() = 11
  VSG_DIFFUSE_MAP
  VSG_DISPLACEMENT_MAP
  VSG_EMISSIVE_MAP
  VSG_GREYSACLE_DIFFUSE_MAP
  VSG_INSTANCE_POSITIONS
  VSG_LIGHTMAP_MAP
  VSG_METALLROUGHNESS_MAP
  VSG_NORMAL_MAP
  VSG_SPECULAR_MAP
  VSG_TWO_SIDED_LIGHTING
  VSG_WORKFLOW_SPECGLOSS


The variants required to handle all these models are:

compiling shaderSet->variants.size() = 28
{
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aee417f48) : VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aee4178a0) : VSG_DIFFUSE_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b6a970) : VSG_EMISSIVE_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b70200) : VSG_LIGHTMAP_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b69950) : VSG_METALLROUGHNESS_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b6a2c8) : VSG_NORMAL_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b393766b0) : VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b393a71c0) : VSG_DIFFUSE_MAP VSG_EMISSIVE_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b70888) : VSG_DIFFUSE_MAP VSG_LIGHTMAP_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b71a90) : VSG_DIFFUSE_MAP VSG_METALLROUGHNESS_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b64698) : VSG_DIFFUSE_MAP VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aee41bae8) : VSG_METALLROUGHNESS_MAP VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b6fb78) : VSG_DIFFUSE_MAP VSG_LIGHTMAP_MAP VSG_METALLROUGHNESS_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b16b6f4f0) : VSG_DIFFUSE_MAP VSG_LIGHTMAP_MAP VSG_NORMAL_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564af7360a90) : VSG_DIFFUSE_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b393bc9b0) : VSG_DIFFUSE_MAP VSG_METALLROUGHNESS_MAP VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b58fa9f48) : VSG_DIFFUSE_MAP VSG_EMISSIVE_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aecc16a20) : VSG_DIFFUSE_MAP VSG_LIGHTMAP_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b7e815a10) : VSG_DIFFUSE_MAP VSG_LIGHTMAP_MAP VSG_NORMAL_MAP VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b393c5318) : VSG_DIFFUSE_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_SPECULAR_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aea8464b0) : VSG_DIFFUSE_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564ae8aac548) : VSG_DIFFUSE_MAP VSG_EMISSIVE_MAP VSG_LIGHTMAP_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b39366078) : VSG_DIFFUSE_MAP VSG_EMISSIVE_MAP VSG_LIGHTMAP_MAP VSG_NORMAL_MAP VSG_SPECULAR_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b7e811470) : VSG_DIFFUSE_MAP VSG_EMISSIVE_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_SPECULAR_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aee416a50) : VSG_DIFFUSE_MAP VSG_LIGHTMAP_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_SPECULAR_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aff362238) : VSG_DIFFUSE_MAP VSG_LIGHTMAP_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLOSS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564aea8455f0) : VSG_DIFFUSE_MAP VSG_EMISSIVE_MAP VSG_LIGHTMAP_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_SPECULAR_MAP VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_SPECGLO
SS  
   ref_ptr<vsg::ShaderCompileSettings>(vsg::ShaderCompileSettings 0x564b7e815388) : VSG_DIFFUSE_MAP VSG_EMISSIVE_MAP VSG_LIGHTMAP_MAP VSG_METALLROUGHNESS_MAP VSG_NORMAL_MAP VSG_TWO_SIDED_LIGHTING VSG_VIEW_LIGHT_DATA VSG_WORKFLOW_S
PECGLOSS  
}

And when these variants are compiled to SPIR-V we end up with 29 unique ShaderStage/ShaderModule, the final number of each of the following lines is the SPIR-V code size in bytes:

stages.size() = 29
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aee4182d0) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aee418348) 583
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aee4183a8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aee418420) 6024
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aee417b98) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aee417c10) 6212
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b6b7a8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b6b820) 6198
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b704f8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b70570) 6077
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b69f18) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b69f90) 6056
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b6a5c0) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b6a638) 6403
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b39376de0) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b39376e58) 6068
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b393a74b8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b393a7530) 6271
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b70b80) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b70bf8) 6249
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b71d88) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b71e00) 6228
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b64990) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b64a08) 6256
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aee41bde0) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aee41be58) 6100
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b6fe70) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b6fee8) 6265
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b16b6f7e8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b16b6f860) 6612
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564af7360d88) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564af7360e00) 6591
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b393bd3b0) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b393bd428) 6272
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b58faa240) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b58faa2b8) 6650
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aecc16d18) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aecc16d90) 6628
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b7e815d08) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b7e815d80) 6656
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b393c3da8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b393c3968) 6668
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aea8467a8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aea846820) 6635
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564ae8aac880) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564ae8aac8f8) 6687
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b39366370) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b393663e8) 6748
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b7e811940) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b7e8119b8) 6727
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aee416d48) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aee416dc0) 6705
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aff362530) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aff3625a8) 6672
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564aea8458e8) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564aea845960) 6764
  ref_ptr<vsg::ShaderStage>(vsg::ShaderStage 0x564b7e815680) ref_ptr<vsg::ShaderModule>(vsg::ShaderModule 0x564b7e8156f8) 6731

To write this pre-compiled ShaderSet to disk as an VSG ascii file we simple add a -o pbr_Shader.vsgt to the command line:

    find . -name "*.glb" -o -name "*.gltf" | xargs vsgshaderset --pbr -o pbr_ShaderSet.vsgt

The resultinig pbr_ShaderSet.vsgt can be loaded and assigned to Options::shaderSet["pbr"] just like I did in the text customization post above, for instance if you wanted to modified things like blending, depth testing etc. you can add these to the ShaderSet, write it to .vsgt and then use it customize how models are loaded.  You can also use this approach to add extra pre-compiled variants.

The next step to build in the pre-compiled SPIR-V variants into the build of the VSG itself I used the vsgXchange::cpp writer that converts VSG objects into lambda function that code can call to generate the VSG objects, to generate the .cpp we simple change to -o pbr_ShaderSet.cpp:

    find . -name "*.glb" -o -name "*.gltf" | xargs vsgshaderset --pbr -o pbr_ShaderSet.cpp

Originally the generated .cpp would encode the data as c string and it would be nicely readable, and this worked fine under Linux with sensible compilers but the automated Windows build that the VSG github does caused problems with the string being too long, then when I workaround this by using a char[] as suggested online as a workaround I then kept causing internal compiler errors.  Turns out that VisualStudio is a heap of junk when it comes to handling large literal strings.

It took me a few tries to figure out a workaround to the buggy VisualStudio problems - to a cut a long frustrating story short I've change the vsgXchange::cpp writer so that for large objects it encodes the serialized data in the form of a uint8_t array like:

    uint8_t data[] = { 35, 118, 115, 103... };

then pass this as raw data to VSG ReaderWriter:

    vsg::VSG io;
  return io.read_cast<vsg::ShaderSet>(data, sizeof(data));

Originally VSG::read(uint8_t data, size_t size, ref_ptr<Options options = {}) method was implemented by making a std::stringstream from the data so would have created dynamic memory for the std::string doubling the memory used and slowing things down, while "functional" it wouldn't be efficient so I implemented a simple vsg::mem_stream class that takes the uint8_t[] data directly without any copying and allows it to be read from just like any normal stream.  As this feature will be useful for other tasks I've added it to the include/vsg/io as it'w own mem_stream.h and associated .cpp:
  
  
Since this large ShaderSet had to be enoded as a uint8_t array rather than human readable c string I added support to the vsgXchange::cpp write and vsgshaderset for hinting that binary serialization should be used.   To select this we add a --binary to the command line:

    find . -name "*.glb" -o -name "*.gltf" | xargs vsgshaderset --pbr -o pbr_ShaderSet.cpp --binary

The original pbr_ShaderSet.cpp generated for the PBR variants was 4,112,857 bytes (~4Mb), but with with binary serialization this shrinks to 2589583 bytes (~2.6Mb) which is still big for a single source file but it's worthwhile reduction in source and final .lib size.

Another bonus about binary serilization is that is faster to parse so the vsg::createPbrShaderSet(..) call is faster as well. 

The way that the final .cpp is used can be see in ShaderSet.cpp where most of the the vsg:create*ShaderSet() are provided - the .cpp's are included directly:

    #include "shaders/flat_ShaderSet.cpp"
    #include "shaders/pbr_ShaderSet.cpp"
    #include "shaders/phong_ShaderSet.cpp"

Then in the creae*ShaderSet() methods the lambda functions that the .cpp provide is invoked:

          ref_ptr<ShaderSet> vsg::createPhysicsBasedRenderingShaderSet(ref_ptr<const Options> options)
    {
        if (options)
        {
            // check if a ShaderSet has already been assigned to the options object, if so return it
            if (auto itr = options->shaderSets.find("pbr"); itr != options->shaderSets.end()) return itr->second;
        }

        return pbr_ShaderSet();
    }


--

All of this work means when you create vsg::Text, use vsg::Builder or load gltf etc. files using vsgXchange::Assimp the respective ShaderSet will be created efficient with all the main SPIR-V variants compiled directly into the vsg library so at runtime scene graph can be created without needing to loading any shader files and without invoke GLSLang to compile the GLSL variants to SPIR-V, which means models are loaded faster.

This works on all platforms even when you haven't compiled the VSG against GLSLang, so out of the box users will be able to do lots of rendering without crashes that were prevously occurring and with everything working as efficiently as one can. This further stetches the performance gap between OpenGL/OpenSceneGraph applications and VSG applications as the later now pop on screen even faster.

All is need now is folks to check out VSG, osg2vsg, vsgXchange and vsgExample masters and test the build and runtime of this code.  If you have success or failure let me know so I know how well the code is convering to stable release status.

Cheers,
Robert.

Tim Moore

unread,
Oct 25, 2022, 11:12:35 AM10/25/22
to vsg-...@googlegroups.com
On Tue, Oct 25, 2022 at 12:49 PM Robert Osfield <robert....@gmail.com> wrote:
A key aim of the work on finish this ShaderSet work was the pre compilation of all variants of GLSL shaders to SPIR-V and to build these as part of the library.  Figuring all the variants required is easy for something like the Text ShaderSet as there are only 3 defines in play, but for the PBR ShaderSet has 11 defines, with potentially potential permutations if off the scale (nearly 40 million!) so you can't pre-compile all of them :-)

This is more of an aside, and don't let me delay your work :), but...
Aren't there just 2048 permutations to pre-compile (2^11), if you wanted to go that route?  I agree that the direction you've chosen is more practical than generating all the permutations, and moreso if you want to include the SPIRV binaries in C++ source, but 2k files of .spv binaries (for example) is something I would consider as reasonable to ship in a data directory, for example.

Tim

Reply all
Reply to author
Forward
0 new messages