flaw-graphics/Flaw/Graphics/Program.hs

Summary

Maintainability
Test Coverage
{-|
Module: Flaw.Graphics.Program
Description: Shader program support.
License: MIT
-}

{-# LANGUAGE TemplateHaskell #-}

module Flaw.Graphics.Program
  ( Program
  , OfScalarType(..)
  , OfAttributeType(..)
  , AttributeFormat(..)
  , AttributeType(..)
  , Normalization(..)
  , Node
  , cnst
  , constf, const2f, const3f, const4f
  , cvec11, cvec111, cvec12, cvec21, cvec1111, cvec112, cvec121, cvec211, cvec22, cvec13, cvec31
  , cast
  , attribute
  , attributeWithType
  , UniformBufferSlot
  , UniformStorage
  , uniformBufferSlot
  , uniform
  , uniformArray
  , createUniformStorage
  , setUniform
  , renderUniform
  , renderIndexedUniform
  , renderUploadUniformStorage
  , renderUniformStorage
  , sampler
  , sampler1Df, sampler1D2f, sampler1D3f, sampler1D4f
  , sampler2Df, sampler2D2f, sampler2D3f, sampler2D4f
  , sampler3Df, sampler3D2f, sampler3D3f, sampler3D4f
  , samplerCubef, samplerCube2f, samplerCube3f, samplerCube4f
  , sample, sampleOffset, sampleLod, sampleLodOffset, sampleBias, sampleBiasOffset, sampleGrad, sampleGradOffset
  , temp
  , rasterize
  , colorTarget
  , dualColorTarget
  , depthTarget
  , (!)
  , min_, max_
  , clamp, lerp
  , equal_ , less_, lessEqual_, greater_, greaterEqual_, if_
  , ddx, ddy
  , floor_
  , instanceId
  , invSqrt
  , screenToTexture
  , normalizeSampledDepth
  , fragCoord
  ) where

import Control.Monad.Reader
import qualified Data.ByteString.Unsafe as B
import Data.Char
import Data.Int
import Data.IORef
import Data.Word
import Foreign.ForeignPtr
import Foreign.ForeignPtr.Unsafe
import Foreign.Ptr
import Foreign.Storable
import Language.Haskell.TH

import Flaw.Graphics
import Flaw.Graphics.Program.Internal
import Flaw.Math

cnst :: OfValueType a => a -> Node a
cnst value = ConstNode (valueType value) value

-- | Helper method to know return value.
withUndefined :: (a -> Node a) -> Node a
withUndefined q = q undefined

-- | Helper method to know return value in monad.
withUndefinedM :: (a -> m (Node a)) -> m (Node a)
withUndefinedM q = q undefined

-- | Create vector as a combination of scalars/vectors.
fmap concat $ forM
  [ [1, 1]
  , [1, 1, 1], [1, 2], [2, 1]
  , [1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [2, 2], [1, 3], [3, 1]]
  $ \cs -> do
  ps <- forM (zip cs [1..]) $ \(_c, i) -> newName ['p', intToDigit i]
  tvA <- newName "a"
  u <- newName "u"
  let
    funName = mkName $ "cvec" ++ map intToDigit cs
    vecType n = appT (conT $ mkName $ "Vec" ++ [intToDigit n]) (varT tvA)
    argType n = [t| Node $(if n > 1 then vecType n else varT tvA) |]
    funType = forallT [PlainTV tvA] (sequence [ [t| OfScalarType $(varT tvA) |], [t| Vectorized $(varT tvA) |] ]) $
      foldr (\a b -> [t| $a -> $b |]) [t| Node $(vecType $ sum cs) |] $ map argType cs
    construction = foldl appE (conE $ mkName $ "Combine" ++ [intToDigit $ length cs] ++ "VecNode") $
      (map (\a -> [| nodeValueType $(varE a) |]) ps) ++ [ [| valueType $(varE u) |] ] ++ (map varE ps)
  sequence
    [ sigD funName funType
    , funD funName [clause (map varP ps) (normalB [| withUndefined $ \ $(varP u) -> $construction |]) []]
    ]

-- | Cast value to other type.
cast :: (OfValueType a, OfValueType b) => Node a -> Node b
cast a = withUndefined $ \u -> CastNode (nodeValueType a) (valueType u) a

-- | Define vertex attribute using typed format.
attribute :: OfAttributeType a
  => Int -- ^ Slot.
  -> Int -- ^ Offset.
  -> Int -- ^ Divisor.
  -> AttributeFormat a -- ^ Format.
  -> Program (Node a)
attribute slot offset divisor format = attributeWithType slot offset divisor $ attributeFormatToType format

-- | Define vertex attribute using untyped 'AttributeType'.
attributeWithType :: OfAttributeType a
  => Int -- ^ Slot.
  -> Int -- ^ Offset.
  -> Int -- ^ Divisor.
  -> AttributeType -- ^ Attribute type.
  -> Program (Node a)
attributeWithType slot offset divisor at = withUndefinedM $ \u -> withState $ \state@State
  { stateStage = stage
  } -> do
  if stage /= VertexStage then fail "attribute can only be defined in vertex program"
  else return ()
  tempInternal (AttributeNode Attribute
    { attributeSlot = slot
    , attributeOffset = offset
    , attributeDivisor = divisor
    , attributeType = at
    , attributeValueType = valueType u
    }) state

data UniformBufferSlot = UniformBufferSlot
  { uniformBufferSlotIndex :: Int
  , uniformBufferSlotSizeRef :: IORef Int
  }

-- | Helper object for uniform buffer.
data UniformStorage d = UniformStorage
  { uniformStorageSlot :: Int
  , uniformStorageBufferId :: UniformBufferId d
  , uniformStorageBytes :: ForeignPtr ()
  , uniformStorageSize :: Int
  }

uniformBufferSlot :: Int -> IO UniformBufferSlot
uniformBufferSlot slot = do
  sizeRef <- newIORef 0
  return UniformBufferSlot
    { uniformBufferSlotIndex = slot
    , uniformBufferSlotSizeRef = sizeRef
    }

-- | Modified alignment calculation for shaders.
-- Standard math types return alignment only for their inner components, so:
-- > alignment (undefined :: Float3) = alignment (undefined :: Float)
-- This function calculates more realistic alignment for shader programs.
shaderAlignment :: Storable a => a -> Int
shaderAlignment u = f (sizeOf u) (sizeOf (undefined :: Float)) where
  f n s
    | n <= s || s >= (sizeOf (undefined :: Float4)) = s
    | otherwise = f n (s * 2)

uniform :: (OfValueType a, Storable a) => UniformBufferSlot -> IO (Node a)
uniform UniformBufferSlot
  { uniformBufferSlotIndex = slot
  , uniformBufferSlotSizeRef = sizeRef
  } = withUndefinedM $ \u -> do
  bufferSize <- readIORef sizeRef
  let align = shaderAlignment u
  let alignedBufferSize = ((bufferSize + align - 1) `quot` align) * align
  writeIORef sizeRef $ alignedBufferSize + sizeOf u
  return $ UniformNode Uniform
    { uniformSlot = slot
    , uniformOffset = alignedBufferSize
    , uniformSize = 0
    , uniformType = valueType u
    }

uniformArray
  :: (OfValueType a, Storable a)
  => Int -- ^ size
  -> UniformBufferSlot -- ^ slot
  -> IO (Node [a])
uniformArray size UniformBufferSlot
  { uniformBufferSlotIndex = slot
  , uniformBufferSlotSizeRef = sizeRef
  } = wu func where
  wu :: (a -> IO (Node [a])) -> IO (Node [a])
  wu f = f undefined
  func u = do
    bufferSize <- readIORef sizeRef
    let align = shaderAlignment u
    let alignedBufferSize = ((bufferSize + align - 1) `quot` align) * align
    writeIORef sizeRef $ alignedBufferSize + (sizeOf u) * size
    return $ UniformNode Uniform
      { uniformSlot = slot
      , uniformOffset = alignedBufferSize
      , uniformSize = size
      , uniformType = valueType u
      }

createUniformStorage :: Device d => d -> UniformBufferSlot -> IO (UniformStorage d, IO ())
createUniformStorage device UniformBufferSlot
  { uniformBufferSlotIndex = slot
  , uniformBufferSlotSizeRef = sizeRef
  } = do
  size <- readIORef sizeRef
  -- align just in case
  let alignedSize = ((size + 15) `quot` 16) * 16
  (uniformBuffer, release) <- createUniformBuffer device alignedSize
  bytes <- mallocForeignPtrBytes alignedSize
  return (UniformStorage
    { uniformStorageSlot = slot
    , uniformStorageBufferId = uniformBuffer
    , uniformStorageBytes = bytes
    , uniformStorageSize = alignedSize
    }, release)

setUniform :: (OfValueType a, Storable a) => UniformStorage d -> Node a -> a -> IO ()
setUniform UniformStorage
  { uniformStorageBytes = bytes
  } (UniformNode Uniform
  { uniformOffset = offset
  }) value = do
  withForeignPtr bytes $ \ptr -> do
    pokeByteOff ptr offset value
setUniform _ _ _ = undefined

renderUniform :: (OfValueType a, Storable a) => UniformStorage d -> Node a -> a -> Render c ()
renderUniform uniformStorage node value = liftIO $ setUniform uniformStorage node value

setIndexedUniform :: (OfValueType a, Storable a) => UniformStorage d -> Node [a] -> Int -> a -> IO ()
setIndexedUniform UniformStorage
  { uniformStorageBytes = bytes
  } (UniformNode Uniform
  { uniformOffset = offset
  }) i value = do
  withForeignPtr bytes $ \ptr -> do
    pokeElemOff (ptr `plusPtr` offset) i value
setIndexedUniform _ _ _ _ = undefined

renderIndexedUniform :: (OfValueType a, Storable a) => UniformStorage d -> Node [a] -> Int -> a -> Render c ()
renderIndexedUniform uniformStorage node i value = liftIO $ setIndexedUniform uniformStorage node i value

renderUploadUniformStorage :: Context c d => UniformStorage d -> Render c ()
renderUploadUniformStorage UniformStorage
  { uniformStorageBufferId = uniformBuffer
  , uniformStorageBytes = bytes
  , uniformStorageSize = size
  } = do
  bs <- liftIO $ B.unsafePackCStringLen (castPtr $ unsafeForeignPtrToPtr bytes, size)
  renderUploadUniformBuffer uniformBuffer bs
  liftIO $ touchForeignPtr bytes

renderUniformStorage :: Context c d => UniformStorage d -> Render c ()
renderUniformStorage UniformStorage
  { uniformStorageSlot = slot
  , uniformStorageBufferId = uniformBuffer
  } = renderUniformBuffer slot uniformBuffer

sampler :: (OfValueType s, OfValueType c) => Int -> SamplerDimension -> SamplerNode s c
sampler slot dimension = withUndefined2 f where
  f s c = SamplerNode Sampler
    { samplerSlot = slot
    , samplerDimension = dimension
    , samplerSampleType = valueType s
    , samplerCoordsType = valueType c
    }
  withUndefined2 :: (s -> c -> SamplerNode s c) -> SamplerNode s c
  withUndefined2 q = q undefined undefined

sample :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node s
sample s c = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Nothing
  , sampleNodeLod = SampleNodeAutoLod
  }

sampleOffset :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node (v Int32) -> Node s
sampleOffset s c o = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Just o
  , sampleNodeLod = SampleNodeAutoLod
  }

