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.