----------------------------------------------------------------------------- -- | -- 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 #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-} module Graphics.OpenGLES.Core ( GL, -- * Lifecycle forkGL, stopGL, --destroyGL, endFrameGL, runGL, --runGLRes withGL, resetDrawQueue, glLog, glReadLogs, glLogContents, glFrameCount, glFlipping, framesize, -- * Draw Operation -- ** Clear Screen -- | See "Graphics.OpenGLES.Framebuffer" -- ** Draw glDraw, -- ** Draw Mode DrawMode, drawPoints, drawLines, drawLineLoop, drawLineStrip, drawTriangles, triangleStrip, triangleFan, linesAdjacency, lineStripAdjacency, trianglesAdjacency, triangleStripAdjacency, -- ** Graphics State RenderConfig, renderTo, -- ** Programmable Shader Shader, vertexShader, fragmentShader, pixelShader, computeShader, geometryShader, tessellationEvalS, tessellationCtrlS, Program, module Data.Typeable, TransformFeedback(..), ProgramBinary, glCompile, glValidate, -- ** Uniform Variable Uniform, uniform, ($=), UnifVal, UniformAssignment, -- ** Vertex Attribute Array Attrib, attrib, normalized, divisor, (&=), VertexArray, glVA, VertexAttribute, AttrStruct, SetVertexAttr, -- ** Constant Vertex Attribute constAttrib, -- ** Texture -- | See "Graphics.OpenGLES.Texture" -- ** Vertex Picker VertexPicker, takeFrom, takeFromInstanced, takeFromMany, takeFromMany', VertexIx, byIndex, byIndexInstanced, byIndices, byIndices', byRange ) where import Control.Applicative import Control.Monad import Control.Monad.IO.Class 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 qualified Data.Vector.Storable as V import Foreign import Foreign.C.String (peekCStringLen) import Graphics.OpenGLES.Base import Graphics.OpenGLES.Buffer import Graphics.OpenGLES.Caps import Graphics.OpenGLES.Internal -- * Initialization forkGL :: MonadIO m => IO Bool -> GL () -> GL () -> m ThreadId forkGL resumeGL suspendGL swapBuffers = liftIO $ 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 :: Integer) catch (loop 0) $ \(e :: SomeException) -> do glLog $ "Rendering thread terminated: " ++ show e suspendGL putStrLn "unbindEGL" writeIORef drawOrExit (Just (glLog "Fatal lifecycle bug")) stopGL :: MonadIO m => m () stopGL = liftIO $ do putStrLn "stopGL" writeIORef drawOrExit Nothing let waitGLThread = readIORef drawOrExit >>= \case Just _ -> nop Nothing -> waitGLThread waitGLThread putStrLn "Rendering has stopped." --destroyGL :: MonadIO m => m () --destroyGL = runGL $ eglMakeCurrent Nothing, eglDestroyXXX ... endFrameGL :: MonadIO m => m () endFrameGL = liftIO $ withGL go >>= waitFor >> nop where go = do readIORef drawOrExit >>= \case Just eglSwapBuffer -> do eglSwapBuffer modifyIORef frameCounter (+1) Nothing -> myThreadId >>= killThread runGL :: MonadIO m => GL () -> m () runGL io = liftIO $ writeChan drawQueue io --runGLRes :: GL () -> IO () --runGLRes io = forkOS withGL :: MonadIO m => GL a -> m (Future' a) withGL io = mkFuture $ \update -> runGL (io >>= update . Finished) -- | drawQueue may have drawcalls that use previous context, -- so make it sure they are removed from the queue. resetDrawQueue :: MonadIO m => m () resetDrawQueue = liftIO $ do isEmpty <- isEmptyChan drawQueue when (not isEmpty) (readChan drawQueue >> resetDrawQueue) glReadLogs :: MonadIO m => m [String] glReadLogs = liftIO $ do isEmpty <- isEmptyChan errorQueue if isEmpty then return [] else (:) <$> readChan errorQueue <*> glReadLogs glLogContents :: MonadIO m => m [String] glLogContents = liftIO $ getChanContents errorQueue -- | @return ()@ nop :: MonadIO m => m () nop = return () glFrameCount :: MonadIO m => m Int64 glFrameCount = liftIO $ readIORef frameCounter glFlipping :: MonadIO m => m Bool glFlipping = fmap odd glFrameCount -- XXX bindFb defaultFramebuffer needed -- | > GLFW.setFramebufferSizeCallback win $ Just (const framesize) framesize :: MonadIO m => Int -> Int -> m () framesize w h = runGL $ glViewport 0 0 (f w) (f h) where f = fromIntegral -- * Drawing glDraw :: Typeable p => DrawMode -> Program p -> [RenderConfig] -> [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 -- | Exists for better documentation. -- -- > renderTo $ do -- > bindFb defaultFramebuffer -- > viewport $ V4 0 0 512 512 -- > depthRange $ V2 0.1 10.0 -- > begin culling -- > cullFace hideBack renderTo :: RenderConfig -> GL () renderTo = id -- ** Draw Mode drawPoints, drawLines, drawLineLoop, drawLineStrip, drawTriangles, triangleStrip, triangleFan :: DrawMode drawPoints = DrawMode 0 drawLines = DrawMode 1 drawLineLoop = DrawMode 2 drawLineStrip = DrawMode 3 drawTriangles = DrawMode 4 triangleStrip = DrawMode 5 triangleFan = DrawMode 6 -- | /GL_EXT_geometry_shader/ GL_LINES_ADJACENCY_EXT linesAdjacency = DrawMode 10 -- | /GL_EXT_geometry_shader/ GL_LINE_STRIP_ADJACENCY_EXT lineStripAdjacency = DrawMode 11 -- | /GL_EXT_geometry_shader/ GL_TRIANGLES_ADJACENCY_EXT trianglesAdjacency = DrawMode 12 -- | /GL_EXT_geometry_shader/ GL_TRIANGLE_STRIP_ADJACENCY_EXT triangleStripAdjacency = DrawMode 13 -- ** 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) infix 0 $= ($=) :: 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. (VertexAttribute 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 () infix 0 &= (&=) :: AttrStruct a p b => a -> Buffer b -> SetVertexAttr p attribs &= buf = do bindBuffer array_buffer buf glVertexBuffer attribs 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) extVAO :: Maybe (GLsizei -> Ptr GLuint -> GL (), GLuint -> GL (), GLsizei -> Ptr GLuint -> GL ()) extVAO | hasES3 = Just (glGenVertexArrays, glBindVertexArray, glDeleteVertexArrays) | hasExt "GL_OES_vertex_array_object" = Just (glGenVertexArraysOES, glBindVertexArrayOES, glDeleteVertexArraysOES) | otherwise = Nothing -- ** Constant Vertex Attribute constAttrib :: VertexAttribute a => Attrib p a -> a -> SetVertexAttr p constAttrib (Attrib (ix, _, _, _)) val = do glDisableVertexAttribArray ix glVertexAttrib ix val -- ** 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 :: V.Vector Int32 -> V.Vector Int32 -> VertexPicker takeFromMany first_ count_ = VertexPicker $ \mode -> if hasMDA then do let len = fromIntegral $ min (V.length first_) (V.length count_) V.unsafeWith first_ $ \first -> V.unsafeWith count_ $ \count -> glMultiDrawArraysEXT mode first count len showError "glMultiDrawArraysEXT" else do V.zipWithM_ (go mode) first_ count_ return True where go mode first count = do glDrawArrays mode first count showError "glDrawArrays[]" takeFromMany' :: [(Int32, Int32)] -> VertexPicker takeFromMany' xs = takeFromMany (V.fromList first) (V.fromList count) where (first, count) = unzip xs hasMDA = hasExt "GL_EXT_multi_draw_arrays" sizePtr :: Int32 -> Ptr () sizePtr = intPtrToPtr . fromIntegral -- Wrapping glDrawElements byIndex :: VertexIx a => Buffer a -> Int32 -> Int32 -> VertexPicker byIndex buf first count = VertexPicker $ \mode -> do let (typ, stride) = vxix buf 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 = VertexPicker $ \mode -> do let (typ, stride) = vxix buf bindBuffer element_array_buffer buf glDrawElementsInstanced mode count typ (sizePtr $ first * stride) instances showError "glDrawElementsInstanced" -- Wrapping glMultiDrawElementsEXT byIndices :: VertexIx a => Buffer a -> V.Vector Int32 -> V.Vector Int32 -> VertexPicker byIndices buf first_ count_ = VertexPicker $ \mode -> do let (typ, stride) = vxix buf let offset ix = sizePtr (stride * ix) bindBuffer element_array_buffer buf if hasMDA then do let len = fromIntegral $ min (V.length first_) (V.length count_) V.unsafeWith first_ $ \first -> V.unsafeWith count_ $ \count -> glMultiDrawElementsEXT mode count typ (castPtr first) len showError "glMultiDrawElementsEXT" else do let go mode first count = do glDrawElements mode count typ (offset first) showError "glDrawElements[]" V.zipWithM_ (go mode) first_ count_ return True byIndices' :: VertexIx a => Buffer a -> [(Int32, Int32)] -> VertexPicker byIndices' buf xs = do byIndices buf (V.fromList first) (V.fromList count) where (first, count) = unzip xs extDRE = if hasES3 then Just glDrawRangeElements else if hasExt "GL_EXT_draw_range_elements" then Just glDrawRangeElementsEXT else Nothing -- Wrapping glDrawRangeElements[EXT] byRange :: VertexIx a => Buffer a -> Int32 -> Int32 -> Word32 -> Word32 -> VertexPicker byRange buf first count start end_ = VertexPicker $ \mode -> do let (typ, stride) = vxix buf let offset = sizePtr (first * stride) bindBuffer element_array_buffer buf case extDRE of Just glDRE -> do glDRE mode start end_ count typ offset showError "glDrawRangeElements[EXT]" Nothing -> do glDrawElements mode count typ offset showError "glDrawElements'"