Расширение VK_EXT_shader_object

Изначально когда Vulkan только придумывался, туда было добавлено многое из того, что не прошло проверку времени и оказалось слишком сложным, неудобным и не дающим ощутимого эффекта.

К числу таких возможностей были неизменяемые объекты-конвейеры (Vk*Pipeline). Изначально идея была в том, что их использование поможет драйверу принимать более оптимальные решения заранее зная полную конфигурацию. За счет этого планировалось перенести всю тяжесть поддержки множества различных комбинаций с драйвера на само приложение.

Также за счет этого планировалось заметно сократить время на валидацию. При этом предполагалось, что разработчики заранее могут определить все возможные комбинации состояния и занести их в объекты-конвейеры. Также считалось что цена, которую платит драйвер за эту абстракцию, окажется сильно меньше получаемого выигрыша.

Однако на практике оказалось, что пользы от них не так много, а вот сложностей более чем достаточно. В результате в приложениях стали плодится многочисленные объекты-конвейеры - для каждой используемой комбинации параметров/шейдеров пришлось создавать свой объект-конвейер.

И в результате оказалось, что код, для работы с кучей состояний, просто перешел из драйвера в многочисленные приложения. Цена перешла на постоянный bind-конвейеров, создавая значительную нагрузку. Стали возникать разнообразные хэши, где по требуемому состоянию искали соответствующий объект-конвейер. Более того, для некоторых IHV оказалось, что данная модель просто ограничивает возможности разработчика. Т.е. оказалось, что эта абстракция стала создавать многочисленные проблемы, не давая при этом четкого выигрыша. Ее использование затрудняло использование Vulkan, особенно для новичков.

В результате было принято радикальное решение вообще отказаться от концепции объекта-конвейера Vk*Pipeline, представляющей по сути проблему для многих с самого появления Vulkan, перейдя к более удобному и дружественному подходу.

В результате появилось расширение VK_EXT_shader_object, которое предложило простую альтернативу. Больше никаких объектов-конвейеров - Vk*Pipeline оказались вообще не нужны. Вместо них вводится новый тип объекта Vulkan - VkShaderEXT, представляющий отдельный шейдерный шаг конвейера - вершинный, фрагментный, вычислительный и т.д.

Эти объекты могут создаваться независимо друг от друга или вместе при помощи вызова vkCreateShadersEXT:

VkResult vkCreateShadersEXT (
   VkDevice device,
   uint32_t createInfoCount,
   VkShaderCreateInfoEXT pCreateInfos,
   VkAllocationCallbacks pAllocator,
   VkShaderEXT* pShaders );

Вызов этой функции собирает код для одной или нескольких стадий конвейера (графического или вычислительного) в объект типа VkShaderEXT. Если createInfoCount больше единицы, то несколько шейдеров могут быть слинкованы вместе (передавая флаг VK_SHADER_CREATE_LINK_STAGE_BIT_EXT в каждой из структур VkShaderCreateInfoEXT).

Ниже приводится описание структуры VkShaderCreateInfoEXT, через которую передается информация о шейдерной стадии конвейера. Обратите внимание, что через поле nextStage мы можем передать следующую стадию (например, что за вершинный шейдером следует именно фрагментный, а не геометрический) - это может позволить драйверу произвести оптимизацию.

typedef struct VkShaderCreateInfoEXT {
   VkStructureType sType;
   const void pNext;
   VkShaderCreateFlagsEXT flags;
   VkShaderStageFlagBits stage;
   VkShaderStageFlags nextStage;
   VkShaderCodeTypeEXT codeType;
   size_t codeSize;
   const void pCode;
   const char pName;
   uint32_t setLayoutCount;
   const VkDescriptorSetLayout pSetLayouts;
   uint32_t pushConstantRangeCount;
   const VkPushConstantRange pPushConstantRanges;
   const VkSpecializationInfo pSpecializationInfo;
} VkShaderCreateInfoEXT;

Для подключения этого объекта (вместо конвейера) к командному буферу служит следующая команда.

VKAPI_ATTR void VKAPI_CALL vkCmdBindShadersEXT (
   VkCommandBuffer commandBuffer,
   uint32_t stageCount,
   const VkShaderStageFlagBits pStages,
   const VkShaderEXT pShaders );

Для уничтожения объектов типа VkShaderEXT служит следующая функция