sampleLod :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node c -> Node s
sampleLod s c l = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Nothing
  , sampleNodeLod = SampleNodeLod l
  }

sampleLodOffset :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node c -> Node (v Int32) -> Node s
sampleLodOffset s c l o = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Just o
  , sampleNodeLod = SampleNodeLod l
  }

sampleBias :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node c -> Node s
sampleBias s c b = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Nothing
  , sampleNodeLod = SampleNodeBiasLod b
  }

sampleBiasOffset :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node c -> Node (v Int32) -> Node s
sampleBiasOffset s c b o = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Just o
  , sampleNodeLod = SampleNodeBiasLod b
  }

sampleGrad :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node (v c) -> Node (v c) -> Node s
sampleGrad s c gx gy = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Nothing
  , sampleNodeLod = SampleNodeGradLod gx gy
  }

sampleGradOffset :: (OfVectorType (v c), OfVectorType (v Int32)) => SamplerNode s (v c) -> Node (v c) -> Node (v c) -> Node (v c) -> Node (v Int32) -> Node s
sampleGradOffset s c gx gy o = SampleNode
  { sampleNodeSamplerNode = s
  , sampleNodeCoordsNode = c
  , sampleNodeOffsetNode = Just o
  , sampleNodeLod = SampleNodeGradLod gx gy
  }

