packages feed

keid-core-0.1.0.0: src/Resource/Image.hs

module Resource.Image where

import RIO

import Data.Bits (shiftR, (.|.))
import RIO.Vector qualified as Vector
import Vulkan.Core10 qualified as Vk
import Vulkan.CStruct.Extends (SomeStruct(..))
import Vulkan.NamedType ((:::))
import Vulkan.Utils.Debug qualified as Debug
import Vulkan.Zero (zero)
import VulkanMemoryAllocator qualified as VMA

import Engine.Vulkan.Types (HasVulkan(..), HasSwapchain(..), Queues(..))
import Resource.CommandBuffer (oneshot_)

data AllocatedImage = AllocatedImage
  { aiAllocation :: VMA.Allocation
  , aiImage      :: Vk.Image
  , aiImageView  :: Vk.ImageView
  }
  deriving (Show)

createColorResource
  :: ( MonadIO io
     , HasVulkan ctx
     , HasSwapchain ctx
     )
  => ctx
  -> Vk.Extent2D
  -> io AllocatedImage
createColorResource context Vk.Extent2D{width, height} = do
  let
    device    = getDevice context
    allocator = getAllocator context
    format    = getSurfaceFormat context
    msaa      = getMultisample context

  (image, allocation, _info) <- VMA.createImage
    allocator
    (imageCI format msaa)
    imageAllocationCI
  Debug.nameObject device image "ColorResource.image"

  imageView <- Vk.createImageView
    device
    (imageViewCI image format)
    Nothing
  Debug.nameObject device image "ColorResource.view"

  pure AllocatedImage
    { aiAllocation = allocation
    , aiImage      = image
    , aiImageView  = imageView
    }
  where
    imageCI format msaa = zero
      { Vk.imageType     = Vk.IMAGE_TYPE_2D
      , Vk.format        = format
      , Vk.extent        = Vk.Extent3D width height 1
      , Vk.mipLevels     = 1
      , Vk.arrayLayers   = 1
      , Vk.tiling        = Vk.IMAGE_TILING_OPTIMAL
      , Vk.initialLayout = Vk.IMAGE_LAYOUT_UNDEFINED
      , Vk.usage         = Vk.IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT .|. Vk.IMAGE_USAGE_COLOR_ATTACHMENT_BIT
      , Vk.sharingMode   = Vk.SHARING_MODE_EXCLUSIVE
      , Vk.samples       = msaa
      }

    imageAllocationCI = zero
      { VMA.usage         = VMA.MEMORY_USAGE_GPU_ONLY
      , VMA.requiredFlags = Vk.MEMORY_PROPERTY_DEVICE_LOCAL_BIT
      }

    imageViewCI image format = zero
      { Vk.image            = image
      , Vk.viewType         = Vk.IMAGE_VIEW_TYPE_2D
      , Vk.format           = format
      , Vk.components       = zero
      , Vk.subresourceRange = subr
      }

    subr = zero
      { Vk.aspectMask     = Vk.IMAGE_ASPECT_COLOR_BIT
      , Vk.baseMipLevel   = 0
      , Vk.levelCount     = 1
      , Vk.baseArrayLayer = 0
      , Vk.layerCount     = 1
      }

createDepthResource
  :: ( MonadIO io
     , HasVulkan context
     , HasSwapchain context
     )
  => context
  -> Vk.Extent2D
  -> "shadowmap layers" ::: Maybe Word32
  -> io AllocatedImage
createDepthResource context Vk.Extent2D{width, height} depthLayers = do
  let
    device    = getDevice context
    allocator = getAllocator context

    depthFormat = getDepthFormat context
    msaa        = getMultisample context

    (samples, usage, numLayers) =
      case depthLayers of
        Nothing ->
          ( msaa
          , Vk.IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
          , 1
          )
        Just nl ->
          ( Vk.SAMPLE_COUNT_1_BIT
          , Vk.IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT .|.
            Vk.IMAGE_USAGE_SAMPLED_BIT
          , nl
          )

  (image, allocation, _info) <- VMA.createImage
    allocator
    (imageCI depthFormat usage samples numLayers)
    imageAllocationCI
  Debug.nameObject device image "DepthResource.image"

  imageView <- Vk.createImageView
    device
    (imageViewCI depthFormat image numLayers)
    Nothing
  Debug.nameObject device image "DepthResource.view"

  pure AllocatedImage
    { aiAllocation = allocation
    , aiImage      = image
    , aiImageView  = imageView
    }
  where
    imageCI format usage samples numLayers = zero
      { Vk.imageType     = Vk.IMAGE_TYPE_2D
      , Vk.format        = format
      , Vk.extent        = Vk.Extent3D width height 1
      , Vk.mipLevels     = 1
      , Vk.arrayLayers   = numLayers
      , Vk.tiling        = Vk.IMAGE_TILING_OPTIMAL
      , Vk.initialLayout = Vk.IMAGE_LAYOUT_UNDEFINED
      , Vk.usage         = usage
      , Vk.sharingMode   = Vk.SHARING_MODE_EXCLUSIVE
      , Vk.samples       = samples
      }

    imageAllocationCI = zero
      { VMA.usage         = VMA.MEMORY_USAGE_GPU_ONLY
      , VMA.requiredFlags = Vk.MEMORY_PROPERTY_DEVICE_LOCAL_BIT
      }

    imageViewCI format image numLayers = zero
      { Vk.image            = image
      , Vk.viewType         = viewType
      , Vk.format           = format
      , Vk.components       = zero
      , Vk.subresourceRange = subr numLayers
      }
      where
        viewType =
          if numLayers > 1 then
            Vk.IMAGE_VIEW_TYPE_2D_ARRAY
          else
            Vk.IMAGE_VIEW_TYPE_2D

    subr numLayers = zero
      { Vk.aspectMask     = Vk.IMAGE_ASPECT_DEPTH_BIT
      , Vk.baseMipLevel   = 0
      , Vk.levelCount     = 1
      , Vk.baseArrayLayer = 0
      , Vk.layerCount     = numLayers
      }