void vkDestroyShaderEXT (
   VkDevice device,
   VkShaderEXT shader,
   VkAllocationCallbacks* pAllocator );

Поскольку теперь у нас нет объекта-конвейера, то то, что раньше было его состоянием, нужно задавать динамически. Необходимые для этого команды были введены в Vulkan при помощи ранее введенных расширений VK_EXT_extended_dynamic_state, VK_EXT_extended_dynamic_state2, VK_EXT_extended_dynamic_state3, или VK_EXT_vertex_input_dynamic_state. Ниже приводится прототипы основных таких функций.

void vkCmdBindVertexBuffers2 ( VkCommandBuffer commandBuffer,
       uint32_t firstBinding, uint32_t bindingCount,
       const VkBuffer pBuffers,
       const VkDeviceSize pOffsets,
       const VkDeviceSize pSizes,
       const VkDeviceSize pStrides );

void vkCmdSetAlphaToCoverageEnableEXT ( VkCommandBuffer commandBuffer, VkBool32 alphaToCoverageEnable );
void vkCmdSetAlphaToOneEnableEXT      ( VkCommandBuffer commandBuffer, VkBool32 alphaToOneEnable );

void vkCmdSetColorBlendEnableEXT      ( VkCommandBuffer commandBuffer,
       uint32_t firstAttachment, uint32_t attachmentCount, const VkBool32* pColorBlendEnables );

void vkCmdSetColorBlendEquationEXT ( VkCommandBuffer commandBuffer,
       uint32_t firstAttachment, uint32_t attachmentCount, const VkColorBlendEquationEXT* pColorBlendEquations );

void vkCmdSetColorWriteMaskEXT ( VkCommandBuffer commandBuffer,
       uint32_t firstAttachment, uint32_t attachmentCount, const VkColorComponentFlags* pColorWriteMasks );

void vkCmdSetCullMode                    ( VkCommandBuffer commandBuffer, VkCullModeFlags cullMode );
void vkCmdSetDepthBiasEnable             ( VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable );
void vkCmdSetDepthBoundsTestEnable       ( VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable );
void vkCmdSetDepthClampEnableEXT         ( VkCommandBuffer commandBuffer, VkBool32 depthClampEnable );
void vkCmdSetDepthCompareOp              ( VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp );
void vkCmdSetDepthTestEnable             ( VkCommandBuffer commandBuffer, VkBool32 depthTestEnable );
void vkCmdSetDepthWriteEnable            ( VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable );
void vkCmdSetFrontFace                   ( VkCommandBuffer commandBuffer, VkFrontFace frontFace );
void vkCmdSetLogicOpEXT                  ( VkCommandBuffer commandBuffer, VkLogicOp logicOp );
void vkCmdSetLogicOpEnableEXT            ( VkCommandBuffer commandBuffer, VkBool32 logicOpEnable );
void vkCmdSetPolygonModeEXT              ( VkCommandBuffer commandBuffer, VkPolygonMode polygonMode );
void vkCmdSetPrimitiveRestartEnable      ( VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable );
void vkCmdSetPrimitiveTopology           ( VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology );
void vkCmdSetRasterizerDiscardEnable     ( VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable );
void vkCmdSetScissorWithCount            ( VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors );
void vkCmdSetStencilTestEnable           ( VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable );
void vkCmdSetTessellationDomainOriginEXT ( kCommandBuffer commandBuffer, VkTessellationDomainOrigin domainOrigin );

void vkCmdSetVertexInputEXT ( VkCommandBuffer commandBuffer,
    uint32_t vertexBindingDescriptionCount,
    const VkVertexInputBindingDescription2EXT pVertexBindingDescriptions,
    uint32_t vertexAttributeDescriptionCount, const VkVertexInputAttributeDescription2EXT pVertexAttributeDescriptions );

Обратите внимание, что в Vulkan нет состояния по умолчанию, поэтому все необходимое состояния графического конвейера необходимо явно задать через приведенные выше команды vkCmdSet*. Ниже приводится пример типового задания свойств.

// No more pipelines required, everything is bound at command buffer level
// This also means that we need to explicitly set a lot of the state to be spec compliant

