packages feed

vulkan-utils-0.5.11.0: src/Vulkan/Utils/RenderPass.hs

{-# LANGUAGE OverloadedLists #-}

{-| The classic render-pass drawing path: a 'Vk.RenderPass' over one or more
colour attachments (and an optional depth attachment), framebuffers over the
swapchain image views, and a vanilla pipeline that targets the render pass.

This is one of two self-contained alternatives — see
"Vulkan.Utils.DynamicRendering" for the @VK_KHR_dynamic_rendering@ path, which
needs neither a render pass nor framebuffers. Pick one and import only it.
-}
module Vulkan.Utils.RenderPass
  ( -- * Render pass
    allocateRenderPass
  , allocateColorRenderPass

    -- * Pipeline
  , PipelineConfig (..)
  , allocatePipeline
  , allocatePipelineFromShaders
  ) where

import Control.Monad.IO.Unlift (MonadUnliftIO)
import Control.Monad.Trans.Resource (MonadResource, ReleaseKey, allocate)
import Data.Bits ((.|.))
import Data.ByteString (ByteString)
import Data.Maybe (fromMaybe, isJust)
import Data.Vector (Vector)
import qualified Data.Vector as V
import Vulkan.CStruct.Extends (SomeStruct (..))
import qualified Vulkan.Core10 as Vk
import Vulkan.Utils.DynamicState (defaultDynamicStatesFor)
import Vulkan.Utils.Pipeline.Internal (basePipelineCreateInfo, buildColorPipeline, withCompiledStages)
import Vulkan.Utils.Pipeline.Specialization (Specialization)
import Vulkan.Zero (Zero (..))

{- | A render pass with @colors@ colour attachments (each @(format, finalLayout)@)
and an optional depth attachment, all cleared on load and stored on completion, in
a single graphics subpass. Attachment indices are the colours @0..N-1@ then the
depth attachment at @N@ — the colour-then-depth order the framebuffer's
@attachments@ must follow. The external
dependency synchronizes colour output and, when present, the
depth fragment tests.
-}
allocateRenderPass
  :: (MonadResource m)
  => Vk.Device
  -> Vector (Vk.Format, Vk.ImageLayout)
  -- ^ Colour attachments: @(format, finalLayout)@.
  -> Maybe Vk.Format
  -- ^ Optional depth attachment format.
  -> m (ReleaseKey, Vk.RenderPass)
allocateRenderPass dev colors depth =
  Vk.withRenderPass
    dev
    zero
      { Vk.attachments = colorDescriptions <> depthDescriptions
      , Vk.subpasses = [subpass]
      , Vk.dependencies = [subpassDependency]
      }
    Nothing
    allocate
  where
    colorCount = V.length colors
    hasColor = colorCount > 0
    hasDepth = isJust depth

    colorDescriptions :: Vector Vk.AttachmentDescription
    colorDescriptions = fmap (uncurry colorAttachmentDescription) colors

    depthDescriptions :: Vector Vk.AttachmentDescription
    depthDescriptions = maybe [] (V.singleton . depthAttachmentDescription) depth

    colorReferences :: Vector Vk.AttachmentReference
    colorReferences =
      V.imap
        ( \i _ ->
            zero
              { Vk.attachment = fromIntegral i
              , Vk.layout = Vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
              }
        )
        colors

    depthReference :: Maybe Vk.AttachmentReference
    depthReference
      | hasDepth =
          Just
            zero
              { Vk.attachment = fromIntegral colorCount
              , Vk.layout = Vk.IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL
              }
      | otherwise = Nothing

    subpass :: Vk.SubpassDescription
    subpass =
      zero
        { Vk.pipelineBindPoint = Vk.PIPELINE_BIND_POINT_GRAPHICS
        , Vk.colorAttachments = colorReferences
        , Vk.depthStencilAttachment = depthReference
        }

    subpassDependency :: Vk.SubpassDependency
    subpassDependency =
      zero
        { Vk.srcSubpass = Vk.SUBPASS_EXTERNAL
        , Vk.dstSubpass = 0
        , Vk.srcStageMask = stageMask
        , Vk.srcAccessMask = zero
        , Vk.dstStageMask = stageMask
        , Vk.dstAccessMask = accessMask
        }

    stageMask =
      (if hasColor then Vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT else zero)
        .|. ( if hasDepth
                then
                  Vk.PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
                    .|. Vk.PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
                else zero
            )
    accessMask =
      ( if hasColor
          then Vk.ACCESS_COLOR_ATTACHMENT_READ_BIT .|. Vk.ACCESS_COLOR_ATTACHMENT_WRITE_BIT
          else zero
      )
        .|. (if hasDepth then Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT else zero)

colorAttachmentDescription :: Vk.Format -> Vk.ImageLayout -> Vk.AttachmentDescription
colorAttachmentDescription imageFormat finalLayout =
  zero
    { Vk.format = imageFormat
    , Vk.samples = Vk.SAMPLE_COUNT_1_BIT
    , Vk.loadOp = Vk.ATTACHMENT_LOAD_OP_CLEAR
    , Vk.storeOp = Vk.ATTACHMENT_STORE_OP_STORE
    , Vk.stencilLoadOp = Vk.ATTACHMENT_LOAD_OP_DONT_CARE
    , Vk.stencilStoreOp = Vk.ATTACHMENT_STORE_OP_DONT_CARE
    , Vk.initialLayout = Vk.IMAGE_LAYOUT_UNDEFINED
    , Vk.finalLayout = finalLayout
    }

depthAttachmentDescription :: Vk.Format -> Vk.AttachmentDescription
depthAttachmentDescription imageFormat =
  zero
    { Vk.format = imageFormat
    , Vk.samples = Vk.SAMPLE_COUNT_1_BIT
    , Vk.loadOp = Vk.ATTACHMENT_LOAD_OP_CLEAR
    , Vk.storeOp = Vk.ATTACHMENT_STORE_OP_STORE
    , Vk.stencilLoadOp = Vk.ATTACHMENT_LOAD_OP_DONT_CARE
    , Vk.stencilStoreOp = Vk.ATTACHMENT_STORE_OP_DONT_CARE
    , Vk.initialLayout = Vk.IMAGE_LAYOUT_UNDEFINED
    , Vk.finalLayout = Vk.IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL
    }

{- | The single-colour render pass: one attachment cleared on load and stored,
ending in @finalLayout@ (e.g. @PRESENT_SRC_KHR@ for swapchains,
@TRANSFER_SRC_OPTIMAL@ for offscreen images). The common special case of
'allocateRenderPass'.
-}
allocateColorRenderPass
  :: (MonadResource m)
  => Vk.Device
  -> Vk.Format
  -- ^ Color attachment format.
  -> Vk.ImageLayout
  -- ^ Final layout.
  -> m (ReleaseKey, Vk.RenderPass)
allocateColorRenderPass dev imageFormat finalLayout =
  allocateRenderPass dev [(imageFormat, finalLayout)] Nothing

{- | Attachment + fixed-function knobs for a render-pass pipeline.

Construct with 'zero' and override what differs, e.g.
@zero { RenderPass.colorFormats = [fmt], RenderPass.depthFormat = Just d }@. The
attachment shape — 'colorFormats' count and whether 'depthFormat' is present — MUST
match the render pass; the formats themselves live in the render pass, so only the
count and depth presence are read here.
-}
data PipelineConfig = PipelineConfig
  { colorFormats :: [Vk.Format]
  -- ^ Colour attachment formats; only the count is read (must match the render pass).
  , depthFormat :: Maybe Vk.Format
  -- ^ Optional depth attachment; only its presence is read (must match the render pass).
  , vertexInput :: Vk.PipelineVertexInputStateCreateInfo '[]
  -- ^ Vertex input (bindings + attributes); 'zero' for none.
  , dynamicStates :: Maybe (Vector Vk.DynamicState)
  -- ^ Dynamic states; 'Nothing' defaults layout-aware (see "Vulkan.Utils.DynamicState").
  , layout :: Maybe Vk.PipelineLayout
  {- ^ Pipeline layout for descriptor sets \/ push constants; 'Nothing' uses a
  transient empty layout (shaders take no resources). A supplied layout stays
  owned by the caller, who must keep it alive for the pipeline's lifetime.
  -}
  }

instance Zero PipelineConfig where
  zero =
    PipelineConfig
      { colorFormats = []
      , depthFormat = Nothing
      , vertexInput = zero
      , dynamicStates = Nothing
      , layout = Nothing
      }

{- | A vanilla vertex+fragment pipeline targeting @renderPass@ (subpass 0). The
'PipelineConfig' attachment shape MUST match @renderPass@. Whatever dynamic state
is selected MUST be set before drawing. Intended to be used qualified, e.g.
@RenderPass.allocatePipeline@.
-}
allocatePipeline
  :: (MonadResource m, MonadFail m)
  => Vk.Device
  -> Vk.RenderPass
  -> PipelineConfig
  -> Vector (SomeStruct Vk.PipelineShaderStageCreateInfo)
  -> m (ReleaseKey, Vk.Pipeline)
allocatePipeline dev renderPass PipelineConfig{..} stages =
  buildColorPipeline dev layout $ \resolvedLayout ->
    SomeStruct
      ( basePipelineCreateInfo
          resolvedLayout
          (Just renderPass)
          (length colorFormats)
          (isJust depthFormat)
          vertexInput
          (fromMaybe (defaultDynamicStatesFor (not (null colorFormats))) dynamicStates)
          stages
      )

{- | 'allocatePipeline' from @(stage, SPIR-V)@ pairs: compile each into a shader
module, build the pipeline, then release the now-redundant module handles.

@spec@ is one specialization shared by every stage (see
'Vulkan.Utils.Pipeline.Specialization'); pass @()@ for none.
-}
allocatePipelineFromShaders
  :: (MonadResource m, MonadUnliftIO m, MonadFail m, Specialization spec)
  => Vk.Device
  -> Vk.RenderPass
  -> PipelineConfig
  -> spec
  -- ^ Specialization shared by every stage; @()@ for none.
  -> [(Vk.ShaderStageFlagBits, ByteString)]
  -> m (ReleaseKey, Vk.Pipeline)
allocatePipelineFromShaders dev renderPass config spec shaders =
  withCompiledStages dev spec shaders (allocatePipeline dev renderPass config)