module Render.ShadowMap.Pipeline
  ( Pipeline
  , allocate
  , Settings(..)
  , defaults
  ) where

import RIO

import Control.Monad.Trans.Resource (ResourceT)
import Data.Tagged (Tagged(..))
import Geomancy (Transform)
import Vulkan.Core10 qualified as Vk
import Vulkan.Zero (zero)

import Engine.Vulkan.Pipeline qualified as Pipeline
import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)
import Render.Code (compileVert, glsl)
import Render.Code.Lit (structLight)
import Render.DescSets.Set0 (vertexPos, instanceTransform)
import Render.DescSets.Sun (Sun, pattern MAX_VIEWS)

type Config = Pipeline.Config '[Sun] () Transform
type Pipeline = Pipeline.Pipeline '[Sun] () Transform

data Settings = Settings
  { cull      :: Vk.CullModeFlagBits
  , depthBias :: Maybe (Float, Float)
  }

defaults :: Settings
defaults = Settings
  { cull      = Vk.CULL_MODE_BACK_BIT
  , depthBias = Just (2.0, 2.5)
  }

allocate
  :: ( HasVulkan env
     , HasRenderPass renderpass
     )
  => Tagged Sun DsBindings
  -> renderpass
  -> Settings
  -> ResourceT (RIO env) Pipeline
allocate tset0 rp settings = do
  (_, p) <- Pipeline.allocate
    Nothing
    Vk.SAMPLE_COUNT_1_BIT
    (config tset0 settings)
    rp
  pure p

config :: Tagged Sun DsBindings -> Settings -> Config
config (Tagged set0) Settings{..} = zero
  { Pipeline.cDescLayouts  = Tagged @'[Sun] [set0]
  , Pipeline.cVertexCode   = Just vertCode
  , Pipeline.cVertexInput  = vertexInput
  , Pipeline.cDepthBias    = depthBias
  , Pipeline.cCull         = cull
  }
  where
    vertexInput = Pipeline.vertexInput
      [ vertexPos
      , instanceTransform
      ]

vertCode :: ByteString
vertCode =
  $(compileVert [glsl|
    #version 450
    #extension GL_ARB_separate_shader_objects : enable
    #extension GL_EXT_multiview : enable

    ${structLight}

    layout(set=0, binding=0, std140) uniform Globals {
      Light lights[${MAX_VIEWS}];
    };

    layout(location = 0) in vec3 vPosition;

    layout(location = 1) in mat4 iModel;

    void main() {
      gl_Position
        = lights[gl_ViewIndex].viewProjection
        * iModel
        * vec4(vPosition, 1.0);
    }
  |])
