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 | ||