withState :: (State -> IO (State, a)) -> Program a
withState f = do
  stateVar <- ask
  liftIO $ do
    state <- readIORef stateVar
    (newState, result) <- f state
    writeIORef stateVar newState
    return result

temp :: OfValueType a => Node a -> Program (Node a)
temp = withState . tempInternal

tempInternal :: OfValueType a => Node a -> State -> IO (State, Node a)
tempInternal node state@State
  { stateStage = stage
  , stateTemps = temps
  , stateTempsCount = tempsCount
  } = do
  if stage == EndStage then fail "failed to add temp after end of the program"
  else return ()
  return (state
    { stateTemps = (Temp
      { tempIndex = tempsCount
      , tempNode = node
      , tempStage = stage
      , tempType = nodeValueType node
      }) : temps
    , stateTempsCount = tempsCount + 1
    }, TempNode tempsCount)

rasterize :: Node Float4 -> Program () -> Program ()
rasterize positionNode pixelProgram = withState $ \state@State
  { stateStage = stage
  , stateTargets = targets
  } -> do
  if stage /= VertexStage then fail $ show ("wrong stage to add pixel program", stage)
  else return ()
  let positionTarget = PositionTarget positionNode
  pixelStateVar <- newIORef state
    { stateStage = PixelStage
    , stateTargets = positionTarget : targets
    }
  runReaderT pixelProgram pixelStateVar
  pixelState <- readIORef pixelStateVar
  return (pixelState
    { stateStage = EndStage
    }, ())