vkCmdSetViewportWithCountEXT       ( cmdBuffer, 1, &viewport );
vkCmdSetScissorWithCountEXT        ( cmdBuffer, 1, &scissor );
vkCmdSetCullModeEXT                ( cmdBuffer, VK_CULL_MODE_BACK_BIT );
vkCmdSetFrontFaceEXT               ( cmdBuffer, VK_FRONT_FACE_COUNTER_CLOCKWISE);
vkCmdSetDepthTestEnableEXT         ( cmdBuffer, VK_TRUE );
vkCmdSetDepthWriteEnableEXT        ( cmdBuffer, VK_TRUE );
vkCmdSetDepthCompareOpEXT          ( cmdBuffer, VK_COMPARE_OP_LESS_OR_EQUAL);
vkCmdSetPrimitiveTopologyEXT       ( cmdBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST );
vkCmdSetRasterizerDiscardEnableEXT ( cmdBuffer, VK_FALSE );
vkCmdSetPolygonModeEXT             ( cmdBuffer, VK_POLYGON_MODE_FILL );
vkCmdSetRasterizationSamplesEXT    ( cmdBuffer, VK_SAMPLE_COUNT_1_BIT );
vkCmdSetAlphaToCoverageEnableEXT   ( cmdBuffer, VK_FALSE );
vkCmdSetDepthBiasEnableEXT         ( cmdBuffer, VK_FALSE );
vkCmdSetStencilTestEnableEXT       ( cmdBuffer, VK_FALSE );
vkCmdSetPrimitiveRestartEnableEXT  ( cmdBuffer, VK_FALSE );
const uint32_t sampleMask = 0xFF;
vkCmdSetSampleMaskEXT              ( cmdBuffer, VK_SAMPLE_COUNT_1_BIT, &sampleMask );
const VkBool32 colorBlendEnables = false;
const VkColorComponentFlags colorBlendComponentFlags = 0xf;
const VkColorBlendEquationEXT colorBlendEquation {};
vkCmdSetColorBlendEnableEXT        ( cmdBuffer, 0, 1, &colorBlendEnables );
vkCmdSetColorWriteMaskEXT          ( cmdBuffer, 0, 1, &colorBlendComponentFlags );

Точно также и структура вершины, которая раньше задавалась в VkGraphicsPipeline должна задаваться отдельно. Ниже приводится подобный пример.

VkVertexInputBindingDescription2EXT vertexInputBinding {};
vertexInputBinding.sType     = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT;
vertexInputBinding.binding   = 0;
vertexInputBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
vertexInputBinding.stride    = sizeof(Vertex);
vertexInputBinding.divisor   = 1;
std::vector vertexAttributes = {
{ VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) },
{ VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal) },
{ VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, color) }
};
vkCmdSetVertexInputEXT ( cmdBuffer, 1, &vertexInputBinding, 3, vertexAttributes.data () );

По аналогии с тем как это было сделано раньше, мы можем завернуть задание атрибутов вершины с отдельный объект с удобным интерфейсом.

class VertexDescription
{
   VkVertexInputBindingDescription2EXT                vertexInputBinding {};
   std::vector<VkVertexInputAttributeDescription2EXT> vertexAttributes;

public:
   VertexDescription ( uint32_t stride, uint32_t rate = VK_VERTEX_INPUT_RATE_VERTEX, uint32_t divisor = 1 );
   VertexDescription& addAttr ( uint32_t location, VkFormat format, uint32_t offset, uint32_t bindidng = 0 )
   {
      VkVertexInputAttributeDescription2EXT d {};
       d.sType    = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT;
       d.location = location;
       d.binding  = binding;
       d.format   = format;
       d.offset   = offset;

       vertexAttributes.push_back ( d );

       return *this;
   }
};
template 
inline VertexDescription& registerVertexAttrs ( VertexDescription& desc )
{
   return desc;
}

Ниже приводится простой пример использования данного расширения (вместе с динамическим рендерингом), весь исходный код находится в репозитории на github.

#include    <memory>
#include    "VulkanWindow.h"
#include    "Buffer.h"
#include    "DescriptorSet.h"
#include    "Mesh.h"
#include    "Controller.h"
#include    "StatisticsPool.h"
#include    "TimestampPool.h"

struct Ubo 
{
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
    glm::mat3 nm;
};

class   ShaderObjectWindow : public VulkanWindow
{
    std::vector<CommandBuffer>      commandBuffers;
    std::vector<DescriptorSet>  descriptorSets;
    std::vector<Uniform<Ubo>>       uniformBuffers;
    DescSetLayout               descSetLayout;
    VkPipelineLayout            pipelineLayout = VK_NULL_HANDLE;
    VkShaderEXT             vertexShader   {};
    VkShaderEXT             fragmentShader {};


