vinyl-gl-0.2.1: src/Graphics/VinylGL/Vertex.hs
{-# LANGUAGE DataKinds, ScopedTypeVariables, TypeOperators, GADTs, BangPatterns,
FlexibleInstances, FlexibleContexts, KindSignatures, RankNTypes,
ConstraintKinds, CPP #-}
-- | Utilities for working with vertex buffer objects (VBOs) filled
-- with vertices represented as vinyl records.
module Graphics.VinylGL.Vertex (bufferVertices, bindVertices, reloadVertices,
deleteVertices, enableVertices, enableVertices',
enableVertexFields, ViableVertex,
BufferedVertices(..), fieldToVAD) where
import Control.Applicative
import Control.Arrow (second)
import Data.Foldable (Foldable, foldMap)
import Data.List (find)
import qualified Data.Map as M
import Data.Monoid (Sum(..))
import Data.Proxy (Proxy(..))
import qualified Data.Vector.Storable as V
import Data.Vinyl (PlainFieldRec, Implicit(..), Elem)
import Data.Vinyl.Universe((:::))
import Foreign.Ptr (plusPtr)
import Foreign.Storable
import GHC.TypeLits
import Graphics.GLUtil hiding (Elem, throwError)
import Graphics.Rendering.OpenGL (VertexArrayDescriptor(..), bindBuffer,
($=), BufferTarget(..))
import qualified Graphics.Rendering.OpenGL as GL
import Data.Vinyl.Reflect
import Graphics.VinylGL.Uniforms
-- | Representation of a VBO whose type describes the vertices.
newtype BufferedVertices (fields::[*]) =
BufferedVertices { getVertexBuffer :: GL.BufferObject }
-- | Load vertex data into a GPU-accessible buffer.
bufferVertices :: (Storable (PlainFieldRec rs),
BufferSource (v (PlainFieldRec rs)))
=> v (PlainFieldRec rs) -> IO (BufferedVertices rs)
bufferVertices = fmap BufferedVertices . fromSource ArrayBuffer
-- | Reload 'BufferedVertices' with a 'V.Vector' of new vertex data.
reloadVertices :: Storable (PlainFieldRec rs)
=> BufferedVertices rs -> V.Vector (PlainFieldRec rs) -> IO ()
reloadVertices b v = do bindBuffer ArrayBuffer $= Just (getVertexBuffer b)
replaceVector ArrayBuffer v
-- | Delete the object name associated with 'BufferedVertices'.
deleteVertices :: BufferedVertices a -> IO ()
deleteVertices = GL.deleteObjectNames . (:[]) . getVertexBuffer
-- | Bind previously-buffered vertex data.
bindVertices :: BufferedVertices a -> IO ()
bindVertices = (bindBuffer ArrayBuffer $=) . Just . getVertexBuffer
-- | Line up a shader's attribute inputs with a vertex record. This
-- maps vertex fields to GLSL attributes on the basis of record field
-- names on the Haskell side, and variable names on the GLSL side.
enableVertices :: forall f rs. ViableVertex (PlainFieldRec rs)
=> ShaderProgram -> f rs -> IO (Maybe String)
enableVertices s _ = enableAttribs s (Proxy::Proxy (PlainFieldRec rs))
-- | Behaves like 'enableVertices', but raises an exception if the
-- supplied vertex record does not include a field required by the
-- shader.
enableVertices' :: forall f rs. ViableVertex (PlainFieldRec rs)
=> ShaderProgram -> f rs -> IO ()
enableVertices' s _ = enableAttribs s (Proxy::Proxy (PlainFieldRec rs)) >>=
maybe (return ()) error
-- | Produce a 'GL.VertexArrayDescriptor' for a particular field of a
-- vertex record.
fieldToVAD :: forall sy v a r rs field proxy.
(r ~ ((sy :: Symbol) ::: v a), HasFieldNames (PlainFieldRec rs),
HasFieldSizes (PlainFieldRec rs), HasGLType a,
Storable (PlainFieldRec rs), Num (v a),
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707
KnownSymbol sy,
#else
SingI sy,
#endif
Foldable v, Implicit (Elem r rs)) =>
field r -> proxy (PlainFieldRec rs) -> GL.VertexArrayDescriptor a
fieldToVAD _ _ = GL.VertexArrayDescriptor dim
(glType (undefined::a))
(fromIntegral sz)
(offset0 `plusPtr` offset)
where sz = sizeOf (undefined::PlainFieldRec rs)
dim = getSum $ foldMap (const (Sum 1)) (0::v a)
Just offset = lookup n $
namesAndOffsets (undefined::PlainFieldRec rs)
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707
n = symbolVal (Proxy::Proxy sy)
#else
n = fromSing (sing::Sing sy)
#endif
-- | Constraint alias capturing the requirements of a vertex type.
type ViableVertex t = (HasFieldNames t, HasFieldSizes t, HasFieldDims t,
HasFieldGLTypes t, Storable t)
-- | Bind some of a shader's attribute inputs to a vertex record. This
-- is useful when the inputs of a shader are split across multiple
-- arrays.
enableVertexFields :: forall p rs. (ViableVertex (PlainFieldRec rs))
=> ShaderProgram -> p rs -> IO ()
enableVertexFields s _ = enableSomeAttribs s p >>= maybe (return ()) error
where p = Proxy :: Proxy (PlainFieldRec rs)
-- | Do not raise an error is some of a shader's inputs are not bound
-- by a vertex record.
enableSomeAttribs :: forall v. ViableVertex v
=> ShaderProgram -> Proxy v -> IO (Maybe String)
enableSomeAttribs s p = go $ fieldDescriptors (undefined::v)
where go [] = return Nothing
go (fd:fds) =
let n = fieldName fd
shaderAttribs = attribs s
in case M.lookup n shaderAttribs of
Nothing -> return (Just $ "Unexpected attribute " ++ n)
Just (_,t)
| fieldType fd == t -> do enableAttrib s n
setAttrib s n GL.ToFloat $
descriptorVAD p fd
go fds
| otherwise -> return . Just $ "Type mismatch in " ++ n
enableAttribs :: forall v. ViableVertex v
=> ShaderProgram -> Proxy v -> IO (Maybe String)
enableAttribs s p = go (map (second snd) $ M.assocs (attribs s))
where go [] = return Nothing
go ((n,t):ns) = case find ((== n) . fieldName) fs of
Nothing -> return (Just $ "GLSL expecting "++n)
Just fd
| fieldType fd == t ->
do enableAttrib s n
setAttrib s n GL.ToFloat $
descriptorVAD p fd
go ns
| otherwise -> return . Just $
"Type mismatch in "++n
fs = fieldDescriptors (undefined::v)
namesAndOffsets :: (HasFieldNames t, HasFieldSizes t) => t -> [(String,Int)]
namesAndOffsets x = zip (fieldNames x) (scanl (+) 0 $ fieldSizes x)
data FieldDescriptor = FieldDescriptor { fieldName :: String
, fieldOffset :: Int
, fieldDim :: Int
, fieldType :: GL.VariableType }
deriving Show
fieldDescriptors :: ViableVertex t => t -> [FieldDescriptor]
fieldDescriptors x = getZipList $
FieldDescriptor <$> zl (fieldNames x)
<*> zl (scanl (+) 0 $ fieldSizes x)
<*> zl (fieldDims x)
<*> zl (fieldGLTypes x)
where zl = ZipList
descriptorVAD :: forall t a. Storable t
=> Proxy t -> FieldDescriptor -> VertexArrayDescriptor a
descriptorVAD _ fd = VertexArrayDescriptor (fromIntegral $ fieldDim fd)
(variableDataType $ fieldType fd)
(fromIntegral $
sizeOf (undefined::t))
(offset0 `plusPtr` fieldOffset fd)