colorTarget :: Int -> Node Float4 -> Program ()
colorTarget i colorNode = withState $ \state@State
  { stateStage = stage
  , stateTargets = targets
  } -> do
  if stage /= PixelStage then fail "colorTarget can be used only in pixel program"
  else return ()
  let target = ColorTarget i colorNode
  return (state
    { stateTargets = targets ++ [target]
    }, ())

dualColorTarget :: Node Float4 -> Node Float4 -> Program ()
dualColorTarget colorNode1 colorNode2 = withState $ \state@State
  { stateStage = stage
  , stateTargets = targets
  } -> do
  if stage /= PixelStage then fail "dualColorTarget can be used only in pixel program"
  else return ()
  let target = DualColorTarget colorNode1 colorNode2
  return (state
    { stateTargets = target : targets
    }, ())

depthTarget :: Node Float -> Program ()
depthTarget depthNode = withState $ \state@State
  { stateStage = stage
  , stateTargets = targets
  } -> do
  if stage /= PixelStage then fail "depthTarget can be used only in pixel program"
  else return ()
  let target = DepthTarget depthNode
  return (state
    { stateTargets = target : targets
    }, ())

constf :: Float -> Node Float
constf = cnst
const2f :: Float2 -> Node Float2
const2f = cnst
const3f :: Float3 -> Node Float3
const3f = cnst
const4f :: Float4 -> Node Float4
const4f = cnst

sampler1Df :: Int -> SamplerNode Float Float
sampler1Df slot = sampler slot Sampler1D
sampler1D2f :: Int -> SamplerNode Float2 Float
sampler1D2f slot = sampler slot Sampler1D
sampler1D3f :: Int -> SamplerNode Float3 Float
sampler1D3f slot = sampler slot Sampler1D
sampler1D4f :: Int -> SamplerNode Float4 Float
sampler1D4f slot = sampler slot Sampler1D