    Texture             texture;
    Sampler             sampler;
    std::unique_ptr<Mesh>             mesh;

    PFN_vkCreateShadersEXT       vkCreateShadersEXT       { VK_NULL_HANDLE };
    PFN_vkDestroyShaderEXT       vkDestroyShaderEXT       { VK_NULL_HANDLE };
    PFN_vkCmdBindShadersEXT      vkCmdBindShadersEXT      { VK_NULL_HANDLE };
    PFN_vkGetShaderBinaryDataEXT vkGetShaderBinaryDataEXT { VK_NULL_HANDLE };

        // VK_EXT_shader_objects requires render passes to be dynamic
    PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR   { VK_NULL_HANDLE };
    PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR       { VK_NULL_HANDLE };

        // With VK_EXT_shader_object pipeline state must be set at 
        // command buffer creation using these functions
    PFN_vkCmdSetAlphaToCoverageEnableEXT   vkCmdSetAlphaToCoverageEnableEXT   { VK_NULL_HANDLE };
    PFN_vkCmdSetColorBlendEnableEXT        vkCmdSetColorBlendEnableEXT        { VK_NULL_HANDLE };
    PFN_vkCmdSetColorWriteMaskEXT          vkCmdSetColorWriteMaskEXT          { VK_NULL_HANDLE };
    PFN_vkCmdSetCullModeEXT                vkCmdSetCullModeEXT                { VK_NULL_HANDLE };
    PFN_vkCmdSetDepthBiasEnableEXT         vkCmdSetDepthBiasEnableEXT         { VK_NULL_HANDLE };
    PFN_vkCmdSetDepthCompareOpEXT          vkCmdSetDepthCompareOpEXT          { VK_NULL_HANDLE };
    PFN_vkCmdSetDepthTestEnableEXT         vkCmdSetDepthTestEnableEXT         { VK_NULL_HANDLE };
    PFN_vkCmdSetDepthWriteEnableEXT        vkCmdSetDepthWriteEnableEXT        { VK_NULL_HANDLE };
    PFN_vkCmdSetFrontFaceEXT               vkCmdSetFrontFaceEXT               { VK_NULL_HANDLE };
    PFN_vkCmdSetPolygonModeEXT             vkCmdSetPolygonModeEXT             { VK_NULL_HANDLE };
    PFN_vkCmdSetPrimitiveRestartEnableEXT  vkCmdSetPrimitiveRestartEnableEXT  { VK_NULL_HANDLE };
    PFN_vkCmdSetPrimitiveTopologyEXT       vkCmdSetPrimitiveTopologyEXT       { VK_NULL_HANDLE };
    PFN_vkCmdSetRasterizationSamplesEXT    vkCmdSetRasterizationSamplesEXT    { VK_NULL_HANDLE };
    PFN_vkCmdSetRasterizerDiscardEnableEXT vkCmdSetRasterizerDiscardEnableEXT { VK_NULL_HANDLE };
    PFN_vkCmdSetSampleMaskEXT              vkCmdSetSampleMaskEXT              { VK_NULL_HANDLE };
    PFN_vkCmdSetScissorWithCountEXT        vkCmdSetScissorWithCountEXT        { VK_NULL_HANDLE };
    PFN_vkCmdSetStencilTestEnableEXT       vkCmdSetStencilTestEnableEXT       { VK_NULL_HANDLE };
    PFN_vkCmdSetViewportWithCountEXT       vkCmdSetViewportWithCountEXT       { VK_NULL_HANDLE };

        // VK_EXT_vertex_input_dynamic_state
    PFN_vkCmdSetVertexInputEXT             vkCmdSetVertexInputEXT { VK_NULL_HANDLE };


public:
    ShaderObjectWindow ( int w, int h, const std::string& t, DevicePolicy * p ) : 
                VulkanWindow ( w, h, t, true, p )
    {
        loadExtensions ();
        setController  ( new RotateController ( this, glm::vec3(2.0f, 2.0f, 2.0f) ) );

        mesh = std::unique_ptr<Mesh> ( loadMesh ( device, "../../Models/teapot.3ds", 0.04f ) );

        sampler.create  ( device );     // use default options
        texture.load    ( device, "../../Textures/Fieldstone.dds", false );

        descSetLayout
            .add    ( 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,         VK_SHADER_STAGE_VERTEX_BIT )
            .add    ( 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT )
            .create ( device.getDevice () );

        vertexShader   = createShader ( Data ( "shaders/shader-tex.vert.spv" ), 
                                            VK_SHADER_STAGE_VERTEX_BIT,   &descSetLayout );
        fragmentShader = createShader ( Data ( "shaders/shader-tex.frag.spv" ), 
                                            VK_SHADER_STAGE_FRAGMENT_BIT, &descSetLayout );

        createPipelines ();
    }

