----------------------------------------------------------------------------- -- | -- Module : Graphics.OpenGLES.Core -- Copyright : (c) capsjac 2014 -- License : LGPL-3 (see the file LICENSE) -- -- The neat and easy to use wrapper for OpenGL EmbedSystems (ES). -- The wrapper is optimised for mobile and have small footprint. -- Assuming OpenGL ES 2.0 or any later version, however, also works -- with OpenGL 4.1/4.3+ on desktop. -- ----------------------------------------------------------------------------- {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} module Graphics.OpenGLES.Core ( GL, -- * Lifecycle forkGL, stopGL, --destroyGL, endFrameGL, runGL, --runGLRes withGL, resetDrawQueue, glLog, glReadLogs, glLogContents, flushCommandQ, finishCommands, -- * Draw Operation -- ** Clear Screen clear, ClearBufferMask, depthBuffer, stencilBuffer, colorBuffer, -- ** Draw glDraw, -- ** Draw Mode DrawMode, drawPoints, drawLines, drawLineLoop, drawLineStrip, drawTriangles, triangleStrip, triangleFan, -- ** Graphics State GraphicsState, -- ** Programmable Shader Shader, vertexShader, fragmentShader, pixelShader, computeShader, geometryShader, tessellationEvalS, tessellationCtrlS, Program, TransformFeedback(..), ProgramBinary, glCompile, glValidate, -- ** Uniform Variable Uniform, uniform, ($=), UnifVal, UniformAssignment, -- ** Vertex Attribute Array Attrib, attrib, normalized, divisor, (&=), VertexArray, glVA, ShaderAttribute, AttrStruct, SetVertexAttr, -- ** Constant Vertex Attribute constAttrib, -- ** Texture -- | See "Graphics.OpenGLES.Texture" -- ** Vertex Picker VertexPicker, takeFrom, takeFromInstanced, takeFromMany, VertexIx, byIndex, byIndexInstanced, byIndices, byIndexLimited, drawCallSequence ) where import Control.Applicative import Control.Monad import Control.Concurrent (forkOS, ThreadId, myThreadId, killThread) import Control.Concurrent.Chan import Control.Exception (catch, SomeException) import Control.Future import qualified Data.ByteString as B import Data.IORef import Data.Typeable import Foreign import Foreign.C.String (peekCString, peekCStringLen) import Graphics.OpenGLES.Base import Graphics.OpenGLES.Buffer import Graphics.OpenGLES.Env import Graphics.OpenGLES.Internal import Graphics.OpenGLES.Types -- * Initialization forkGL :: IO Bool -> GL () -> GL () -> IO ThreadId forkGL resumeGL suspendGL swapBuffers = forkOS $ do writeIORef drawOrExit (Just swapBuffers) resumeGL -- Note: implicit glFlush here putStrLn "bindEGL" -- glRestoreLostObjects let loop count = do putStrLn $ "start draw " ++ show count readChan drawQueue >>= id loop (count + 1) catch (loop 0) $ \(e :: SomeException) -> do glLog $ "Rendering thread terminated: " ++ show e suspendGL putStrLn "unbindEGL" writeIORef drawOrExit (Just (glLog "Fatal lifecycle bug")) stopGL :: IO () stopGL = do putStrLn "stopGL" writeIORef drawOrExit Nothing let waitGLThread = readIORef drawOrExit >>= \case Just _ -> nop Nothing -> waitGLThread waitGLThread writeIORef frameCounter 0 putStrLn "Rendering has stopped." --destroyGL :: IO () --destroyGL = runGL $ eglMakeCurrent Nothing, eglDestroyXXX ... endFrameGL :: IO () endFrameGL = withGL go >>= waitFuture >> nop where go = do readIORef drawOrExit >>= \case Just eglSwapBuffer -> do eglSwapBuffer modifyIORef frameCounter (+1) Nothing -> myThreadId >>= killThread runGL :: GL () -> IO () runGL io = writeChan drawQueue io --runGLRes :: GL () -> IO () --runGLRes io = forkOS withGL :: GL a -> IO (Future' a) withGL io = asyncIO $ \update -> runGL (io >>= update . Finished) -- | drawQueue may have drawcalls that use previous context, -- so make it sure they are removed from the queue. resetDrawQueue :: IO () resetDrawQueue = do empty <- isEmptyChan drawQueue when (not empty) (readChan drawQueue >> resetDrawQueue) glReadLogs :: IO [String] glReadLogs = do empty <- isEmptyChan errorQueue if empty then return [] else (:) <$> readChan errorQueue <*> glReadLogs glLogContents :: IO [String] glLogContents = getChanContents errorQueue flushCommandQ :: IO () flushCommandQ = runGL glFlush finishCommands :: IO () finishCommands = runGL glFinish -- | @return ()@ nop :: Monad m => m () nop = return () glFrameCount :: IO Int glFrameCount = readIORef frameCounter glFlipping :: IO Bool glFlipping = fmap odd glFrameCount -- * Drawing -- | -- > clear [] colorBuffer -- > clear [bindFramebuffer buf] (colorBuffer+depthBuffer) clear :: [GraphicsState] -> ClearBufferMask -> GL () clear gs (ClearBufferMask flags) = sequence gs >> glClear flags depthBuffer = ClearBufferMask 0x100 stencilBuffer = ClearBufferMask 0x400 colorBuffer = ClearBufferMask 0x4000 glDraw :: Typeable p => DrawMode -> Program p -> [GraphicsState] -> [UniformAssignment p] -> VertexArray p -> VertexPicker -> GL Bool glDraw (DrawMode mode) prog@(Program pobj _ _ _) setState unifs (VertexArray (vao, setVA)) (VertexPicker picker) = do glUseProgram =<< getObjId pobj sequence setState sequence unifs case extVAO of Nothing -> setVA Just (_, bind, _) -> getObjId vao >>= bind --glValidate prog picker mode -- | See "Graphics.OpenGLES.State" type GraphicsState = GL () -- ** Draw Mode drawPoints = DrawMode 0 drawLines = DrawMode 1 drawLineLoop = DrawMode 2 drawLineStrip = DrawMode 3 drawTriangles = DrawMode 4 triangleStrip = DrawMode 5 triangleFan = DrawMode 6 -- ** Programmable Shader vertexShader, fragmentShader, pixelShader, computeShader, geometryShader, tessellationEvalS, tessellationCtrlS :: GLName -> B.ByteString -> Shader vertexShader = Shader 0x8B31 fragmentShader = Shader 0x8B30 -- | Same as 'fragmentShader' pixelShader = fragmentShader -- | Compute shader requires /ES3.1+/ computeShader = Shader 0x91B9 -- | Geometry shader requires /GL_EXT_geometry_shader (ES3.1)/ geometryShader = Shader 0x8DD9 -- | Tessellation Shader requires /GL_EXT_tessellation_shader (ES3.1)/ tessellationEvalS = Shader 0x8E87 -- | Tessellation Shader requires /GL_EXT_tessellation_shader (ES3.1)/ tessellationCtrlS = Shader 0x8E88 glCompile :: Typeable p => TransformFeedback -> [Shader] -> (Program p -> Int -> String -> Maybe ProgramBinary -> GL ()) -> GL (Progress [String] (Program p)) glCompile tf shaders progressLogger = do glo <- newIORef undefined let prog = Program glo tf shaders ([],[]) loadProgram prog (progressLogger prog) -- | glValidateProgram checks to see whether the executables contained in -- program can execute given the current OpenGL state. glValidate :: Program p -> GL String glValidate prog = alloca $ \intptr -> do pid <- getObjId $ programGLO prog glValidateProgram pid glGetProgramiv pid c_info_log_length intptr len <- fmap fromIntegral $ peek intptr info <- allocaBytes len $ \buf -> do glGetProgramInfoLog pid (fromIntegral len) nullPtr buf peekCStringLen (buf, len-1) glLog $ "validateProgram: " ++ info return info -- ** Uniform Variable type UniformAssignment p = GL () uniform :: forall p a. (UnifVal a, Typeable p) => GLName -> IO (Uniform p a) uniform name = do desc <- lookupVarDesc typ case desc of Nothing -> return $ Uniform (-1, 0, nullPtr) Just (unifs, _) -> case lookup name unifs of Just unif -> validateType unif Nothing -> glLog errmsg >> return (Uniform (-1, 0, nullPtr)) where typ = typeRep (undefined :: Program p) errmsg = "Uniform not found: " ++ name ++ " (" ++ show typ ++ ")" validateType (loc, size, gltyp) = do -- Prevent drawtime allocation (it leaks, though) ptr <- mallocArray (fromIntegral size) :: IO (Ptr Float) return $ Uniform (loc, size, castPtr ptr) ($=) :: UnifVal a => Uniform p a -> a -> UniformAssignment p Uniform desc $= value = glUniform desc value -- ** Vertex Attribute -- normalized color `divisor` 1 &= buffer attrib :: forall p a. (ShaderAttribute a, Typeable p) => GLName -> IO (Attrib p a) attrib name = do desc <- lookupVarDesc typ case desc of Nothing -> return $ Attrib (-1, 0, 0, 0) Just (_, attrs) -> case lookup name attrs of Just attr -> validateType attr Nothing -> glLog errmsg >> return (Attrib (-1, 0, 0, 0)) where typ = typeRep (undefined :: Program p) errmsg = "Attribute not found: " ++ name ++ " (" ++ show typ ++ ")" validateType (loc, size, gltyp) = return $ Attrib (fromIntegral loc, size, 0, 0) normalized :: Attrib p a -> Attrib p a normalized (Attrib (i, s, 0, d)) = Attrib (i, s, 1, d) normalized _ = error "inapplicable use of 'normalized'" divisor :: Attrib p a -> Word32 -> Attrib p a divisor (Attrib (i, s, n, _)) d = Attrib (i, s, n, d) type SetVertexAttr p = GL () (&=) :: AttrStruct b a p => a -> Buffer b -> SetVertexAttr p attrib &= buf = do bindBuffer array_buffer buf glVertexAttribPtr attrib buf glVA :: [SetVertexAttr p] -> GL (VertexArray p) glVA attrs = do let setVA = sequence_ attrs glo <- case extVAO of Nothing -> return (error "GLO not used") Just (gen, bind, del) -> newGLO gen del (\i-> bind i >> setVA) return $ VertexArray (glo, setVA) -- ** Constant Vertex Attribute constAttrib :: ShaderAttribute a => Attrib p a -> a -> SetVertexAttr p constAttrib (Attrib (idx, s, n, d)) val = do glDisableVertexAttribArray idx glVertexAttrib idx val --withConstAttr :: Attrib p a -> GL b -> GL b --withConstAttr (Attrib (idx, _, _, _)) io = do -- glDisableVertexAttribArray idx -- result <- io -- glEnableVertexAttribArray idx -- ** Texture data Texture = Texture Int32 instance UnifVal Texture where glUniform unif (Texture i) = glUniform unif i -- ** Vertex Picker -- Wrapping glDrawArrays takeFrom :: Int32 -> Int32 -> VertexPicker takeFrom first count = VertexPicker $ \mode -> do glDrawArrays mode first count showError "glDrawArrays" -- Wrapping glDrawArraysInstanced[EXT] takeFromInstanced :: Int32 -> Int32 -> Int32 -> VertexPicker takeFromInstanced first count numInstances = VertexPicker $ \mode -> do glDrawArraysInstanced mode first count numInstances showError "glDrawArraysInstanced" -- Wrapping glMultiDrawArraysEXT takeFromMany :: [(Int32, Int32)] -> VertexPicker takeFromMany list = VertexPicker $ \mode -> do forM_ list $ \(first, count) -> do glDrawArrays mode first count showError "glDrawArrays[]" -- showError "glMultiDrawElementsEXT" return True -- TakeFromManyRaw (Buffer Int32) (Buffer Word32) sizePtr :: Int32 -> Ptr () sizePtr = intPtrToPtr . fromIntegral -- Wrapping glDrawElements byIndex :: VertexIx a => (Buffer a) -> Int32 -> Int32 -> VertexPicker byIndex buf first count = let (typ, stride) = vxix buf in VertexPicker $ \mode -> do bindBuffer element_array_buffer buf glDrawElements mode count typ (sizePtr $ first * stride) showError "glDrawElements" -- Wrapping glDrawElementsInstanced[EXT] byIndexInstanced :: VertexIx a => (Buffer a) -> Int32 -> Int32 -> Int32 -> VertexPicker byIndexInstanced buf first count instances = let (typ, stride) = vxix buf in VertexPicker $ \mode -> do bindBuffer element_array_buffer buf glDrawElementsInstanced mode count typ (sizePtr $ first * stride) instances showError "glDrawElementsInstanced" -- Wrapping glMultiDrawElementsEXT byIndices :: VertexIx a => (Buffer a) -> [(Int32, Int32)] -> VertexPicker byIndices buf list = let (typ, stride) = vxix buf in VertexPicker $ \mode -> do bindBuffer element_array_buffer buf forM_ list $ \(first, count) -> do glDrawElements mode count typ (sizePtr $ first * stride) showError "glDrawElements[]" return True --withFirstCountArray list $ \cptr iptr clen -> do -- glMultiDrawElementsEXT mode cptr typ iptr (clen * stride) -- showError "glMultiDrawElementsEXT" -- ByIndicesRaw (Buffer w) (Buffer Word32) (Buffer Word32) -- Wrapping glDrawRangeElements[EXT] byIndexLimited :: VertexIx a => (Buffer a) -> Int32 -> Int32 -> Word32 -> Word32 -> VertexPicker byIndexLimited buf first count min max = let (typ, stride) = vxix buf in VertexPicker $ \mode -> do bindBuffer element_array_buffer buf glDrawElements mode count typ (sizePtr $ first * stride) showError "glDrawElements'" --showError "glDrawRangeElements[EXT]" -- FromToIndexRaw !BufferRef !Int !Int !GLsizei !GLenum !Int drawCallSequence :: [VertexPicker] -> VertexPicker drawCallSequence xs = VertexPicker $ \mode -> mapM_ (\(VertexPicker f) -> f mode) xs >> return True