sampler2Df :: Int -> SamplerNode Float Float2
sampler2Df slot = sampler slot Sampler2D
sampler2D2f :: Int -> SamplerNode Float2 Float2
sampler2D2f slot = sampler slot Sampler2D
sampler2D3f :: Int -> SamplerNode Float3 Float2
sampler2D3f slot = sampler slot Sampler2D
sampler2D4f :: Int -> SamplerNode Float4 Float2
sampler2D4f slot = sampler slot Sampler2D

sampler3Df :: Int -> SamplerNode Float Float3
sampler3Df slot = sampler slot Sampler3D
sampler3D2f :: Int -> SamplerNode Float2 Float3
sampler3D2f slot = sampler slot Sampler3D
sampler3D3f :: Int -> SamplerNode Float3 Float3
sampler3D3f slot = sampler slot Sampler3D
sampler3D4f :: Int -> SamplerNode Float4 Float3
sampler3D4f slot = sampler slot Sampler3D

samplerCubef :: Int -> SamplerNode Float Float3
samplerCubef slot = sampler slot SamplerCube
samplerCube2f :: Int -> SamplerNode Float2 Float3
samplerCube2f slot = sampler slot SamplerCube
samplerCube3f :: Int -> SamplerNode Float3 Float3
samplerCube3f slot = sampler slot SamplerCube
samplerCube4f :: Int -> SamplerNode Float4 Float3
samplerCube4f slot = sampler slot SamplerCube

(!) :: (OfValueType a, OfValueType b, Integral b) => Node [a] -> Node b -> Node a
a ! b = IndexNode (nodeArrayValueType a) (nodeValueType b) a b

min_ :: OfValueType a => Node a -> Node a -> Node a
min_ a b = MinNode (nodeValueType a) a b

max_ :: OfValueType a => Node a -> Node a -> Node a
max_ a b = MaxNode (nodeValueType a) a b

clamp :: OfValueType a => Node a -> Node a -> Node a -> Node a
clamp a b c = ClampNode (nodeValueType a) a b c

lerp :: OfValueType a => Node a -> Node a -> Node a -> Node a
lerp a b c = LerpNode (nodeValueType a) a b c

equal_ :: OfValueType a => Node a -> Node a -> Node Bool
equal_ a b = EqualNode (nodeValueType a) a b

less_ :: OfValueType a => Node a -> Node a -> Node Bool
less_ a b = LessNode (nodeValueType a) a b

lessEqual_ :: OfValueType a => Node a -> Node a -> Node Bool
lessEqual_ a b = LessEqualNode (nodeValueType a) a b

greater_ :: OfValueType a => Node a -> Node a -> Node Bool
greater_ = flip less_

greaterEqual_ :: OfValueType a => Node a -> Node a -> Node Bool
greaterEqual_ = flip lessEqual_

if_ :: OfValueType a => Node Bool -> Node a -> Node a -> Node a
if_ c a b = IfNode (nodeValueType a) c a b

ddx :: OfValueType a => Node a -> Node a
ddx a = DdxNode (nodeValueType a) a

ddy :: OfValueType a => Node a -> Node a
ddy a = DdyNode (nodeValueType a) a

floor_ :: OfValueType a => Node a -> Node a
floor_ a = FloorNode (nodeValueType a) a

instanceId :: Node Word32
instanceId = InstanceIdNode

invSqrt :: (OfValueType a, Floating a) => Node a -> Node a
invSqrt a = InvSqrtNode (nodeValueType a) a

screenToTexture :: OfValueType a => Node a -> Node a
screenToTexture a = ScreenToTextureNode (nodeValueType a) a

normalizeSampledDepth :: Node Float -> Node Float
normalizeSampledDepth = NormalizeSampledDepthNode

fragCoord :: Node Float4
fragCoord = FragCoordNode