steps3D - Tutorials - Расширение VK_EXT_dynamic_rendering

Расширение VK_EXT_dynamic_rendering

Вошедшее в состав Vulkan 1.3 Core расширение VK_EXT_dynamic_rendering позволяет упростить рендеринг, полностью отказавшись от использование таких объектов, как VkRenderPass и VkFramebuffer - они становятся больше не нужны. На самом деле они имеют смысл только в одном случае - когда мы пишем отложенный рендерер для тайлового GPU. Во всех остальных случаях (например, для традиционного GPU) пользы от них никакой нет, зато усложняется работа, требуя больше действий (которых при использовании Vulkan итак хватает).

При создании (логического) устройства мы должны через поле pNext структуры VkDeviceCreateInfo передать указатель на структуру типа VkPhysicalDeviceDynamicRenderingFeaturesKHR, у которой значение поля dynamicRendering равно VK_TRUE.

VkPhysicalDeviceVulkan13Features features13 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES };

features13.dynamicRendering = VK_TRUE;

policy.addFeatures ( &features13 );

Кроме этого изменится процедура создания конвейера рендеринга (pipeline), поскольку объект прохода рендеринга (VkRenderPass) больше не нужен. Мы задаем соответствующее поле renderPass в структуре VkGraphicsPipelineCreateInfo равным nullptr. Но также мы должны через поле pNext этой структуры передать структуру VkPipelineRenderingCreateInfoKHR, содержащую информацию о подключениях (attachments, color, depth and stencil) и их форматах. При этом такие объекты как VkRenderPass и VkFramebuffer из SwapChain становятся просто не нужны.

typedef struct VkPipelineRenderingCreateInfo
{
    VkStructureType    sType;
    const void*        pNext;
    uint32_t           viewMask;
    uint32_t           colorAttachmentCount;
    const VkFormat*    pColorAttachmentFormats;
    VkFormat           depthAttachmentFormat;
    VkFormat           stencilAttachmentFormat;
} VkPipelineRenderingCreateInfo;

Ниже приводится пример того, как можно задать эту структуру.

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 ();

Теперь давайте рассмотрим каким образом будет осуществляться сам рендеринг. Если ранее у нас код рендеринга находился между вызовами функций vkBeginRenderPass и vkEndRenderPass, то теперь код для рендеринга мы будем помещать между вызовами vkCmdBeginRenderingKHR и vkCmdEndRenderingKHR (а старые команды мы вообще не будем использовать).

void vkCmdBeginRenderingKHR(
    VkCommandBuffer                             commandBuffer,
    const VkRenderingInfo*                      pRenderingInfo);
void vkCmdEndRenderingKHR(
    VkCommandBuffer                             commandBuffer);

Команда vkCmdBeginRenderingKHR принимает на вход помимо командного буфера указатель на структуру VkRenderingInfo, задающую куда именно будет осуществляться рендеринг.

typedef struct VkRenderingInfo
{
    VkStructureType                     sType;
    const void*                         pNext;
    VkRenderingFlags                    flags;
    VkRect2D                            renderArea;
    uint32_t                            layerCount;
    uint32_t                            viewMask;
    uint32_t                            colorAttachmentCount;
    const VkRenderingAttachmentInfo*    pColorAttachments;
    const VkRenderingAttachmentInfo*    pDepthAttachment;
    const VkRenderingAttachmentInfo*    pStencilAttachment;
} VkRenderingInfo;

Поле flags обычно не используется и равно нулю. Поле renderArea задает область, в которую будет осуществляться рендеринг. Поле viewMask используется только в так называемом multiview-рендеринге и задает индексы подключений, в которые будет осуществляться рендеринг. При традиционном рендеринге оно также равно нулю. Поле layerCount нужно при viewMask равным нулю и задает число слоев в каждом подключении, в которые будет осуществляться рендеринг. Через оставшиеся поля передается информация обо всех подключениях, в которые будет осуществляться рендеринг.

