packages feed

keid-core-0.1.8.0: src/Resource/Model.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

module Resource.Model where

import RIO
import GHC.Generics

import Codec.Serialise qualified as CBOR
import Data.Kind (Constraint, Type)
import Data.List qualified as List
import Data.Vector.Storable qualified as Storable
import Foreign (Storable(..))
import Geomancy (Vec2)
import Geomancy.Vec3 qualified as Vec3
import UnliftIO.Resource (MonadResource)
import Vulkan.Core10 qualified as Vk

import Engine.Vulkan.Pipeline.Graphics (HasVertexInputBindings(..))
import Engine.Vulkan.Format (HasVkFormat(..))
import Engine.Vulkan.Pipeline.Graphics qualified as Graphics
import Engine.Vulkan.Types (MonadVulkan, Queues(..))
import Resource.Buffer qualified as Buffer

data Indexed storage pos attrs = Indexed
  { iLabel     :: Maybe Text
  , iPositions :: Buffer.Allocated storage pos
  , iAttrs     :: Buffer.Allocated storage attrs
  , iIndices   :: Buffer.Allocated storage Word32
  } deriving (Show)

type Vertex2d attrs = Vertex Vec2 attrs
type Vertex3d attrs = Vertex Vec3.Packed attrs

data Vertex pos attrs = Vertex
  { vPosition :: pos
  , vAttrs    :: attrs
  } deriving (Eq, Ord, Show, Functor, Foldable, Traversable)