    void    createUniformBuffers ()
    {
        uniformBuffers.resize ( swapChain.getImageCount() );
        
        for ( size_t i = 0; i < swapChain.getImageCount (); i++ )
            uniformBuffers [i].create ( device, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT );
    }

    void    freeUniformBuffers ()
    {
        uniformBuffers.clear ();
    }

    void    createDescriptorSets ()
    {
        descriptorSets.resize ( swapChain.getImageCount () );

        for ( uint32_t i = 0; i < swapChain.getImageCount (); i++ )
        {
            descriptorSets  [i]
                .setLayout        ( device, descAllocator, descSetLayout )
                .addUniformBuffer ( 0, uniformBuffers [i], 0, sizeof ( Ubo ) )
                .addImage         ( 1, texture, sampler )
                .create           ();
        }
    }
    
    virtual void    createPipelines () override 
    {
        VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo = {};
        VkFormat                         swapChainFormats []         = { swapChain.getFormat () };

        pipelineRenderingCreateInfo.sType                   = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
        pipelineRenderingCreateInfo.colorAttachmentCount    = 1;
        pipelineRenderingCreateInfo.pColorAttachmentFormats = swapChainFormats;
        pipelineRenderingCreateInfo.depthAttachmentFormat   = depthTexture.getFormat ();
        pipelineRenderingCreateInfo.stencilAttachmentFormat = depthTexture.getFormat ();

        createUniformBuffers ();
        createDescriptorSets ();
        createCommandBuffers ();
    }

    virtual void    freePipelines () override
    {
        commandBuffers.clear ();
        freeUniformBuffers   ();
        descriptorSets.clear ();
        descAllocator.clean  ();
    }
    
    virtual void    submit ( uint32_t imageIndex ) override 
    {
        updateUniformBuffer ( imageIndex );
        defaultSubmit       ( commandBuffers [imageIndex] );
    }

