aboutsummaryrefslogtreecommitdiff
path: root/Spear/GL.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Spear/GL.hs')
-rw-r--r--Spear/GL.hs672
1 files changed, 672 insertions, 0 deletions
diff --git a/Spear/GL.hs b/Spear/GL.hs
new file mode 100644
index 0000000..05e439a
--- /dev/null
+++ b/Spear/GL.hs
@@ -0,0 +1,672 @@
1module Spear.GL
2(
3 -- * General Management
4 GLSLShader
5, GLSLProgram
6, ShaderType(..)
7 -- ** Programs
8, newProgram
9, linkProgram
10, useProgram
11, withGLSLProgram
12 -- ** Shaders
13, attachShader
14, detachShader
15, loadShader
16, newShader
17 -- *** Source loading
18, loadSource
19, shaderSource
20, readSource
21, compile
22 -- ** Locations
23, attribLocation
24, fragLocation
25, uniformLocation
26 -- ** Uniforms
27, uniformVec2
28, uniformVec3
29, uniformVec4
30, uniformMat3
31, uniformMat4
32, uniformfl
33, uniformil
34 -- ** Helper functions
35, ($=)
36, Data.StateVar.get
37 -- * VAOs
38, VAO
39 -- ** Creation and destruction
40, newVAO
41 -- ** Manipulation
42, bindVAO
43, enableVAOAttrib
44, attribVAOPointer
45 -- ** Rendering
46, drawArrays
47, drawElements
48 -- * Buffers
49, GLBuffer
50, TargetBuffer(..)
51, BufferUsage(..)
52 -- ** Creation and destruction
53, newBuffer
54 -- ** Manipulation
55, bindBuffer
56, bufferData
57, bufferDatal
58, withGLBuffer
59 -- * Textures
60, Texture
61, SettableStateVar
62, ($)
63 -- ** Creation and destruction
64, newTexture
65, loadTextureImage
66 -- ** Manipulation
67, bindTexture
68, loadTextureData
69, texParami
70, texParamf
71, activeTexture
72 -- * Error Handling
73, getGLError
74, printGLError
75, assertGL
76 -- * OpenGL
77, module Graphics.Rendering.OpenGL.Raw.Core31
78, Ptr
79, nullPtr
80)
81where
82
83import Spear.Assets.Image
84import Spear.Game
85import Spear.Math.Matrix3 (Matrix3)
86import Spear.Math.Matrix4 (Matrix4)
87import Spear.Math.Vector
88
89import Control.Monad
90import Control.Monad.Trans.Class
91import Control.Monad.Trans.Error
92import Control.Monad.Trans.State as State
93import qualified Data.ByteString.Char8 as B
94import Data.StateVar
95import Foreign.C.String
96import Foreign.Ptr
97import Foreign.Storable
98import Foreign.Marshal.Utils as Foreign (with)
99import Foreign.Marshal.Alloc (alloca)
100import Foreign.Marshal.Array (withArray)
101import Foreign.Storable (peek)
102import Graphics.Rendering.OpenGL.Raw.Core31
103import System.Directory (doesFileExist, getCurrentDirectory, setCurrentDirectory)
104import System.IO (hPutStrLn, stderr)
105import Unsafe.Coerce
106
107--
108-- MANAGEMENT
109--
110
111-- | A GLSL shader handle.
112data GLSLShader = GLSLShader
113 { getShader :: GLuint
114 , getShaderKey :: Resource
115 }
116
117instance ResourceClass GLSLShader where
118 getResource = getShaderKey
119
120-- | A GLSL program handle.
121data GLSLProgram = GLSLProgram
122 { getProgram :: GLuint
123 , getProgramKey :: Resource
124 }
125
126instance ResourceClass GLSLProgram where
127 getResource = getProgramKey
128
129-- | Supported shader types.
130data ShaderType = VertexShader | FragmentShader deriving (Eq, Show)
131
132toGLShader :: ShaderType -> GLenum
133toGLShader VertexShader = gl_VERTEX_SHADER
134toGLShader FragmentShader = gl_FRAGMENT_SHADER
135
136-- | Apply the given function to the program's id.
137withGLSLProgram :: GLSLProgram -> (GLuint -> a) -> a
138withGLSLProgram prog f = f $ getProgram prog
139
140-- | Get the location of the given uniform variable within the given program.
141uniformLocation :: GLSLProgram -> String -> GettableStateVar GLint
142uniformLocation prog var = makeGettableStateVar get
143 where
144 get = withCString var $ \str -> glGetUniformLocation (getProgram prog) (unsafeCoerce str)
145
146-- | Get or set the location of the given variable to a fragment shader colour number.
147fragLocation :: GLSLProgram -> String -> StateVar GLint
148fragLocation prog var = makeStateVar get set
149 where
150 get = withCString var $ \str -> glGetFragDataLocation (getProgram prog) (unsafeCoerce str)
151 set idx = withCString var $ \str ->
152 glBindFragDataLocation (getProgram prog) (unsafeCoerce idx) (unsafeCoerce str)
153
154-- | Get or set the location of the given attribute within the given program.
155attribLocation :: GLSLProgram -> String -> StateVar GLint
156attribLocation prog var = makeStateVar get set
157 where
158 get = withCString var $ \str -> glGetAttribLocation (getProgram prog) (unsafeCoerce str)
159 set idx = withCString var $ \str ->
160 glBindAttribLocation (getProgram prog) (unsafeCoerce idx) (unsafeCoerce str)
161
162-- | Create a new program.
163newProgram :: [GLSLShader] -> Game s GLSLProgram
164newProgram shaders = do
165 h <- gameIO glCreateProgram
166 when (h == 0) $ gameError "glCreateProgram failed"
167 rkey <- register $ deleteProgram h
168 let program = GLSLProgram h rkey
169
170 mapM_ (gameIO . attachShader program) shaders
171 linkProgram program
172
173 return program
174
175-- | Delete the program.
176deleteProgram :: GLuint -> IO ()
177--deleteProgram = glDeleteProgram
178deleteProgram prog = do
179 putStrLn $ "Deleting shader program " ++ show prog
180 glDeleteProgram prog
181
182-- | Link the program.
183linkProgram :: GLSLProgram -> Game s ()
184linkProgram prog = do
185 let h = getProgram prog
186 err <- gameIO $ do
187 glLinkProgram h
188 alloca $ \statptr -> do
189 glGetProgramiv h gl_LINK_STATUS statptr
190 status <- peek statptr
191 case status of
192 0 -> getStatus glGetProgramiv glGetProgramInfoLog h
193 _ -> return ""
194
195 case length err of
196 0 -> return ()
197 _ -> gameError err
198
199-- | Use the program.
200useProgram :: GLSLProgram -> IO ()
201useProgram prog = glUseProgram $ getProgram prog
202
203-- | Attach the given shader to the given program.
204attachShader :: GLSLProgram -> GLSLShader -> IO ()
205attachShader prog shader = glAttachShader (getProgram prog) (getShader shader)
206
207-- | Detach the given GLSL from the given program.
208detachShader :: GLSLProgram -> GLSLShader -> IO ()
209detachShader prog shader = glDetachShader (getProgram prog) (getShader shader)
210
211-- | Load a shader from the file specified by the given string.
212--
213-- This function creates a new shader. To load source code into an existing shader,
214-- see 'loadSource', 'shaderSource' and 'readSource'.
215loadShader :: FilePath -> ShaderType -> Game s GLSLShader
216loadShader file shaderType = do
217 shader <- newShader shaderType
218 loadSource file shader
219 compile file shader
220 return shader
221
222-- | Create a new shader.
223newShader :: ShaderType -> Game s GLSLShader
224newShader shaderType = do
225 h <- gameIO $ glCreateShader (toGLShader shaderType)
226 case h of
227 0 -> gameError "glCreateShader failed"
228 _ -> do
229 rkey <- register $ deleteShader h
230 return $ GLSLShader h rkey
231
232-- | Free the shader.
233deleteShader :: GLuint -> IO ()
234--deleteShader = glDeleteShader
235deleteShader shader = do
236 putStrLn $ "Deleting shader " ++ show shader
237 glDeleteShader shader
238
239-- | Load a shader source from the file specified by the given string
240-- into the shader.
241loadSource :: FilePath -> GLSLShader -> Game s ()
242loadSource file h = do
243 exists <- gameIO $ doesFileExist file
244 case exists of
245 False -> gameError "the specified shader file does not exist"
246 True -> gameIO $ do
247 code <- readSource file
248 withCString code $ shaderSource h
249
250-- | Load the given shader source into the shader.
251shaderSource :: GLSLShader -> CString -> IO ()
252shaderSource shader str =
253 let ptr = unsafeCoerce str
254 in withArray [ptr] $ flip (glShaderSource (getShader shader) 1) nullPtr
255
256-- | Compile the shader.
257compile :: FilePath -> GLSLShader -> Game s ()
258compile file shader = do
259 let h = getShader shader
260
261 -- Compile
262 gameIO $ glCompileShader h
263
264 -- Verify status
265 err <- gameIO $ alloca $ \statusPtr -> do
266 glGetShaderiv h gl_COMPILE_STATUS statusPtr
267 result <- peek statusPtr
268 case result of
269 0 -> getStatus glGetShaderiv glGetShaderInfoLog h
270 _ -> return ""
271
272 case length err of
273 0 -> return ()
274 _ -> gameError $ "Unable to compile shader " ++ file ++ ":\n" ++ err
275
276type StatusCall = GLuint -> GLenum -> Ptr GLint -> IO ()
277type LogCall = GLuint -> GLsizei -> Ptr GLsizei -> Ptr GLchar -> IO ()
278
279getStatus :: StatusCall -> LogCall -> GLuint -> IO String
280getStatus getStatus getLog h = do
281 alloca $ \lenPtr -> do
282 getStatus h gl_INFO_LOG_LENGTH lenPtr
283 len <- peek lenPtr
284 case len of
285 0 -> return ""
286 _ -> withCString (replicate (unsafeCoerce len) '\0') $ getErrorString getLog h (unsafeCoerce len)
287
288getErrorString :: LogCall -> GLuint -> GLsizei -> CString -> IO String
289getErrorString getLog h len str = do
290 let ptr = unsafeCoerce str
291 getLog h len nullPtr ptr
292 peekCString str
293
294-- | Load the shader source specified by the given file.
295--
296-- This function implements an #include mechanism, so the given file can
297-- refer to other files.
298readSource :: FilePath -> IO String
299readSource = fmap B.unpack . readSource'
300
301readSource' :: FilePath -> IO B.ByteString
302readSource' file = do
303 let includeB = B.pack "#include"
304 newLineB = B.pack "\n"
305 isInclude = ((==) includeB) . B.take 8
306 clean = B.dropWhile (\c -> c == ' ')
307 cleanInclude = B.filter (\c -> c /= '"') . B.dropWhile (\c -> c /= ' ')
308 toLines = B.splitWith (\c -> c == '\n' || c == '\r')
309 addNewLine s = if (B.last s /= '\n') then B.append s newLineB else s
310 parse = fmap B.concat . (fmap . fmap $ flip B.append newLineB) . sequence .
311 fmap (processLine . clean) . toLines
312 processLine l =
313 if isInclude l
314 then readSource' $ B.unpack . clean . cleanInclude $ l
315 else return l
316
317 contents <- B.readFile file
318
319 dir <- getCurrentDirectory
320 let dir' = dir ++ "/" ++ (reverse . dropWhile (\c -> c /= '/') . reverse) file
321
322 setCurrentDirectory dir'
323 code <- parse contents
324 setCurrentDirectory dir
325
326 return code
327
328-- | Load a 2D vector.
329uniformVec2 :: GLint -> Vector2 -> IO ()
330uniformVec2 loc v = glUniform2f loc x' y'
331 where x' = unsafeCoerce $ x v
332 y' = unsafeCoerce $ y v
333
334-- | Load a 3D vector.
335uniformVec3 :: GLint -> Vector3 -> IO ()
336uniformVec3 loc v = glUniform3f loc x' y' z'
337 where x' = unsafeCoerce $ x v
338 y' = unsafeCoerce $ y v
339 z' = unsafeCoerce $ z v
340
341-- | Load a 4D vector.
342uniformVec4 :: GLint -> Vector4 -> IO ()
343uniformVec4 loc v = glUniform4f loc x' y' z' w'
344 where x' = unsafeCoerce $ x v
345 y' = unsafeCoerce $ y v
346 z' = unsafeCoerce $ z v
347 w' = unsafeCoerce $ w v
348
349-- | Load a 3x3 matrix.
350uniformMat3 :: GLint -> Matrix3 -> IO ()
351uniformMat3 loc mat =
352 with mat $ \ptrMat ->
353 glUniformMatrix3fv loc 1 (toEnum 0) (unsafeCoerce ptrMat)
354
355-- | Load a 4x4 matrix.
356uniformMat4 :: GLint -> Matrix4 -> IO ()
357uniformMat4 loc mat =
358 with mat $ \ptrMat ->
359 glUniformMatrix4fv loc 1 (toEnum 0) (unsafeCoerce ptrMat)
360
361-- | Load a list of floats.
362uniformfl :: GLint -> [GLfloat] -> IO ()
363uniformfl loc vals = withArray vals $ \ptr ->
364 case length vals of
365 1 -> glUniform1fv loc 1 ptr
366 2 -> glUniform2fv loc 1 ptr
367 3 -> glUniform3fv loc 1 ptr
368 4 -> glUniform4fv loc 1 ptr
369
370-- | Load a list of integers.
371uniformil :: GLint -> [GLint] -> IO ()
372uniformil loc vals = withArray vals $ \ptr ->
373 case length vals of
374 1 -> glUniform1iv loc 1 ptr
375 2 -> glUniform2iv loc 1 ptr
376 3 -> glUniform3iv loc 1 ptr
377 4 -> glUniform4iv loc 1 ptr
378
379--
380-- VAOs
381--
382
383-- | A vertex array object.
384data VAO = VAO
385 { getVAO :: GLuint
386 , vaoKey :: Resource
387 }
388
389instance ResourceClass VAO where
390 getResource = vaoKey
391
392instance Eq VAO where
393 vao1 == vao2 = getVAO vao1 == getVAO vao2
394
395instance Ord VAO where
396 vao1 < vao2 = getVAO vao1 < getVAO vao2
397
398-- | Create a new vao.
399newVAO :: Game s VAO
400newVAO = do
401 h <- gameIO . alloca $ \ptr -> do
402 glGenVertexArrays 1 ptr
403 peek ptr
404
405 rkey <- register $ deleteVAO h
406 return $ VAO h rkey
407
408-- | Delete the vao.
409deleteVAO :: GLuint -> IO ()
410deleteVAO vao = Foreign.with vao $ glDeleteVertexArrays 1
411
412-- | Bind the vao.
413bindVAO :: VAO -> IO ()
414bindVAO = glBindVertexArray . getVAO
415
416-- | Enable the given vertex attribute of the bound vao.
417--
418-- See also 'bindVAO'.
419enableVAOAttrib :: GLuint -- ^ Attribute index.
420 -> IO ()
421enableVAOAttrib = glEnableVertexAttribArray
422
423-- | Bind the bound buffer to the given point.
424attribVAOPointer
425 :: GLuint -- ^ The index of the generic vertex attribute to be modified.
426 -> GLint -- ^ The number of components per generic vertex attribute. Must be 1,2,3,4.
427 -> GLenum -- ^ The data type of each component in the array.
428 -> Bool -- ^ Whether fixed-point data values should be normalized.
429 -> GLsizei -- ^ Stride. Byte offset between consecutive generic vertex attributes.
430 -> Int -- ^ Offset to the first component in the array.
431 -> IO ()
432attribVAOPointer idx ncomp dattype normalise stride off =
433 glVertexAttribPointer idx ncomp dattype (unsafeCoerce normalise) stride (unsafeCoerce off)
434
435-- | Draw the bound vao.
436drawArrays
437 :: GLenum -- ^ The kind of primitives to render.
438 -> Int -- ^ Starting index in the enabled arrays.
439 -> Int -- ^ The number of indices to be rendered.
440 -> IO ()
441drawArrays mode first count = glDrawArrays mode (unsafeCoerce first) (unsafeCoerce count)
442
443-- | Draw the bound vao, indexed mode.
444drawElements
445 :: GLenum -- ^ The kind of primitives to render.
446 -> Int -- ^ The number of elements to be rendered.
447 -> GLenum -- ^ The type of the index values. Must be one of gl_UNSIGNED_BYTE, gl_UNSIGNED_SHORT, or gl_UNSIGNED_INT.
448 -> Ptr a -- ^ Pointer to the location where indices are stored, or offset into the index array when there is a bound ElementArrayBuffer.
449 -> IO ()
450drawElements mode count t idxs = glDrawElements mode (unsafeCoerce count) t idxs
451
452--
453-- BUFFER
454--
455
456-- | An OpenGL buffer.
457data GLBuffer = GLBuffer
458 { getBuffer :: GLuint
459 , rkey :: Resource
460 }
461
462instance ResourceClass GLBuffer where
463 getResource = rkey
464
465-- | The type of target buffer.
466data TargetBuffer
467 = ArrayBuffer
468 | ElementArrayBuffer
469 | PixelPackBuffer
470 | PixelUnpackBuffer
471 deriving (Eq, Show)
472
473fromTarget :: TargetBuffer -> GLenum
474fromTarget ArrayBuffer = gl_ARRAY_BUFFER
475fromTarget ElementArrayBuffer = gl_ELEMENT_ARRAY_BUFFER
476fromTarget PixelPackBuffer = gl_PIXEL_PACK_BUFFER
477fromTarget PixelUnpackBuffer = gl_PIXEL_UNPACK_BUFFER
478
479-- | A buffer usage.
480data BufferUsage
481 = StreamDraw
482 | StreamRead
483 | StreamCopy
484 | StaticDraw
485 | StaticRead
486 | StaticCopy
487 | DynamicDraw
488 | DynamicRead
489 | DynamicCopy
490 deriving (Eq, Show)
491
492fromUsage :: BufferUsage -> GLenum
493fromUsage StreamDraw = gl_STREAM_DRAW
494fromUsage StreamRead = gl_STREAM_READ
495fromUsage StreamCopy = gl_STREAM_COPY
496fromUsage StaticDraw = gl_STATIC_DRAW
497fromUsage StaticRead = gl_STATIC_READ
498fromUsage StaticCopy = gl_STATIC_COPY
499fromUsage DynamicDraw = gl_DYNAMIC_DRAW
500fromUsage DynamicRead = gl_DYNAMIC_READ
501fromUsage DynamicCopy = gl_DYNAMIC_COPY
502
503-- | Create a new buffer.
504newBuffer :: Game s GLBuffer
505newBuffer = do
506 h <- gameIO . alloca $ \ptr -> do
507 glGenBuffers 1 ptr
508 peek ptr
509
510 rkey <- register $ deleteBuffer h
511 return $ GLBuffer h rkey
512
513-- | Delete the buffer.
514deleteBuffer :: GLuint -> IO ()
515deleteBuffer buf = Foreign.with buf $ glDeleteBuffers 1
516
517-- | Bind the buffer.
518bindBuffer :: GLBuffer -> TargetBuffer -> IO ()
519bindBuffer buf target = glBindBuffer (fromTarget target) $ getBuffer buf
520
521-- | Set the buffer's data.
522bufferData :: TargetBuffer
523 -> Int -- ^ Buffer size in bytes.
524 -> Ptr a
525 -> BufferUsage
526 -> IO ()
527bufferData target n bufData usage = glBufferData (fromTarget target) (unsafeCoerce n) bufData (fromUsage usage)
528
529-- | Set the buffer's data.
530bufferDatal :: Storable a
531 => TargetBuffer
532 -> Int -- ^ The size in bytes of an element in the data list.
533 -> [a] -- ^ The data list.
534 -> BufferUsage
535 -> IO ()
536bufferDatal target n bufData usage = withArray bufData $
537 \ptr -> bufferData target (n * length bufData) ptr usage
538
539-- | Apply the given function the buffer's id.
540withGLBuffer :: GLBuffer -> (GLuint -> a) -> a
541withGLBuffer buf f = f $ getBuffer buf
542
543--
544-- TEXTURE
545--
546
547-- | Represents a texture resource.
548data Texture = Texture
549 { getTex :: GLuint
550 , texKey :: Resource
551 }
552
553instance Eq Texture where
554 t1 == t2 = getTex t1 == getTex t2
555
556instance Ord Texture where
557 t1 < t2 = getTex t1 < getTex t2
558
559instance ResourceClass Texture where
560 getResource = texKey
561
562-- | Create a new texture.
563newTexture :: Game s Texture
564newTexture = do
565 tex <- gameIO . alloca $ \ptr -> do
566 glGenTextures 1 ptr
567 peek ptr
568
569 rkey <- register $ deleteTexture tex
570 return $ Texture tex rkey
571
572-- | Delete the texture.
573deleteTexture :: GLuint -> IO ()
574--deleteTexture tex = with tex $ glDeleteTextures 1
575deleteTexture tex = do
576 putStrLn $ "Releasing texture " ++ show tex
577 with tex $ glDeleteTextures 1
578
579-- | Load the 'Texture' specified by the given file.
580loadTextureImage :: FilePath
581 -> GLenum -- ^ Texture's min filter.
582 -> GLenum -- ^ Texture's mag filter.
583 -> Game s Texture
584loadTextureImage file minFilter magFilter = do
585 image <- loadImage file
586 tex <- newTexture
587 gameIO $ do
588 let w = width image
589 h = height image
590 pix = pixels image
591 rgb = fromIntegral . fromEnum $ gl_RGB
592
593 bindTexture tex
594 loadTextureData gl_TEXTURE_2D 0 rgb w h 0 gl_RGB gl_UNSIGNED_BYTE pix
595 texParami gl_TEXTURE_2D gl_TEXTURE_MIN_FILTER $= minFilter
596 texParami gl_TEXTURE_2D gl_TEXTURE_MAG_FILTER $= magFilter
597
598 return tex
599
600-- | Bind the texture.
601bindTexture :: Texture -> IO ()
602bindTexture = glBindTexture gl_TEXTURE_2D . getTex
603
604-- | Load data onto the bound texture.
605--
606-- See also 'bindTexture'.
607loadTextureData :: GLenum
608 -> Int -- ^ Target
609 -> Int -- ^ Level
610 -> Int -- ^ Internal format
611 -> Int -- ^ Width
612 -> Int -- ^ Height
613 -> GLenum -- ^ Border
614 -> GLenum -- ^ Texture type
615 -> Ptr a -- ^ Texture data
616 -> IO ()
617loadTextureData target level internalFormat width height border format texType texData = do
618 glTexImage2D target
619 (fromIntegral level)
620 (fromIntegral internalFormat)
621 (fromIntegral width)
622 (fromIntegral height)
623 (fromIntegral border)
624 (fromIntegral format)
625 texType
626 texData
627
628-- | Set the bound texture's parameter to the given value.
629texParami :: GLenum -> GLenum -> SettableStateVar GLenum
630texParami target param = makeSettableStateVar $ \val -> glTexParameteri target param $ fromIntegral . fromEnum $ val
631
632-- | Set the bound texture's parameter to the given value.
633texParamf :: GLenum -> GLenum -> SettableStateVar Float
634texParamf target param = makeSettableStateVar $ \val -> glTexParameterf target param (unsafeCoerce val)
635
636-- | Set the active texture unit.
637activeTexture :: SettableStateVar GLenum
638activeTexture = makeSettableStateVar glActiveTexture
639
640--
641-- ERROR
642--
643
644-- | Get the last OpenGL error.
645getGLError :: IO (Maybe String)
646getGLError = fmap translate glGetError
647 where
648 translate err
649 | err == gl_NO_ERROR = Nothing
650 | err == gl_INVALID_ENUM = Just "Invalid enum"
651 | err == gl_INVALID_VALUE = Just "Invalid value"
652 | err == gl_INVALID_OPERATION = Just "Invalid operation"
653 | err == gl_OUT_OF_MEMORY = Just "Out of memory"
654 | otherwise = Just "Unknown error"
655
656-- | Print the last OpenGL error.
657printGLError :: IO ()
658printGLError = getGLError >>= \err -> case err of
659 Nothing -> return ()
660 Just str -> hPutStrLn stderr str
661
662-- | Run the given setup action and check for OpenGL errors.
663--
664-- If an OpenGL error is produced, an exception is thrown containing
665-- the given string appended to the string describing the error.
666assertGL :: Game s a -> String -> Game s a
667assertGL action err = do
668 result <- action
669 status <- gameIO getGLError
670 case status of
671 Just str -> gameError $ "OpenGL error raised: " ++ err ++ "; " ++ str
672 Nothing -> return result