diff options
Diffstat (limited to 'Spear/GL.hs')
-rw-r--r-- | Spear/GL.hs | 672 |
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 @@ | |||
1 | module 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 | ) | ||
81 | where | ||
82 | |||
83 | import Spear.Assets.Image | ||
84 | import Spear.Game | ||
85 | import Spear.Math.Matrix3 (Matrix3) | ||
86 | import Spear.Math.Matrix4 (Matrix4) | ||
87 | import Spear.Math.Vector | ||
88 | |||
89 | import Control.Monad | ||
90 | import Control.Monad.Trans.Class | ||
91 | import Control.Monad.Trans.Error | ||
92 | import Control.Monad.Trans.State as State | ||
93 | import qualified Data.ByteString.Char8 as B | ||
94 | import Data.StateVar | ||
95 | import Foreign.C.String | ||
96 | import Foreign.Ptr | ||
97 | import Foreign.Storable | ||
98 | import Foreign.Marshal.Utils as Foreign (with) | ||
99 | import Foreign.Marshal.Alloc (alloca) | ||
100 | import Foreign.Marshal.Array (withArray) | ||
101 | import Foreign.Storable (peek) | ||
102 | import Graphics.Rendering.OpenGL.Raw.Core31 | ||
103 | import System.Directory (doesFileExist, getCurrentDirectory, setCurrentDirectory) | ||
104 | import System.IO (hPutStrLn, stderr) | ||
105 | import Unsafe.Coerce | ||
106 | |||
107 | -- | ||
108 | -- MANAGEMENT | ||
109 | -- | ||
110 | |||
111 | -- | A GLSL shader handle. | ||
112 | data GLSLShader = GLSLShader | ||
113 | { getShader :: GLuint | ||
114 | , getShaderKey :: Resource | ||
115 | } | ||
116 | |||
117 | instance ResourceClass GLSLShader where | ||
118 | getResource = getShaderKey | ||
119 | |||
120 | -- | A GLSL program handle. | ||
121 | data GLSLProgram = GLSLProgram | ||
122 | { getProgram :: GLuint | ||
123 | , getProgramKey :: Resource | ||
124 | } | ||
125 | |||
126 | instance ResourceClass GLSLProgram where | ||
127 | getResource = getProgramKey | ||
128 | |||
129 | -- | Supported shader types. | ||
130 | data ShaderType = VertexShader | FragmentShader deriving (Eq, Show) | ||
131 | |||
132 | toGLShader :: ShaderType -> GLenum | ||
133 | toGLShader VertexShader = gl_VERTEX_SHADER | ||
134 | toGLShader FragmentShader = gl_FRAGMENT_SHADER | ||
135 | |||
136 | -- | Apply the given function to the program's id. | ||
137 | withGLSLProgram :: GLSLProgram -> (GLuint -> a) -> a | ||
138 | withGLSLProgram prog f = f $ getProgram prog | ||
139 | |||
140 | -- | Get the location of the given uniform variable within the given program. | ||
141 | uniformLocation :: GLSLProgram -> String -> GettableStateVar GLint | ||
142 | uniformLocation 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. | ||
147 | fragLocation :: GLSLProgram -> String -> StateVar GLint | ||
148 | fragLocation 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. | ||
155 | attribLocation :: GLSLProgram -> String -> StateVar GLint | ||
156 | attribLocation 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. | ||
163 | newProgram :: [GLSLShader] -> Game s GLSLProgram | ||
164 | newProgram 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. | ||
176 | deleteProgram :: GLuint -> IO () | ||
177 | --deleteProgram = glDeleteProgram | ||
178 | deleteProgram prog = do | ||
179 | putStrLn $ "Deleting shader program " ++ show prog | ||
180 | glDeleteProgram prog | ||
181 | |||
182 | -- | Link the program. | ||
183 | linkProgram :: GLSLProgram -> Game s () | ||
184 | linkProgram 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. | ||
200 | useProgram :: GLSLProgram -> IO () | ||
201 | useProgram prog = glUseProgram $ getProgram prog | ||
202 | |||
203 | -- | Attach the given shader to the given program. | ||
204 | attachShader :: GLSLProgram -> GLSLShader -> IO () | ||
205 | attachShader prog shader = glAttachShader (getProgram prog) (getShader shader) | ||
206 | |||
207 | -- | Detach the given GLSL from the given program. | ||
208 | detachShader :: GLSLProgram -> GLSLShader -> IO () | ||
209 | detachShader 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'. | ||
215 | loadShader :: FilePath -> ShaderType -> Game s GLSLShader | ||
216 | loadShader file shaderType = do | ||
217 | shader <- newShader shaderType | ||
218 | loadSource file shader | ||
219 | compile file shader | ||
220 | return shader | ||
221 | |||
222 | -- | Create a new shader. | ||
223 | newShader :: ShaderType -> Game s GLSLShader | ||
224 | newShader 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. | ||
233 | deleteShader :: GLuint -> IO () | ||
234 | --deleteShader = glDeleteShader | ||
235 | deleteShader 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. | ||
241 | loadSource :: FilePath -> GLSLShader -> Game s () | ||
242 | loadSource 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. | ||
251 | shaderSource :: GLSLShader -> CString -> IO () | ||
252 | shaderSource shader str = | ||
253 | let ptr = unsafeCoerce str | ||
254 | in withArray [ptr] $ flip (glShaderSource (getShader shader) 1) nullPtr | ||
255 | |||
256 | -- | Compile the shader. | ||
257 | compile :: FilePath -> GLSLShader -> Game s () | ||
258 | compile 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 | |||
276 | type StatusCall = GLuint -> GLenum -> Ptr GLint -> IO () | ||
277 | type LogCall = GLuint -> GLsizei -> Ptr GLsizei -> Ptr GLchar -> IO () | ||
278 | |||
279 | getStatus :: StatusCall -> LogCall -> GLuint -> IO String | ||
280 | getStatus 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 | |||
288 | getErrorString :: LogCall -> GLuint -> GLsizei -> CString -> IO String | ||
289 | getErrorString 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. | ||
298 | readSource :: FilePath -> IO String | ||
299 | readSource = fmap B.unpack . readSource' | ||
300 | |||
301 | readSource' :: FilePath -> IO B.ByteString | ||
302 | readSource' 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. | ||
329 | uniformVec2 :: GLint -> Vector2 -> IO () | ||
330 | uniformVec2 loc v = glUniform2f loc x' y' | ||
331 | where x' = unsafeCoerce $ x v | ||
332 | y' = unsafeCoerce $ y v | ||
333 | |||
334 | -- | Load a 3D vector. | ||
335 | uniformVec3 :: GLint -> Vector3 -> IO () | ||
336 | uniformVec3 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. | ||
342 | uniformVec4 :: GLint -> Vector4 -> IO () | ||
343 | uniformVec4 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. | ||
350 | uniformMat3 :: GLint -> Matrix3 -> IO () | ||
351 | uniformMat3 loc mat = | ||
352 | with mat $ \ptrMat -> | ||
353 | glUniformMatrix3fv loc 1 (toEnum 0) (unsafeCoerce ptrMat) | ||
354 | |||
355 | -- | Load a 4x4 matrix. | ||
356 | uniformMat4 :: GLint -> Matrix4 -> IO () | ||
357 | uniformMat4 loc mat = | ||
358 | with mat $ \ptrMat -> | ||
359 | glUniformMatrix4fv loc 1 (toEnum 0) (unsafeCoerce ptrMat) | ||
360 | |||
361 | -- | Load a list of floats. | ||
362 | uniformfl :: GLint -> [GLfloat] -> IO () | ||
363 | uniformfl 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. | ||
371 | uniformil :: GLint -> [GLint] -> IO () | ||
372 | uniformil 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. | ||
384 | data VAO = VAO | ||
385 | { getVAO :: GLuint | ||
386 | , vaoKey :: Resource | ||
387 | } | ||
388 | |||
389 | instance ResourceClass VAO where | ||
390 | getResource = vaoKey | ||
391 | |||
392 | instance Eq VAO where | ||
393 | vao1 == vao2 = getVAO vao1 == getVAO vao2 | ||
394 | |||
395 | instance Ord VAO where | ||
396 | vao1 < vao2 = getVAO vao1 < getVAO vao2 | ||
397 | |||
398 | -- | Create a new vao. | ||
399 | newVAO :: Game s VAO | ||
400 | newVAO = 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. | ||
409 | deleteVAO :: GLuint -> IO () | ||
410 | deleteVAO vao = Foreign.with vao $ glDeleteVertexArrays 1 | ||
411 | |||
412 | -- | Bind the vao. | ||
413 | bindVAO :: VAO -> IO () | ||
414 | bindVAO = glBindVertexArray . getVAO | ||
415 | |||
416 | -- | Enable the given vertex attribute of the bound vao. | ||
417 | -- | ||
418 | -- See also 'bindVAO'. | ||
419 | enableVAOAttrib :: GLuint -- ^ Attribute index. | ||
420 | -> IO () | ||
421 | enableVAOAttrib = glEnableVertexAttribArray | ||
422 | |||
423 | -- | Bind the bound buffer to the given point. | ||
424 | attribVAOPointer | ||
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 () | ||
432 | attribVAOPointer idx ncomp dattype normalise stride off = | ||
433 | glVertexAttribPointer idx ncomp dattype (unsafeCoerce normalise) stride (unsafeCoerce off) | ||
434 | |||
435 | -- | Draw the bound vao. | ||
436 | drawArrays | ||
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 () | ||
441 | drawArrays mode first count = glDrawArrays mode (unsafeCoerce first) (unsafeCoerce count) | ||
442 | |||
443 | -- | Draw the bound vao, indexed mode. | ||
444 | drawElements | ||
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 () | ||
450 | drawElements mode count t idxs = glDrawElements mode (unsafeCoerce count) t idxs | ||
451 | |||
452 | -- | ||
453 | -- BUFFER | ||
454 | -- | ||
455 | |||
456 | -- | An OpenGL buffer. | ||
457 | data GLBuffer = GLBuffer | ||
458 | { getBuffer :: GLuint | ||
459 | , rkey :: Resource | ||
460 | } | ||
461 | |||
462 | instance ResourceClass GLBuffer where | ||
463 | getResource = rkey | ||
464 | |||
465 | -- | The type of target buffer. | ||
466 | data TargetBuffer | ||
467 | = ArrayBuffer | ||
468 | | ElementArrayBuffer | ||
469 | | PixelPackBuffer | ||
470 | | PixelUnpackBuffer | ||
471 | deriving (Eq, Show) | ||
472 | |||
473 | fromTarget :: TargetBuffer -> GLenum | ||
474 | fromTarget ArrayBuffer = gl_ARRAY_BUFFER | ||
475 | fromTarget ElementArrayBuffer = gl_ELEMENT_ARRAY_BUFFER | ||
476 | fromTarget PixelPackBuffer = gl_PIXEL_PACK_BUFFER | ||
477 | fromTarget PixelUnpackBuffer = gl_PIXEL_UNPACK_BUFFER | ||
478 | |||
479 | -- | A buffer usage. | ||
480 | data BufferUsage | ||
481 | = StreamDraw | ||
482 | | StreamRead | ||
483 | | StreamCopy | ||
484 | | StaticDraw | ||
485 | | StaticRead | ||
486 | | StaticCopy | ||
487 | | DynamicDraw | ||
488 | | DynamicRead | ||
489 | | DynamicCopy | ||
490 | deriving (Eq, Show) | ||
491 | |||
492 | fromUsage :: BufferUsage -> GLenum | ||
493 | fromUsage StreamDraw = gl_STREAM_DRAW | ||
494 | fromUsage StreamRead = gl_STREAM_READ | ||
495 | fromUsage StreamCopy = gl_STREAM_COPY | ||
496 | fromUsage StaticDraw = gl_STATIC_DRAW | ||
497 | fromUsage StaticRead = gl_STATIC_READ | ||
498 | fromUsage StaticCopy = gl_STATIC_COPY | ||
499 | fromUsage DynamicDraw = gl_DYNAMIC_DRAW | ||
500 | fromUsage DynamicRead = gl_DYNAMIC_READ | ||
501 | fromUsage DynamicCopy = gl_DYNAMIC_COPY | ||
502 | |||
503 | -- | Create a new buffer. | ||
504 | newBuffer :: Game s GLBuffer | ||
505 | newBuffer = 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. | ||
514 | deleteBuffer :: GLuint -> IO () | ||
515 | deleteBuffer buf = Foreign.with buf $ glDeleteBuffers 1 | ||
516 | |||
517 | -- | Bind the buffer. | ||
518 | bindBuffer :: GLBuffer -> TargetBuffer -> IO () | ||
519 | bindBuffer buf target = glBindBuffer (fromTarget target) $ getBuffer buf | ||
520 | |||
521 | -- | Set the buffer's data. | ||
522 | bufferData :: TargetBuffer | ||
523 | -> Int -- ^ Buffer size in bytes. | ||
524 | -> Ptr a | ||
525 | -> BufferUsage | ||
526 | -> IO () | ||
527 | bufferData target n bufData usage = glBufferData (fromTarget target) (unsafeCoerce n) bufData (fromUsage usage) | ||
528 | |||
529 | -- | Set the buffer's data. | ||
530 | bufferDatal :: 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 () | ||
536 | bufferDatal 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. | ||
540 | withGLBuffer :: GLBuffer -> (GLuint -> a) -> a | ||
541 | withGLBuffer buf f = f $ getBuffer buf | ||
542 | |||
543 | -- | ||
544 | -- TEXTURE | ||
545 | -- | ||
546 | |||
547 | -- | Represents a texture resource. | ||
548 | data Texture = Texture | ||
549 | { getTex :: GLuint | ||
550 | , texKey :: Resource | ||
551 | } | ||
552 | |||
553 | instance Eq Texture where | ||
554 | t1 == t2 = getTex t1 == getTex t2 | ||
555 | |||
556 | instance Ord Texture where | ||
557 | t1 < t2 = getTex t1 < getTex t2 | ||
558 | |||
559 | instance ResourceClass Texture where | ||
560 | getResource = texKey | ||
561 | |||
562 | -- | Create a new texture. | ||
563 | newTexture :: Game s Texture | ||
564 | newTexture = 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. | ||
573 | deleteTexture :: GLuint -> IO () | ||
574 | --deleteTexture tex = with tex $ glDeleteTextures 1 | ||
575 | deleteTexture tex = do | ||
576 | putStrLn $ "Releasing texture " ++ show tex | ||
577 | with tex $ glDeleteTextures 1 | ||
578 | |||
579 | -- | Load the 'Texture' specified by the given file. | ||
580 | loadTextureImage :: FilePath | ||
581 | -> GLenum -- ^ Texture's min filter. | ||
582 | -> GLenum -- ^ Texture's mag filter. | ||
583 | -> Game s Texture | ||
584 | loadTextureImage 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. | ||
601 | bindTexture :: Texture -> IO () | ||
602 | bindTexture = glBindTexture gl_TEXTURE_2D . getTex | ||
603 | |||
604 | -- | Load data onto the bound texture. | ||
605 | -- | ||
606 | -- See also 'bindTexture'. | ||
607 | loadTextureData :: 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 () | ||
617 | loadTextureData 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. | ||
629 | texParami :: GLenum -> GLenum -> SettableStateVar GLenum | ||
630 | texParami target param = makeSettableStateVar $ \val -> glTexParameteri target param $ fromIntegral . fromEnum $ val | ||
631 | |||
632 | -- | Set the bound texture's parameter to the given value. | ||
633 | texParamf :: GLenum -> GLenum -> SettableStateVar Float | ||
634 | texParamf target param = makeSettableStateVar $ \val -> glTexParameterf target param (unsafeCoerce val) | ||
635 | |||
636 | -- | Set the active texture unit. | ||
637 | activeTexture :: SettableStateVar GLenum | ||
638 | activeTexture = makeSettableStateVar glActiveTexture | ||
639 | |||
640 | -- | ||
641 | -- ERROR | ||
642 | -- | ||
643 | |||
644 | -- | Get the last OpenGL error. | ||
645 | getGLError :: IO (Maybe String) | ||
646 | getGLError = 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. | ||
657 | printGLError :: IO () | ||
658 | printGLError = 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. | ||
666 | assertGL :: Game s a -> String -> Game s a | ||
667 | assertGL 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 | ||