    void    createCommandBuffers ()
    {
        commandBuffers = device.allocCommandBuffers ( swapChain.getImageCount () );

        for ( size_t i = 0; i < swapChain.getImageCount (); i++ )
        {
            VkRenderingAttachmentInfoKHR colorAttachment        = {};
            VkRenderingAttachmentInfoKHR depthStencilAttachment = {};

            colorAttachment.sType            = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
            colorAttachment.imageView        = swapChain.getImageViews () [i];  
            colorAttachment.imageLayout      = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
            colorAttachment.loadOp           = VK_ATTACHMENT_LOAD_OP_CLEAR;
            colorAttachment.storeOp          = VK_ATTACHMENT_STORE_OP_STORE;
            colorAttachment.clearValue.color = { 0.0f,0.0f,0.0f,0.0f };

            depthStencilAttachment.sType                   = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
            depthStencilAttachment.imageView               = depthTexture.getImageView ();
            depthStencilAttachment.imageLayout             = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
            depthStencilAttachment.loadOp                  = VK_ATTACHMENT_LOAD_OP_CLEAR;
            depthStencilAttachment.storeOp                 = VK_ATTACHMENT_STORE_OP_STORE;
            depthStencilAttachment.clearValue.depthStencil = { 1.0f,  0 };

            VkRenderingInfoKHR renderingInfo = {};

            renderingInfo.sType                = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR;
            renderingInfo.renderArea           = { 0, 0, uint32_t ( width ), uint32_t ( height ) };
            renderingInfo.layerCount           = 1;
            renderingInfo.colorAttachmentCount = 1;
            renderingInfo.pColorAttachments    = &colorAttachment;
            renderingInfo.pDepthAttachment     = &depthStencilAttachment;
            renderingInfo.pStencilAttachment   = &depthStencilAttachment;

                // With dynamic rendering there are no subpass dependencies, 
                // we need to take care of proper layout transitions by using barriers
                // for color and depth images
            auto    barrierImage = imageBarrier  ( swapChain.getImages () [i], 
                VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,      // srcStageMask
                0,                                                  // srcAccessMask
                VK_IMAGE_LAYOUT_UNDEFINED,                          // oldLayout
                VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,      // dstStageMask
                VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,               // dstAccessMask
                VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,           // newLayout
                VK_IMAGE_ASPECT_COLOR_BIT                           // aspectMask
            );

            auto    barrierDepth = imageBarrier  ( depthTexture.getImage (),   
                VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, // srcStageMask, 
                0,                                                                                      // srcAccessMask, 
                VK_IMAGE_LAYOUT_UNDEFINED,                                                              // oldLayout, 
                VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, // dstStageMask, 
                VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,                                           // dstAccessMask, 
                VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,                                       // newLayout, 
                VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT                                 // aspectMask
            );


            commandBuffers [i].begin ();

            auto    cmd = commandBuffers [i].getHandle ();

            pipelineBarrier ( commandBuffers [i], { barrierImage, barrierDepth } );

            vkCmdBeginRenderingKHR ( cmd, &renderingInfo );

            VkViewport viewport = { 0.0f, 0.0f, float(width), float(height), 0.0f, 1.0f };
            VkRect2D   scissor  = { 0, 0, width, height };

                // No more pipelines required, everything is bound at command buffer level
                // This also means that we need to explicitly set a lot of the state to be spec compliant
            vkCmdSetViewportWithCountEXT       ( cmd, 1, &viewport );
            vkCmdSetScissorWithCountEXT        ( cmd, 1, &scissor );
            vkCmdSetCullModeEXT                ( cmd, VK_CULL_MODE_BACK_BIT );
            vkCmdSetFrontFaceEXT               ( cmd, VK_FRONT_FACE_CLOCKWISE );
            vkCmdSetDepthTestEnableEXT         ( cmd, VK_TRUE );
            vkCmdSetDepthWriteEnableEXT        ( cmd, VK_TRUE );
            vkCmdSetDepthCompareOpEXT          ( cmd, VK_COMPARE_OP_LESS );
            vkCmdSetPrimitiveTopologyEXT       ( cmd, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST );
            vkCmdSetRasterizerDiscardEnableEXT ( cmd, VK_FALSE );
            vkCmdSetPolygonModeEXT             ( cmd, VK_POLYGON_MODE_FILL );
            vkCmdSetRasterizationSamplesEXT    ( cmd, VK_SAMPLE_COUNT_1_BIT );
            vkCmdSetAlphaToCoverageEnableEXT   ( cmd, VK_FALSE );
            vkCmdSetDepthBiasEnableEXT         ( cmd, VK_FALSE );
            vkCmdSetStencilTestEnableEXT       ( cmd, VK_FALSE );
            vkCmdSetPrimitiveRestartEnableEXT  ( cmd, VK_FALSE );

            const uint32_t sampleMask = 0xFF;
            
            vkCmdSetSampleMaskEXT              ( cmd, VK_SAMPLE_COUNT_1_BIT, &sampleMask );

            const VkBool32                  colorBlendEnables        = VK_FALSE;
            const VkColorComponentFlags     colorBlendComponentFlags = 0xf;
            const VkColorBlendEquationEXT   colorBlendEquation {};

            vkCmdSetColorBlendEnableEXT       ( cmd, 0, 1, &colorBlendEnables );
            vkCmdSetColorWriteMaskEXT         ( cmd, 0, 1, &colorBlendComponentFlags );

            VkVertexInputBindingDescription2EXT vertexInputBinding {};

            vertexInputBinding.sType     = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT;
            vertexInputBinding.binding   = 0;
            vertexInputBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
            vertexInputBinding.stride    = sizeof(BasicVertex);
            vertexInputBinding.divisor   = 1;

            std::vector<VkVertexInputAttributeDescription2EXT> vertexAttributes = 
            {
                { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 0, 0, 
                        VK_FORMAT_R32G32B32_SFLOAT, offsetof(BasicVertex, pos) },
                { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 1, 0, 
                        VK_FORMAT_R32G32_SFLOAT,    offsetof(BasicVertex, tex) }
            };

            vkCmdSetVertexInputEXT  ( cmd, 1, &vertexInputBinding, uint32_t ( vertexAttributes.size () ), 
                                        vertexAttributes.data () );

                // Create pipeline layout and bind it to command buffer
            VkPipelineLayoutCreateInfo          pipelineLayoutInfo = {};
            std::vector<VkDescriptorSetLayout>  layouts;
            VkDescriptorSet                     descSet = descriptorSets [i].getHandle ();

            layouts.push_back ( descSetLayout.getHandle () );

            pipelineLayoutInfo.sType          = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
            pipelineLayoutInfo.setLayoutCount = 0;
            pipelineLayoutInfo.setLayoutCount = (uint32_t) layouts.size ();
            pipelineLayoutInfo.pSetLayouts    = layouts.data ();

            vkCreatePipelineLayout  ( device.getDevice (), &pipelineLayoutInfo, nullptr, &pipelineLayout );
            vkCmdBindDescriptorSets ( cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descSet, 0, nullptr );

            VkShaderStageFlagBits stages  [2] = { VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT };
            VkShaderEXT           shaders [2] = { vertexShader, fragmentShader };

            vkCmdBindShadersEXT ( cmd, 2, stages, shaders );

                // render mesh
            commandBuffers [i].render ( mesh.get () );

            vkCmdEndRenderingKHR ( commandBuffers [i].getHandle () );

            auto    barrierImage2 = imageBarrier  ( swapChain.getImages () [i], 
                VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,      // srcStageMask
                VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,               // srcAccessMask
                VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,           // oldLayout
                VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,               // dstStageMask
                0,                                                  // dstAccessMask
                VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,                    // newLayout
                VK_IMAGE_ASPECT_COLOR_BIT                           // aspectMask
            );

            pipelineBarrier ( commandBuffers [i], { barrierImage2 } );

            commandBuffers [i].end ();
        }
    }

