packages feed

keid-render-basic-0.1.9.0: src/Render/Unlit/Line2d/Model.hs

{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-}

{-# LANGUAGE OverloadedLists #-}

module Render.Unlit.Line2d.Model
  ( Segment

  , Vertex
  , createVertices
  , verticesRoundRound
  , Points

  , point
  , InstanceAttrs(..)

  , Buffer
  , Observer
  , Buffer.observeCoherentResize_

  , Batches(..)
  , BatchObserver
  , newBatchObserver
  , observeCoherentBatches
  ) where

import RIO

import Control.Monad.Trans.Resource qualified as Resource
import Data.Type.Equality (type (~))
import Data.Vector.Generic qualified as Generic
import Foreign.Storable.Generic (GStorable)
import Geomancy (Vec2, Vec4, vec3)
import Geomancy.Vec3 qualified as Vec3
import RIO.Vector qualified as Vector
import RIO.Vector.Storable qualified as Storable
import Vulkan.Core10 qualified as Vk
import Vulkan.NamedType ((:::))

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

type Segment = Buffer.Allocated 'Buffer.Staged Vec3.Packed
type Vertex = Model.Vertex3d ()

createVertices
  :: MonadVulkan env m
  => Maybe Text
  -> Queues Vk.CommandPool
  -> Float
  -> m Segment
createVertices label pools resolution = do
  Buffer.createStaged
    label
    pools
    Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT
    (Storable.length vertices)
    vertices
  where
    vertices = verticesRoundRound resolution

{- |
  Generate mesh for the round joints / round caps special case.

  With a bit of vertex shader code it allows drawing a batch of
  smooth lines in one call.
-}
verticesRoundRound :: Float -> Storable.Vector Vec3.Packed
verticesRoundRound resolution =
  Storable.fromList . map Vec3.Packed $
    mconcat
      [ segment
      , leftSemi
      , rightSemi
      ]
  where
    segment =
      [ vec3 0 (-0.5) 0
      , vec3 0 (-0.5) 1
      , vec3 0   0.5  1
      , vec3 0 (-0.5) 0
      , vec3 0   0.5  1
      , vec3 0   0.5  0
      ]

    leftSemi = do
      step <- [0..resolution]
      let
        theta0 = pi / 2 + (step + 0) * pi / resolution
        theta1 = pi / 2 + (step + 1) * pi / resolution
        a = vec3 0 0 0
        b = vec3 (cos theta0) (sin theta0) 0 * 0.5
        c = vec3 (cos theta1) (sin theta1) 0 * 0.5
      [ a, b, c ]

    rightSemi = do
      step <- [0..resolution]
      let
        theta0 = 3 * pi / 2 + (step + 0) * pi / resolution
        theta1 = 3 * pi / 2 + (step + 1) * pi / resolution
        a = vec3 0 0 0
        b = vec3 (cos theta0) (sin theta0) 2 * 0.5
        c = vec3 (cos theta1) (sin theta1) 2 * 0.5
      [ a, b, c ]

type Points = Storable.Vector InstanceAttrs

point :: Float -> Vec4 -> Vec2 -> InstanceAttrs
point width color position = InstanceAttrs
  { color    = color
  , position = Vec3.Packed (Vec3.fromVec2 position 0)
  , width    = width
  }

data InstanceAttrs = InstanceAttrs
  { color    :: Vec4

  , position :: Vec3.Packed
  , width    :: Float
  } deriving (Eq, Show, Generic)

-- XXX: okay, the layout matches
instance GStorable InstanceAttrs

instance HasVkFormat InstanceAttrs where
  getVkFormat =
    [ Vk.FORMAT_R32G32B32A32_SFLOAT -- color
    , Vk.FORMAT_R32G32B32A32_SFLOAT -- position+width
    ]

instance HasVertexInputBindings InstanceAttrs where
  vertexInputBindings =
    -- XXX: instance buffer is bound 2 times with a shift
    replicate 2 $ instanceFormat @InstanceAttrs

type Buffer s = Buffer.Allocated s InstanceAttrs

type Observer = Buffer.ObserverCoherent InstanceAttrs
type BatchObserver = Worker.ObserverIO (Buffer 'Buffer.Coherent, Ranges)
type Ranges = [(Word32, Word32)]

newtype Batches v a = Batches [v a]
  deriving (Eq, Show, Ord, Functor, Foldable, Traversable, Semigroup, Monoid)

newBatchObserver
  :: ( MonadVulkan env m
     , Resource.MonadResource m
     )
  => "initial size" ::: Int
  -> m BatchObserver
newBatchObserver initialSize = do
  initialBuffer <- Buffer.createCoherent
    (Just "Line2D")
    Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT
    initialSize
    mempty
  observer <- Worker.newObserverIO (initialBuffer, [])

  context <- ask
  void $! Resource.register do
    (currentBuffer, _ranges) <- Worker.readObservedIO observer
    Buffer.destroy context currentBuffer

  pure (observer :: BatchObserver)

observeCoherentBatches
  :: ( Worker.GetOutput output ~ Batches Storable.Vector InstanceAttrs
     , Worker.HasOutput output
     , MonadVulkan env m
     )
  => output
  -> BatchObserver
  -> m ()
observeCoherentBatches source observer =
  Worker.observeIO_ source observer \(buf, _ranges) (Batches batches) -> do
    let segments = filterSegments batches
    buf' <- Buffer.updateCoherentResize_ buf $
      Storable.concat segments
    pure (buf', toRanges segments)

filterSegments
  :: Generic.Vector v a
  => [v a]
  -> [v a]
filterSegments = filter \v ->
  Vector.length v >= 2

toRanges
  :: ( Generic.Vector v a
     , Integral i
     )
  => [v a]
  -> [(i, i)]
toRanges = collect . filter (not . Vector.null)
  where
    collect v = Vector.toList @Vector $ Vector.zip offsets sizes
      where
        sizes = Vector.fromList $ map (fromIntegral . Vector.length) v
        offsets = Vector.scanl' (+) 0 sizes