{-# LANGUAGE OverloadedLists #-}

module Render.ShadowMap.RenderPass where

import RIO

import Control.Monad.Trans.Resource qualified as Resource
import Data.Vector qualified as Vector
import RIO.Vector.Partial ((!))
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, newRefCounted, resourceTRefCount)
import Engine.Vulkan.Types (HasVulkan(..), HasSwapchain(..), HasRenderPass(..), RenderPass(..), MonadVulkan)
import Resource.Image (AllocatedImage)
import Resource.Image qualified as Image

-- * 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
  allocateRenderpass_ = error "Don't allocate ShadowMap via class"
  updateRenderpass    = error "Don't recreate ShadowMap on resize"
  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
  logDebug "Allocating ShadowMap resources"
  (_rpKey, renderPass) <- allocateRenderPass context (2 ^ layerCount - 1) 0
  (refcounted, depthImage, framebuffer) <- allocateFramebuffer context extent layerCount renderPass

  pure ShadowMap
    { smRenderPass  = renderPass
    , smExtent      = extent
    , smLayerCount  = layerCount
    , smRenderArea  = fullSurface
    , smClear       = clear
    , smDepthImage  = depthImage
    , smFrameBuffer = framebuffer
    , smRelease     = refcounted
    }
  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 context
     )
  => context
  -> Word32
  -> Word32
  -> m (Resource.ReleaseKey, Vk.RenderPass)
allocateRenderPass context viewMask correlationMask = do
  device <- asks getDevice
  let depthFormat = getDepthFormat context

  res@(_key, object) <- Vk.withRenderPass device (createInfo depthFormat) Nothing Resource.allocate
  Debug.nameObject device object "ShadowMap.RP"
  pure res
  where
    createInfo depthFormat =
      zero
        { Vk.attachments  = Vector.fromList [depth depthFormat]
        , Vk.subpasses    = Vector.fromList [subpass]
        , Vk.dependencies = Vector.fromList [pre, post]
        }
      ::& Khr.RenderPassMultiviewCreateInfo
        { Khr.viewMasks        = [viewMask]
        , Khr.viewOffsets      = []
        , Khr.correlationMasks = [correlationMask]
        }
      :& ()

    depth depthFormat = 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 =
  ( RefCounted
  , Image.AllocatedImage
  , Vk.Framebuffer
  )

allocateFramebuffer
  :: ( Resource.MonadResource m
     , MonadVulkan env m
     , HasLogFunc env
     , HasSwapchain context
     )
  => context
  -> Vk.Extent2D
  -> Word32
  -> Vk.RenderPass
  -> m Framebuffers
allocateFramebuffer context extent layerCount renderPass = do
  device <- asks getDevice
  context' <- asks (, context)
  let Vk.Extent2D{width, height} = extent

  (depthKey, depth) <- Resource.allocate
    (Image.createDepthResource context' extent $ Just layerCount)
    (Image.destroyAllocatedImage context')

  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

    fbCI = zero
      { Vk.renderPass  = renderPass
      , Vk.width       = width
      , Vk.height      = height
      , Vk.attachments = attachments
      , Vk.layers      = fbNumLayers
      }
  (framebufferKey, framebuffer) <- Vk.withFramebuffer device fbCI Nothing Resource.allocate
  Debug.nameObject device framebuffer "ShadowMap.FB"

  releaseDebug <- toIO $ logDebug "Releasing ShadowMap resources"
  release <- newRefCounted do
    releaseDebug
    Resource.release depthKey
    Resource.release framebufferKey

  pure (release, depth, framebuffer)

usePass :: (MonadIO io, HasRenderPass a) => a -> Word32 -> Vk.CommandBuffer -> io r -> io r
usePass render imageIndex cb =
  Vk.cmdUseRenderPass
    cb
    (beginInfo render imageIndex)
    Vk.SUBPASS_CONTENTS_INLINE

beginInfo :: HasRenderPass a => a -> Word32 -> Vk.RenderPassBeginInfo '[]
beginInfo rp imageIndex = zero
  { Vk.renderPass  = getRenderPass rp
  , Vk.framebuffer = getFramebuffers rp ! fromIntegral imageIndex -- XXX: we only have one
  , Vk.renderArea  = getRenderArea rp
  , Vk.clearValues = getClearValues rp
  }