    void updateUniformBuffer ( uint32_t currentImage )
    {
        uniformBuffers [currentImage]->model = controller->getModelView  ();
        uniformBuffers [currentImage]->view  = glm::mat4 ( 1 );
        uniformBuffers [currentImage]->proj  = controller->getProjection ();
    }

    VkShaderEXT createShader ( Data& data, VkShaderStageFlagBits stage, DescSetLayout * layout = nullptr, 
                                const std::string& name = "main" )
    {
        VkShaderCreateInfoEXT   shaderCreateInfo {};
        VkDescriptorSetLayout   descSetLayout = layout ? layout -> getHandle () : VK_NULL_HANDLE;
        VkShaderEXT             shader        = VK_NULL_HANDLE;

        shaderCreateInfo.sType     = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT;
        shaderCreateInfo.flags     = VK_SHADER_CREATE_LINK_STAGE_BIT_EXT;
        shaderCreateInfo.stage     = stage;
        //shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT;
        shaderCreateInfo.codeType  = VK_SHADER_CODE_TYPE_SPIRV_EXT;
        shaderCreateInfo.pCode     = reinterpret_cast<const uint32_t*>( data.getPtr () );
        shaderCreateInfo.codeSize  = data.getLength ();
        shaderCreateInfo.pName     = name.c_str ();

        if ( layout != nullptr )
        {
            shaderCreateInfo.setLayoutCount = 1;
            shaderCreateInfo.pSetLayouts    = &descSetLayout;
        }

        if ( vkCreateShadersEXT ( device.getDevice (), 1, &shaderCreateInfo, nullptr, &shader) != VK_SUCCESS )
            warning () << "\nError creating shader object " << data.getFileName () << std::endl;

        return shader;
    }

