packages feed

keid-core-0.1.0.0: src/Render/Draw.hs

module Render.Draw
  ( triangle_
  , triangles_
  , quads

  , indexed
  , indexedRanges
  , indexedParts

  , indexedPos
  , indexedPosRanges

  , unsafeIndexedRanges
  , unsafeIndexedParts
  ) where

import RIO

import Data.Vector qualified as Vector
import Vulkan.Core10 qualified as Vk

import Engine.Vulkan.Types (Bound(..))
import Resource.Buffer qualified as Buffer
import Resource.Model qualified as Model

-- * Primitives

-- | Single triangle, binding nothing.
triangle_ :: MonadUnliftIO m => Vk.CommandBuffer -> Bound dsl () () m ()
triangle_ cb = triangles_ cb 1

-- | Multiple shader-driven triangles without bindings.
triangles_ :: MonadUnliftIO m => Vk.CommandBuffer -> Word32 -> Bound dsl () () m ()
triangles_ cb num =
  Bound $ Vk.cmdDraw cb 3 num 0 0

-- | Instanced quads.
quads
  :: MonadUnliftIO m
  => Vk.CommandBuffer
  -> Buffer.Allocated stage instances
  -> Bound dsl () instances m ()
quads cb instances = Bound do
  Vk.cmdBindVertexBuffers cb 0 (pure $ Buffer.aBuffer instances) (pure 0)
  Vk.cmdDraw cb 6 (Buffer.aUsed instances) 0 0

-- * Indexed models

-- ** Positions + attributes

-- | Draw whole-model instances.
indexed
  :: (MonadUnliftIO m, Model.HasVertexBuffers instances)
  => Vk.CommandBuffer
  -> Model.Indexed storage pos attrs
  -> instances
  -> Bound dsl attrs (Model.VertexBuffersOf instances) m ()
indexed cmd model instances = indexedRanges cmd model instances [wholeIndexed]
  where
    wholeIndexed = Model.IndexRange
      { irFirstIndex = 0
      , irIndexCount = Buffer.aUsed (Model.iIndices model)
      }

{- |
  Draw subrange of each instance.

  E.g. chunks of the same material drawn in different places.
-}
indexedRanges
  :: (MonadUnliftIO m, Model.HasVertexBuffers instances)
  => Vk.CommandBuffer
  -> Model.Indexed storage pos attrs
  -> instances
  -> [Model.IndexRange]
  -> Bound dsl attrs (Model.VertexBuffersOf instances) m ()
indexedRanges cmd model instances ranges = do
  checkedTanges <- traverse check ranges
  Bound $ unsafeIndexedRanges True cmd model instances checkedTanges
  where
    check ir@Model.IndexRange{..}
      | irFirstIndex > maxIndex =
          throwString "firstIndex is over the actual buffer size"
      | irFirstIndex + irIndexCount > maxIndex =
          throwString "firstIndex + indexCount is over the actual buffer size"
      | otherwise =
          pure ir

    maxIndex = Buffer.aUsed (Model.iIndices model)

{- |
  Draw ranges and instances zipped.

  E.g. range materials stored in instances.
-}
indexedParts
  :: (MonadUnliftIO m, Model.HasVertexBuffers instances, Foldable t)
  => Bool
  -> Vk.CommandBuffer
  -> Model.Indexed storage pos attrs
  -> instances
  -> Int
  -> t Model.IndexRange
  -> Bound dsl attrs (Model.VertexBuffersOf instances) m ()
indexedParts drawAttrs cmd model instances startInstance parts =
  Bound $ unsafeIndexedParts drawAttrs cmd model instances startInstance parts

-- ** Position-only draws

-- | Draw whole-model instances, ignoring attributes.
indexedPos
  :: (MonadUnliftIO m, Model.HasVertexBuffers instances)
  => Vk.CommandBuffer
  -> Model.Indexed storage pos unusedAttrs
  -> instances
  -> Bound dsl ignoreAttrs (Model.VertexBuffersOf instances) m ()
indexedPos cmd model instances = indexedPosRanges cmd model instances [wholeIndexed]
  where
    wholeIndexed = Model.IndexRange
      { irFirstIndex = 0
      , irIndexCount = Buffer.aUsed (Model.iIndices model)
      }