typedef struct VkRenderingAttachmentInfo
{
    VkStructureType          sType;
    const void*              pNext;
    VkImageView              imageView;
    VkImageLayout            imageLayout;
    VkResolveModeFlagBits    resolveMode;
    VkImageView              resolveImageView;
    VkImageLayout            resolveImageLayout;
    VkAttachmentLoadOp       loadOp;
    VkAttachmentStoreOp      storeOp;
    VkClearValue             clearValue;
} VkRenderingAttachmentInfo;

Ниже приводится пример задания соответствующих значений для случая традиционного рендеринга с одним цветовым буфером и одним буфером глубины.

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

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

class   DynamicRenderingWindow : public VulkanWindow
{
    std::vector<CommandBuffer>      commandBuffers;
    std::vector<DescriptorSet>      descriptorSets;
    std::vector<Uniform<Ubo>>       uniformBuffers;
    GraphicsPipeline                pipeline;
    Renderpass                      renderPass;
    Texture                         texture;
    Sampler                         sampler;
    std::unique_ptr<Mesh>           mesh;

    PFN_vkCmdBeginRenderingKHR  vkCmdBeginRenderingKHR {};
    PFN_vkCmdEndRenderingKHR    vkCmdEndRenderingKHR   {};

public:
    DynamicRenderingWindow ( int w, int h, const std::string& t, DevicePolicy * p ) : VulkanWindow ( w, h, t, true, p )
    {
        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 optiona      
        texture.load    ( device, "../../Textures/Fieldstone.dds", false );
        loadExtensions  ();
        createPipelines ();
    }

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

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

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

        for ( uint32_t i = 0; i < swapChain.imageCount (); i++ )
        {
            descriptorSets  [i]
                .setLayout        ( device, descAllocator, pipeline.getDescLayout () )
                .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    ();

        pipeline.setDevice ( device )
                .setVertexShader   ( "shaders/shader-tex.vert.spv" )
                .setFragmentShader ( "shaders/shader-tex.frag.spv" )
                .setSize           ( swapChain.getExtent ().width, swapChain.getExtent ().height )
                .addVertexBinding  ( sizeof ( BasicVertex ) )
                .addVertexAttributes <BasicVertex> ()
                .addDescLayout     ( 0, 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 ) )
                .setCullMode       ( VK_CULL_MODE_NONE               )
            .setDepthTest      ( true )
            .setDepthWrite     ( true )
            .setNumColorBlendAttachments ( 1 )
            .addAddInfo        ( &pipelineRenderingCreateInfo )
            .create            ( renderPass );          

        createDescriptorSets ();
        createCommandBuffers ( renderPass );
    }

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

    void    createCommandBuffers ( Renderpass& renderPass )
    {
        //auto  framebuffers = swapChain.getFramebuffers ();

        commandBuffers = device.allocCommandBuffers ( swapChain.imageCount () );

        for ( size_t i = 0; i < swapChain.imageCount (); 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 ();

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

            vkCmdBeginRenderingKHR ( commandBuffers [i].getHandle (), &renderingInfo );

            //VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
            //vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);

            //VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
            //vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);

            commandBuffers [i]
                .pipeline          ( pipeline )
                .addDescriptorSets ( { descriptorSets[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 ();
    }

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

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

    features13.dynamicRendering = VK_TRUE;

    policy.addDeviceExtension ( VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME );

    return DynamicRenderingWindow ( 800, 600, "Dynamic rendering", &policy ).run ();
}

Структуру VkRenderingInfo можно завернуть в класс и добавить удобные способы создания, функции vkCmdBeginRenderingKHR и vkCmdEndRenderingKHR можно просто добавить как дополнительные методы к классу CommandBuffer. В результате мы можем простой пример рендеринга с использованием данного расширения записать следующим образом.

dynamic rendering

VK_EXT_dynamic_rendering