{-# LANGUAGE DefaultSignatures, FlexibleInstances, FlexibleContexts,
             ScopedTypeVariables #-}
{-# OPTIONS_GHC -cpp -pgmPcpphs -optP--cpp -optP--hashes #-}
-- |Support for writing "Linear" types to uniform locations in
-- shader programs.
module Graphics.GLUtil.Linear (AsUniform(..)) where
import Foreign.Marshal.Array (withArray)
import Foreign.Marshal.Utils (with)
import Foreign.Ptr (Ptr, castPtr)
import Graphics.Rendering.OpenGL
import Graphics.Rendering.OpenGL.Raw.Core31
import Linear
import Unsafe.Coerce (unsafeCoerce)

-- | A type class for things we can write to uniform locations in
-- shader programs. We can provide instances of this class for types
-- from "Linear" without introducing orphan instances.
class AsUniform t where
  asUniform :: t -> UniformLocation -> IO ()
  default asUniform :: Uniform t => t -> UniformLocation -> IO ()
  asUniform x loc = uniform loc $= x

getUL :: UniformLocation -> GLint
getUL = unsafeCoerce

castVecComponent :: Ptr (t a) -> Ptr a
castVecComponent = castPtr

castMatComponent :: Ptr (t (f a)) -> Ptr a
castMatComponent = castPtr

instance AsUniform GLint where
  x `asUniform` loc = with x $ glUniform1iv (getUL loc) 1

instance AsUniform GLuint where
  x `asUniform` loc = with x $ glUniform1uiv (getUL loc) 1

instance AsUniform GLfloat where
  x `asUniform` loc = with x $ glUniform1fv (getUL loc) 1

instance AsUniform TextureUnit where
instance UniformComponent a => AsUniform (Index1 a) where
instance UniformComponent a => AsUniform (Color4 a) where
instance UniformComponent a => AsUniform (Color3 a) where
instance UniformComponent a => AsUniform (FogCoord1 a) where
instance UniformComponent a => AsUniform (Normal3 a) where
instance UniformComponent a => AsUniform (TexCoord4 a) where
instance UniformComponent a => AsUniform (TexCoord3 a) where
instance UniformComponent a => AsUniform (TexCoord2 a) where
instance UniformComponent a => AsUniform (TexCoord1 a) where
instance UniformComponent a => AsUniform (Vertex4 a) where
instance UniformComponent a => AsUniform (Vertex3 a) where
instance UniformComponent a => AsUniform (Vertex2 a) where

#define UNIFORMVEC_T(d,ht,glt) instance AsUniform (V ## d ht) where {v `asUniform` loc = with v $ glUniform##d##glt##v (getUL loc) 1 . castVecComponent}

#define UNIFORMVEC(d) UNIFORMVEC_T(d,GLint,i); UNIFORMVEC_T(d,GLuint,ui); UNIFORMVEC_T(d,GLfloat,f)

UNIFORMVEC(1)
UNIFORMVEC(2)
UNIFORMVEC(3)
UNIFORMVEC(4)

instance AsUniform (M22 GLfloat) where
  m `asUniform` loc = with m
                    $ glUniformMatrix2fv (getUL loc) 1 1 . castMatComponent

instance AsUniform (M33 GLfloat) where
  m `asUniform` loc = with m
                    $ glUniformMatrix3fv (getUL loc) 1 1 . castMatComponent

instance AsUniform (M44 GLfloat) where
  m `asUniform` loc = with m
                    $ glUniformMatrix4fv (getUL loc) 1 1 . castMatComponent

-- Support lists of vectors as uniform arrays of vectors.

#define UNIFORMARRAY_T(d,ht,glt) instance AsUniform [V##d ht] where {l `asUniform` loc = withArray l $ glUniform##d##glt##v (getUL loc) (fromIntegral $ length l) . castVecComponent}

#define UNIFORMARRAY(d) UNIFORMARRAY_T(d,GLint,i); UNIFORMARRAY_T(d,GLuint,ui); UNIFORMARRAY_T(d,GLfloat,f)

UNIFORMARRAY(1)
UNIFORMARRAY(2)
UNIFORMARRAY(3)
UNIFORMARRAY(4)