packages feed

keid-render-basic-0.1.8.0: src/Render/ShadowMap/RenderPass.hs

{-# LANGUAGE OverloadedLists #-}

module Render.ShadowMap.RenderPass
  ( ShadowMap(..)
  , allocate
  , usePass
  ) where

import RIO

import Control.Monad.Trans.Resource qualified as Resource
import Data.Bits ((.|.))
import Data.Vector qualified as Vector
import Vulkan.Core10 qualified as Vk
import Vulkan.Core11.Promoted_From_VK_KHR_multiview qualified as Khr
import Vulkan.NamedType ((:::))
import Vulkan.Utils.Debug qualified as Debug
import Vulkan.Zero (zero)
import Vulkan.CStruct.Extends (pattern (:&), pattern (::&))

import Engine.Types.RefCounted (RefCounted, resourceTRefCount)
import Engine.Types.RefCounted qualified as RefCounted
import Engine.Vulkan.Types (HasVulkan(..), HasSwapchain(..), HasRenderPass(..), RenderPass(..), MonadVulkan)
import Render.Pass (usePass)
import Resource.Image (AllocatedImage)
import Resource.Image qualified as Image
import Resource.Region qualified as Region
import Resource.Vulkan.Named qualified as Named

-- * Depth-only pass for shadowmapping pipelines

data ShadowMap = ShadowMap
  { smRenderPass  :: Vk.RenderPass
  , smDepthImage  :: AllocatedImage
  , smFrameBuffer :: Vk.Framebuffer
  , smRenderArea  :: Vk.Rect2D
  , smExtent      :: Vk.Extent2D
  , smLayerCount  :: Word32
  , smClear       :: Vector Vk.ClearValue
  , smRelease     :: RefCounted
  }

instance HasRenderPass ShadowMap where
  getRenderPass   = smRenderPass
  getFramebuffers = Vector.replicate 10 . smFrameBuffer
  getClearValues  = smClear
  getRenderArea   = smRenderArea

instance RenderPass ShadowMap where
  refcountRenderpass  = resourceTRefCount . smRelease

allocate
  :: ( Resource.MonadResource m
     , MonadVulkan env m
     , HasLogFunc env
     , HasSwapchain context
     )
  => context
  -> Word32
  -> "light count" ::: Word32
  -> m ShadowMap
allocate context mapSize layerCount = do
  (_rpKey, renderPass) <- allocateRenderPass context (2 ^ layerCount - 1) 0
  (release, resources) <- RefCounted.wrapped $ Region.run do
    Region.logDebug
      "Allocating ShadowMap resources"
      "Releasing ShadowMap resources"
    allocateFramebuffer context extent layerCount renderPass
  let (depthImage, framebuffer) = resources

  pure ShadowMap
    { smRenderPass  = renderPass
    , smExtent      = extent
    , smLayerCount  = layerCount
    , smRenderArea  = fullSurface
    , smClear       = clear
    , smDepthImage  = depthImage
    , smFrameBuffer = framebuffer
    , smRelease     = release
    }
  where
    extent = Vk.Extent2D{width=mapSize, height=mapSize}

    fullSurface = Vk.Rect2D
      { Vk.offset = zero
      , Vk.extent = extent
      }

    clear = Vector.fromList
      [ Vk.DepthStencil (Vk.ClearDepthStencilValue 1.0 0)
      ]

-- ** Render pass

allocateRenderPass
  :: ( MonadVulkan env m
     , Resource.MonadResource m
     , HasSwapchain swapchain
     )
  => swapchain
  -> Word32
  -> Word32
  -> m (Resource.ReleaseKey, Vk.RenderPass)
allocateRenderPass swapchain viewMask correlationMask = do
  device <- asks getDevice

  res@(_key, object) <- Vk.withRenderPass device createInfo Nothing Resource.allocate
  Debug.nameObject device object "ShadowMap.RP"
  pure res
  where
    depthFormat = getDepthFormat swapchain

    createInfo =
      zero
        { Vk.attachments  = Vector.fromList [depth]
        , Vk.subpasses    = Vector.fromList [subpass]
        , Vk.dependencies = Vector.fromList [pre, post]
        }
      ::& Khr.RenderPassMultiviewCreateInfo
        { Khr.viewMasks        = [viewMask]
        , Khr.viewOffsets      = []
        , Khr.correlationMasks = [correlationMask]
        }
      :& ()

    depth = zero
      { Vk.format         = depthFormat
      , 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_STENCIL_READ_ONLY_OPTIMAL
      }

    subpass = zero
      { Vk.pipelineBindPoint = Vk.PIPELINE_BIND_POINT_GRAPHICS
      , Vk.depthStencilAttachment = Just zero
          { Vk.attachment = 0
          , Vk.layout     = Vk.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
          }
      }

    pre = zero
      { Vk.dependencyFlags = Vk.DEPENDENCY_BY_REGION_BIT

      , Vk.srcSubpass      = Vk.SUBPASS_EXTERNAL
      , Vk.srcStageMask    = Vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT
      , Vk.srcAccessMask   = Vk.ACCESS_SHADER_READ_BIT

      , Vk.dstSubpass      = 0
      , Vk.dstStageMask    = Vk.PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
      , Vk.dstAccessMask   = Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
      }

    post = zero
      { Vk.dependencyFlags = Vk.DEPENDENCY_BY_REGION_BIT

      , Vk.srcSubpass      = 0
      , Vk.srcStageMask    = Vk.PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
      , Vk.srcAccessMask   = Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT

      , Vk.dstSubpass      = Vk.SUBPASS_EXTERNAL
      , Vk.dstStageMask    = Vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT
      , Vk.dstAccessMask   = Vk.ACCESS_SHADER_READ_BIT
      }

-- ** Framebuffer

type Framebuffers =
  ( Image.AllocatedImage
  , Vk.Framebuffer
  )

allocateFramebuffer
  :: ( Resource.MonadResource m
     , MonadVulkan env m
     , HasSwapchain swapchain
     )
  => swapchain
  -> Vk.Extent2D
  -> Word32
  -> Vk.RenderPass
  -> m Framebuffers
allocateFramebuffer swapchain extent layerCount renderPass = do
  depth <- Image.allocate
    (Just "ShadowMap.depth")
    Vk.IMAGE_ASPECT_DEPTH_BIT
    (Image.inflateExtent extent 1)
    1
    layerCount
    Vk.SAMPLE_COUNT_1_BIT
    (getDepthFormat swapchain)
    (Vk.IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT .|. Vk.IMAGE_USAGE_SAMPLED_BIT)

  let
    attachments = Vector.fromList
      [ Image.aiImageView depth
      ]

    {- XXX:
      If the render pass uses multiview, then layers must be one and each attachment
      requires a number of layers that is greater than the maximum bit index set in
      the view mask in the subpasses in which it is used.
    -}
    fbNumLayers = 1

    Vk.Extent2D{width, height} = extent
    fbCI = zero
      { Vk.renderPass  = renderPass
      , Vk.width       = width
      , Vk.height      = height
      , Vk.attachments = attachments
      , Vk.layers      = fbNumLayers
      }

  device <- asks getDevice
  (_framebufferKey, framebuffer) <- Vk.withFramebuffer device fbCI Nothing Resource.allocate
  Named.object framebuffer "ShadowMap.FB"

  pure (depth, framebuffer)