{-# INLINEABLE vertexAttrs #-}
vertexAttrs :: (pos -> a -> b) -> [Vertex pos a] -> [Vertex pos b]
vertexAttrs inject vertices = do
  v@Vertex{..} <- vertices
  pure v
    { vAttrs = inject vPosition vAttrs
    }

{-# INLINEABLE vertexAttrsPos #-}
vertexAttrsPos :: (pos -> a) -> [pos] -> [Vertex pos a]
vertexAttrsPos inject positions = do
  pos <- positions
  pure Vertex
    { vPosition = pos
    , vAttrs    = inject pos
    }

instance
  ( HasVkFormat pos
  , HasVkFormat attrs
  ) =>
  Graphics.HasVertexInputBindings (Vertex pos attrs) where
    vertexInputBindings =
      [ Graphics.vertexFormat @pos
      , Graphics.vertexFormat @attrs
      ]

class HasVertexBuffers a where
  type VertexBuffersOf a
  getVertexBuffers :: a -> [Vk.Buffer]
  getInstanceCount :: a -> Word32

  default getVertexBuffers
    :: ( Generic a
       , GHasVertexBuffers (Rep a)
       )
    => a
    -> [Vk.Buffer]
  getVertexBuffers = genericGetVertexBuffers

  default getInstanceCount
    :: ( Generic a
       , GHasVertexBuffers (Rep a)
       )
    => a
    -> Word32
  getInstanceCount = genericGetInstanceCount

instance HasVertexBuffers () where
  type VertexBuffersOf () = ()

  {-# INLINE getVertexBuffers #-}
  getVertexBuffers () = []

  {-# INLINE getInstanceCount #-}
  getInstanceCount () = 1

instance HasVertexBuffers (Buffer.Allocated store a) where
  type VertexBuffersOf (Buffer.Allocated store a) = a

  {-# INLINE getVertexBuffers #-}
  getVertexBuffers Buffer.Allocated{aBuffer} = [aBuffer]

  {-# INLINE getInstanceCount #-}
  getInstanceCount = Buffer.aUsed

genericGetVertexBuffers
  :: ( Generic a
     , GHasVertexBuffers (Rep a)
     )
  => a
  -> [Vk.Buffer]
genericGetVertexBuffers = gVertexBuffers . GHC.Generics.from

genericGetInstanceCount
  :: ( Generic a
     , GHasVertexBuffers (Rep a)
     )
  => a
  -> Word32
genericGetInstanceCount = gInstanceCount . GHC.Generics.from

type GHasVertexBuffers :: (Type -> Type) -> Constraint
class GHasVertexBuffers f where
  gVertexBuffers  :: forall a . f a -> [Vk.Buffer]
  gInstanceCount :: forall a . f a -> Word32

instance GHasVertexBuffers f => GHasVertexBuffers (M1 c cb f) where
  gVertexBuffers (M1 f) = gVertexBuffers f
  gInstanceCount (M1 f) = gInstanceCount f

instance (GHasVertexBuffers l, GHasVertexBuffers r) => GHasVertexBuffers (l :*: r) where
  gVertexBuffers (l :*: r) = gVertexBuffers l <> gVertexBuffers r
  gInstanceCount (l :*: r) = min (gInstanceCount l) (gInstanceCount r)

instance HasVertexBuffers a => GHasVertexBuffers (K1 r a) where
  gVertexBuffers (K1 a) = getVertexBuffers a
  gInstanceCount (K1 a) = getInstanceCount a

data IndexRange = IndexRange
  { irFirstIndex :: Word32
  , irIndexCount :: Word32
  }
  deriving (Eq, Ord, Show, Generic)

instance CBOR.Serialise IndexRange

instance Storable IndexRange where
  alignment ~_ = 4

  sizeOf ~_ = 8

  peek ptr = do
    irFirstIndex <- peekByteOff ptr 0
    irIndexCount <- peekByteOff ptr 4
    pure IndexRange{..}

  poke ptr IndexRange{..} = do
    pokeByteOff ptr 0 irFirstIndex
    pokeByteOff ptr 4 irIndexCount

createStagedL
  :: ( MonadVulkan env m
     , Storable pos
     , Storable attrs
     )
  => Maybe Text
  -> Queues Vk.CommandPool
  -> [Vertex pos attrs]
  -> Maybe [Word32]
  -> m (Indexed 'Buffer.Staged pos attrs)
createStagedL label pool vertices mindices = createStaged label pool pv av iv
  where
    pv = Storable.fromList ps
    av = Storable.fromList as

    iv = case mindices of
      Just is ->
        Storable.fromList is
      Nothing ->
        -- TODO: add vertex deduplication
        Storable.generate (Storable.length pv) fromIntegral

    (ps, as) = List.unzip do
      Vertex{..} <- vertices
      pure (vPosition, vAttrs)

createStaged
  :: ( MonadVulkan env m
     , Storable pos
     , Storable attrs
     )
  => Maybe Text
  -> Queues Vk.CommandPool
  -> Storable.Vector pos
  -> Storable.Vector attrs
  -> Storable.Vector Word32
  -> m (Indexed 'Buffer.Staged pos attrs)
createStaged label pool pv av iv = do
  positions <- Buffer.createStaged (label <&> \m -> mappend m ".positions") pool Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT 0 pv
  attrs     <- Buffer.createStaged (label <&> \m -> mappend m ".attrs") pool Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT 0 av
  indices   <- Buffer.createStaged (label <&> \m -> mappend m ".indices") pool Vk.BUFFER_USAGE_INDEX_BUFFER_BIT  0 iv
  pure Indexed
    { iLabel     = label
    , iPositions = positions
    , iAttrs     = attrs
    , iIndices   = indices
    }

createCoherentEmpty
  :: ( MonadVulkan env m
     , Storable pos
     , Storable attrs
     )
  => Maybe Text
  -> Int
  -> m (Indexed 'Buffer.Coherent pos attrs)
createCoherentEmpty label initialSize = Indexed label
  <$> Buffer.createCoherent (label <&> \m -> mappend m ".positions") Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT initialSize mempty
  <*> Buffer.createCoherent (label <&> \m -> mappend m ".attrs") Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT initialSize mempty
  <*> Buffer.createCoherent (label <&> \m -> mappend m ".indices") Vk.BUFFER_USAGE_INDEX_BUFFER_BIT  initialSize mempty

registerIndexed_
  :: ( MonadVulkan env m
     , MonadResource m
     )
  => Indexed storage pos attrs
  -> m ()
registerIndexed_ Indexed{..} = do
  void $! Buffer.register iPositions
  void $! Buffer.register iAttrs
  void $! Buffer.register iIndices

destroyIndexed
  :: MonadVulkan env m
  => Indexed storage pos attrs
  -> m ()
destroyIndexed Indexed{..} = do
  ctx <- ask
  Buffer.destroy ctx iPositions
  Buffer.destroy ctx iAttrs
  Buffer.destroy ctx iIndices

updateCoherent
  :: ( MonadVulkan env m
     , Storable pos
     , Storable attrs
     )
  => [Vertex pos attrs]
  -> Indexed 'Buffer.Coherent pos attrs
  -> m (Indexed 'Buffer.Coherent pos attrs)
updateCoherent vertices old = do
  Indexed{..} <- pick
  Indexed iLabel
    <$> Buffer.updateCoherent pv iPositions
    <*> Buffer.updateCoherent av iAttrs
    <*> Buffer.updateCoherent iv iIndices
  where
    pick =
      if oldSize > newSize then
        pure old
      else do
        destroyIndexed old
        createCoherentEmpty (iLabel old) (max newSize $ oldSize * 2)

    oldSize = Buffer.aCapacity $ iIndices old
    newSize = Storable.length pv

    pv = Storable.fromList ps
    av = Storable.fromList as

    iv = Storable.generate newSize fromIntegral

    (ps, as) = List.unzip do
      Vertex{..} <- vertices
      pure (vPosition, vAttrs)