-- | Draw subrange of each instances, ignoring attributes.
indexedPosRanges
  :: (MonadUnliftIO m, Model.HasVertexBuffers instances)
  => Vk.CommandBuffer
  -> Model.Indexed storage pos unusedAttrs
  -> instances
  -> [Model.IndexRange]
  -> Bound dsl ignoreAttrs (Model.VertexBuffersOf instances) m ()
indexedPosRanges cmd model instances ranges = do
  checkedTanges <- traverse check ranges
  Bound $ unsafeIndexedRanges False cmd model instances checkedTanges
  where
    check ir@Model.IndexRange{..}
      | irFirstIndex > maxIndex =
          throwString "firstIndex is over the actual buffer size"
      | irFirstIndex + irIndexCount > maxIndex =
          throwString "firstIndex + indexCount is over the actual buffer size"
      | otherwise =
          pure ir

    maxIndex = Buffer.aUsed (Model.iIndices model)

-- * Unchecked implementations

-- | Common unchecked part for pos/attrs
unsafeIndexedRanges
  :: (MonadUnliftIO io, Model.HasVertexBuffers instances, Foldable t)
  => Bool
  -> Vk.CommandBuffer
  -> Model.Indexed storage pos attrs
  -> instances
  -> t Model.IndexRange
  -> io ()
unsafeIndexedRanges drawAttrs cmd Model.Indexed{..} instances indexRanges =
  case toList indexRanges of
    [] ->
      pure ()

    _skip | instanceCount < 1 ->
      pure ()

    someRanges -> liftIO do
      Vk.cmdBindVertexBuffers cmd 0 vertexBuffers bufferOffsets
      Vk.cmdBindIndexBuffer cmd (Buffer.aBuffer iIndices) indexBufferOffset Vk.INDEX_TYPE_UINT32
      for_ someRanges \Model.IndexRange{..} ->
        Vk.cmdDrawIndexed cmd irIndexCount instanceCount irFirstIndex vertexOffset firstInstance

  where
    indexBufferOffset = 0
    vertexOffset = 0

    instanceCount = Model.getInstanceCount instances
    firstInstance = 0

    vertexBuffers = Vector.fromList $
      if drawAttrs then
        Buffer.aBuffer iPositions :
        Buffer.aBuffer iAttrs :
        Model.getVertexBuffers instances
      else
        Buffer.aBuffer iPositions :
        Model.getVertexBuffers instances

    bufferOffsets =
      Vector.replicate (Vector.length vertexBuffers) 0

-- | Instance/range zipped
unsafeIndexedParts
  :: (MonadUnliftIO io, Model.HasVertexBuffers instances, Foldable t)
  => Bool
  -> Vk.CommandBuffer
  -> Model.Indexed storage pos attrs
  -> instances
  -> Int
  -> t Model.IndexRange
  -> io ()
unsafeIndexedParts drawAttrs cmd Model.Indexed{..} instances startInstance parts =
  case drop startInstance (toList parts) of
    [] ->
      pure ()

    _skip | instanceCount < 1 ->
      pure ()

    someRanges -> liftIO do
      Vk.cmdBindVertexBuffers cmd 0 vertexBuffers bufferOffsets
      Vk.cmdBindIndexBuffer cmd (Buffer.aBuffer iIndices) indexBufferOffset Vk.INDEX_TYPE_UINT32
      for_ (zip [fromIntegral startInstance ..] someRanges) \(firstInstance, Model.IndexRange{..}) ->
        Vk.cmdDrawIndexed cmd irIndexCount instanceCount irFirstIndex vertexOffset firstInstance

  where
    indexBufferOffset = 0
    vertexOffset = 0

    instanceCount = 1 -- Model.getInstanceCount instances
    -- firstInstance = 0

    vertexBuffers = Vector.fromList $
      if drawAttrs then
        Buffer.aBuffer iPositions :
        Buffer.aBuffer iAttrs :
        Model.getVertexBuffers instances
      else
        Buffer.aBuffer iPositions :
        Model.getVertexBuffers instances

    bufferOffsets =
      Vector.replicate (Vector.length vertexBuffers) 0