destroyAllocatedImage
  :: ( MonadIO io
     , HasVulkan context
     )
  => context
  -> AllocatedImage
  -> io ()
destroyAllocatedImage context AllocatedImage{..} = do
  -- traceM "destroyAllocatedImage"
  Vk.destroyImageView (getDevice context) aiImageView Nothing
  VMA.destroyImage (getAllocator context) aiImage aiAllocation

--------------------------------------------

transitionImageLayout
  :: (HasVulkan context)
  => context
  -> Queues Vk.CommandPool
  -> Vk.Image
  -> "mip levels" ::: Word32
  -> "layer count" ::: Word32
  -> Vk.Format
  -> ("old" ::: Vk.ImageLayout)
  -> ("new" ::: Vk.ImageLayout)
  -> RIO env ()
transitionImageLayout ctx pool image mipLevels layerCount format old new =
  case (old, new) of
    (Vk.IMAGE_LAYOUT_UNDEFINED, Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) ->
      oneshot_ ctx pool qTransfer \buf ->
        Vk.cmdPipelineBarrier
          buf
          Vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT
          Vk.PIPELINE_STAGE_TRANSFER_BIT
          zero
          mempty
          mempty
          ( Vector.singleton $
              barrier Vk.IMAGE_ASPECT_COLOR_BIT zero Vk.ACCESS_TRANSFER_WRITE_BIT
          )
    (Vk.IMAGE_LAYOUT_UNDEFINED, Vk.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) ->
      oneshot_ ctx pool qTransfer \buf ->
        Vk.cmdPipelineBarrier
          buf
          Vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT
          Vk.PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
          zero
          mempty
          mempty
          ( Vector.singleton $
              barrier aspectMask zero $
                Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT .|.
                Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
          )
        where
          aspectMask =
            if hasStencilComponent then
              Vk.IMAGE_ASPECT_DEPTH_BIT .|. Vk.IMAGE_ASPECT_STENCIL_BIT
            else
              Vk.IMAGE_ASPECT_DEPTH_BIT
          hasStencilComponent =
            format == Vk.FORMAT_D32_SFLOAT_S8_UINT ||
            format == Vk.FORMAT_D24_UNORM_S8_UINT

    (Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, Vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ->
      oneshot_ ctx pool qGraphics \buf ->
        Vk.cmdPipelineBarrier
          buf
          Vk.PIPELINE_STAGE_TRANSFER_BIT
          Vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT
          zero
          mempty
          mempty
          ( Vector.singleton $
              barrier
                Vk.IMAGE_ASPECT_COLOR_BIT
                Vk.ACCESS_TRANSFER_WRITE_BIT
                Vk.ACCESS_SHADER_READ_BIT
          )
    _ ->
      error $ "Unsupported image layout transfer: " <> show (old, new)
  where
    barrier aspectMask srcMask dstMask = SomeStruct zero
      { Vk.srcAccessMask       = srcMask
      , Vk.dstAccessMask       = dstMask
      , Vk.oldLayout           = old
      , Vk.newLayout           = new
      , Vk.srcQueueFamilyIndex = Vk.QUEUE_FAMILY_IGNORED
      , Vk.dstQueueFamilyIndex = Vk.QUEUE_FAMILY_IGNORED
      , Vk.image               = image
      , Vk.subresourceRange    = subresource aspectMask mipLevels layerCount
      }

subresource
  :: Vk.ImageAspectFlags
  -> "mip levels" ::: Word32
  -> "layer count" ::: Word32
  -> Vk.ImageSubresourceRange
subresource aspectMask mipLevels layerCount = Vk.ImageSubresourceRange
  { aspectMask     = aspectMask
  , baseMipLevel   = 0
  , levelCount     = mipLevels -- XXX: including base
  , baseArrayLayer = 0
  , layerCount     = layerCount
  }

copyBufferToImage
  :: (HasVulkan context, Foldable t, Integral deviceSize)
  => context
  -> Queues Vk.CommandPool
  -> Vk.Buffer
  -> Vk.Image
  -> "base extent" ::: Vk.Extent3D
  -> "mip offsets" ::: t deviceSize
  -> "layer count" ::: Word32
  -> RIO env ()
copyBufferToImage ctx pool src dst Vk.Extent3D{..} mipOffsets layerCount =
  oneshot_ ctx pool qTransfer \cmd ->
    Vk.cmdCopyBufferToImage cmd src dst Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL $
      Vector.fromList copyRegions
  where
    copyRegions = do
      (offset, mipLevel) <- zip (toList mipOffsets) [0..]
      pure Vk.BufferImageCopy
        { Vk.bufferOffset      = fromIntegral offset
        , Vk.bufferRowLength   = zero -- XXX: "use extent width"
        , Vk.bufferImageHeight = zero -- XXX: "use extent height"
        , Vk.imageSubresource = Vk.ImageSubresourceLayers
            { aspectMask     = Vk.IMAGE_ASPECT_COLOR_BIT
            , mipLevel       = fromIntegral mipLevel
            , baseArrayLayer = 0
            , layerCount     = layerCount
            }
        , Vk.imageOffset = zero
        , Vk.imageExtent = Vk.Extent3D
            { width  = max 1 $ width `shiftR` mipLevel
            , height = max 1 $ height `shiftR` mipLevel
            , depth  = depth
            }
        }