    void    loadExtensions ()
    {
        vkCreateShadersEXT       = reinterpret_cast<PFN_vkCreateShadersEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCreateShadersEXT"));
        vkDestroyShaderEXT       = reinterpret_cast<PFN_vkDestroyShaderEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkDestroyShaderEXT"));
        vkCmdBindShadersEXT      = reinterpret_cast<PFN_vkCmdBindShadersEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdBindShadersEXT"));
        vkGetShaderBinaryDataEXT = reinterpret_cast<PFN_vkGetShaderBinaryDataEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkGetShaderBinaryDataEXT"));

        vkCmdBeginRenderingKHR   = reinterpret_cast<PFN_vkCmdBeginRenderingKHR>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdBeginRenderingKHR"));
        vkCmdEndRenderingKHR     = reinterpret_cast<PFN_vkCmdEndRenderingKHR>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdEndRenderingKHR"));

        vkCmdSetAlphaToCoverageEnableEXT   = reinterpret_cast<PFN_vkCmdSetAlphaToCoverageEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetAlphaToCoverageEnableEXT"));
        vkCmdSetColorBlendEnableEXT        = reinterpret_cast<PFN_vkCmdSetColorBlendEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetColorBlendEnableEXT"));
        vkCmdSetColorWriteMaskEXT          = reinterpret_cast<PFN_vkCmdSetColorWriteMaskEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetColorWriteMaskEXT"));
        vkCmdSetCullModeEXT                = reinterpret_cast<PFN_vkCmdSetCullModeEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetCullModeEXT"));
        vkCmdSetDepthBiasEnableEXT         = reinterpret_cast<PFN_vkCmdSetDepthBiasEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetDepthBiasEnableEXT"));
        vkCmdSetDepthCompareOpEXT          = reinterpret_cast<PFN_vkCmdSetDepthCompareOpEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetDepthCompareOpEXT"));
        vkCmdSetDepthTestEnableEXT         = reinterpret_cast<PFN_vkCmdSetDepthTestEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetDepthTestEnableEXT"));
        vkCmdSetDepthWriteEnableEXT        = reinterpret_cast<PFN_vkCmdSetDepthWriteEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetDepthWriteEnableEXT"));
        vkCmdSetFrontFaceEXT               = reinterpret_cast<PFN_vkCmdSetFrontFaceEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetFrontFaceEXT"));
        vkCmdSetPolygonModeEXT             = reinterpret_cast<PFN_vkCmdSetPolygonModeEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetPolygonModeEXT"));
        vkCmdSetPrimitiveRestartEnableEXT  = reinterpret_cast<PFN_vkCmdSetPrimitiveRestartEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetPrimitiveRestartEnableEXT"));
        vkCmdSetPrimitiveTopologyEXT       = reinterpret_cast<PFN_vkCmdSetPrimitiveTopologyEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetPrimitiveTopologyEXT"));
        vkCmdSetRasterizationSamplesEXT    = reinterpret_cast<PFN_vkCmdSetRasterizationSamplesEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetRasterizationSamplesEXT"));
        vkCmdSetRasterizerDiscardEnableEXT = reinterpret_cast<PFN_vkCmdSetRasterizerDiscardEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetRasterizerDiscardEnableEXT"));
        vkCmdSetSampleMaskEXT              = reinterpret_cast<PFN_vkCmdSetSampleMaskEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetSampleMaskEXT"));
        vkCmdSetScissorWithCountEXT        = reinterpret_cast<PFN_vkCmdSetScissorWithCountEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetScissorWithCountEXT"));
        vkCmdSetStencilTestEnableEXT       = reinterpret_cast<PFN_vkCmdSetStencilTestEnableEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetStencilTestEnableEXT"));
        vkCmdSetVertexInputEXT             = reinterpret_cast<PFN_vkCmdSetVertexInputEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetVertexInputEXT"));
        vkCmdSetViewportWithCountEXT       = reinterpret_cast<PFN_vkCmdSetViewportWithCountEXT>(vkGetDeviceProcAddr(device.getDevice (), "vkCmdSetViewportWithCountEXT"));;
    }
};

int main ( int argc, const char * argv [] ) 
{
    DevicePolicy    policy;
    VkPhysicalDeviceShaderObjectFeaturesEXT enabledShaderObjectFeaturesEXT {};

    enabledShaderObjectFeaturesEXT.sType        = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT;
    enabledShaderObjectFeaturesEXT.shaderObject = VK_TRUE;

    policy.addDeviceExtension ( VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME                );
    policy.addDeviceExtension ( VK_EXT_SHADER_OBJECT_EXTENSION_NAME                    );
    policy.addDeviceExtension ( VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME       );
    policy.addDeviceExtension ( VK_KHR_MAINTENANCE2_EXTENSION_NAME                     );
    policy.addDeviceExtension ( VK_KHR_MULTIVIEW_EXTENSION_NAME                        );
    policy.addDeviceExtension ( VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME              );
    policy.addDeviceExtension ( VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME            );

    policy.addFeatures ( &enabledShaderObjectFeaturesEXT );

    return ShaderObjectWindow ( 800, 600, "Shader objects with dynamic rendering", &policy ).run ();
}