module FWGL.Graphics.Draw (
Draw,
DrawState,
runDraw,
execDraw,
drawInit,
drawBegin,
drawLayer,
drawGroup,
drawObject,
drawEnd,
removeGeometry,
removeTexture,
removeProgram,
textureUniform,
textureSize,
setProgram,
resizeViewport,
gl,
renderLayer,
layerToTexture,
drawState
) where
import FWGL.Geometry
import FWGL.Graphics.Color
import FWGL.Graphics.Shapes
import FWGL.Graphics.Types
import FWGL.Graphics.Texture
import FWGL.Backend.IO
import FWGL.Internal.GL hiding (Texture, Program, UniformLocation)
import qualified FWGL.Internal.GL as GL
import FWGL.Internal.Resource
import FWGL.Shader.CPU
import FWGL.Shader.GLSL
import FWGL.Shader.Program
import Data.Bits ((.|.))
import Data.Hashable (Hashable)
import qualified Data.HashMap.Strict as H
import qualified Data.Vector as V
import Data.Typeable
import Data.Vect.Float
import Data.Word (Word, Word8)
import Control.Applicative
import Control.Monad (when)
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
drawInit :: (BackendIO, GLES)
=> Int
-> Int
-> Canvas
-> GL DrawState
drawInit w h canvas =
do enable gl_DEPTH_TEST
enable gl_BLEND
blendFunc gl_SRC_ALPHA gl_ONE_MINUS_SRC_ALPHA
clearColor 0.0 0.0 0.0 1.0
depthFunc gl_LESS
viewport 0 0 (fromIntegral w) (fromIntegral h)
return DrawState { currentProgram = Nothing
, loadedProgram = Nothing
, programs = newGLResMap
, gpuBuffers = newGLResMap
, gpuVAOs = newDrawResMap
, uniforms = newGLResMap
, textureImages = newGLResMap
, activeTextures =
V.replicate maxTexs Nothing
, viewportSize = (w, h) }
where newGLResMap :: (Hashable i, Resource i r GL) => ResMap i r
newGLResMap = newResMap
newDrawResMap :: (Hashable i, Resource i r Draw) => ResMap i r
newDrawResMap = newResMap
maxTexs :: (Integral a, GLES) => a
maxTexs = 32
runDraw :: Draw a
-> DrawState
-> GL (a, DrawState)
runDraw (Draw a) = runStateT a
execDraw :: Draw a
-> DrawState
-> GL DrawState
execDraw (Draw a) = execStateT a
evalDraw :: Draw a
-> DrawState
-> GL a
evalDraw (Draw a) = evalStateT a
drawState :: Draw DrawState
drawState = Draw get
resizeViewport :: GLES
=> Int
-> Int
-> Draw ()
resizeViewport w h = do gl $ viewport 0 0 (fromIntegral w) (fromIntegral h)
Draw . modify $ \s -> s { viewportSize = (w, h) }
drawBegin :: GLES => Draw ()
drawBegin = do freeActiveTextures
gl . clear $ gl_COLOR_BUFFER_BIT .|. gl_DEPTH_BUFFER_BIT
drawEnd :: GLES => Draw ()
drawEnd = return ()
removeGeometry :: (GLES, BackendIO) => Geometry is -> Draw Bool
removeGeometry gi = let g = castGeometry gi in
do removeDrawResource gl gpuBuffers (\m s -> s { gpuBuffers = m }) g
removeDrawResource id gpuVAOs (\m s -> s { gpuVAOs = m }) g
removeTexture :: BackendIO => Texture -> Draw Bool
removeTexture (TextureImage i) = removeDrawResource gl textureImages
(\m s -> s { textureImages = m }) i
removeTexture (TextureLoaded l) = do gl $ unloadResource
(Nothing :: Maybe TextureImage) l
return True
removeProgram :: (GLES, BackendIO) => Program gs is -> Draw Bool
removeProgram = removeDrawResource gl programs (\m s -> s { programs = m })
. castProgram
drawLayer :: (GLES, BackendIO) => Layer -> Draw ()
drawLayer (Layer prg grp) = setProgram prg >> drawGroup grp
drawLayer (SubLayer rl) =
do (layers, textures) <- renderLayer rl
mapM_ drawLayer layers
mapM_ removeTexture textures
drawLayer (MultiLayer layers) = mapM_ drawLayer layers
drawGroup :: (GLES, BackendIO) => Group gs is -> Draw ()
drawGroup Empty = return ()
drawGroup (Object o) = drawObject o
drawGroup (Global (g := c) o) = c >>= uniform g >> drawGroup o
drawGroup (Append g g') = drawGroup g >> drawGroup g'
drawObject :: (GLES, BackendIO) => Object gs is -> Draw ()
drawObject NoMesh = return ()
drawObject (Mesh g) = withRes_ (getGPUVAOGeometry $ castGeometry g)
drawGPUVAOGeometry
drawObject ((g := c) :~> o) = c >>= uniform g >> drawObject o
uniform :: (GLES, Typeable g, UniformCPU c g) => (a -> g) -> c -> Draw ()
uniform g c = withRes_ (getUniform $ g undefined)
$ \(UniformLocation l) -> gl $ setUniform l
(g undefined) c
textureUniform :: (GLES, BackendIO) => Texture -> Draw ActiveTexture
textureUniform tex = withRes (getTexture tex) (return $ ActiveTexture 0)
$ \(LoadedTexture _ _ wtex) ->
do at <- makeActive tex
gl $ bindTexture gl_TEXTURE_2D wtex
return at
textureSize :: (GLES, BackendIO, Num a) => Texture -> Draw (a, a)
textureSize tex = withRes (getTexture tex) (return (0, 0))
$ \(LoadedTexture w h _) -> return ( fromIntegral w
, fromIntegral h)
setProgram :: (GLES, BackendIO) => Program g i -> Draw ()
setProgram p = do current <- currentProgram <$> Draw get
when (current /= Just (castProgram p)) $
withRes_ (getProgram $ castProgram p) $
\lp@(LoadedProgram glp _ _) -> do
Draw . modify $ \s -> s {
currentProgram = Just $ castProgram p,
loadedProgram = Just lp
}
gl $ useProgram glp
withRes_ :: Draw (ResStatus a) -> (a -> Draw ()) -> Draw ()
withRes_ drs = withRes drs $ return ()
withRes :: Draw (ResStatus a) -> Draw b -> (a -> Draw b) -> Draw b
withRes drs u l = drs >>= \rs -> case rs of
Loaded r -> l r
_ -> u
getUniform :: (Typeable a, GLES) => a -> Draw (ResStatus UniformLocation)
getUniform g = do mprg <- loadedProgram <$> Draw get
case mprg of
Just prg ->
getDrawResource gl uniforms
(\ m s -> s { uniforms = m })
(prg, globalName g)
Nothing -> return $ Error "No loaded program."
getGPUVAOGeometry :: (GLES, BackendIO)
=> Geometry '[] -> Draw (ResStatus GPUVAOGeometry)
getGPUVAOGeometry = getDrawResource id gpuVAOs (\ m s -> s { gpuVAOs = m })
getGPUBufferGeometry :: (GLES, BackendIO)
=> Geometry '[] -> Draw (ResStatus GPUBufferGeometry)
getGPUBufferGeometry = getDrawResource gl gpuBuffers
(\ m s -> s { gpuBuffers = m })
getGPUBufferGeometry' :: (GLES, BackendIO)
=> Geometry '[]
-> (Either String GPUBufferGeometry -> GL ())
-> Draw (ResStatus GPUBufferGeometry)
getGPUBufferGeometry' = getDrawResource' gl gpuBuffers
(\ m s -> s { gpuBuffers = m })
getTexture :: (GLES, BackendIO) => Texture -> Draw (ResStatus LoadedTexture)
getTexture (TextureLoaded l) = return $ Loaded l
getTexture (TextureImage t) = getTextureImage t
getTextureImage :: (GLES, BackendIO) => TextureImage
-> Draw (ResStatus LoadedTexture)
getTextureImage = getDrawResource gl textureImages
(\ m s -> s { textureImages = m })
getProgram :: (GLES, BackendIO)
=> Program '[] '[] -> Draw (ResStatus LoadedProgram)
getProgram = getDrawResource gl programs (\ m s -> s { programs = m })
freeActiveTextures :: GLES => Draw ()
freeActiveTextures = Draw . modify $ \ds ->
ds { activeTextures = V.replicate maxTexs Nothing }
makeActive :: GLES => Texture -> Draw ActiveTexture
makeActive t = do ats <- activeTextures <$> Draw get
let at@(ActiveTexture atn) =
case V.elemIndex (Just t) ats of
Just n -> ActiveTexture $ fi n
Nothing ->
case V.elemIndex Nothing ats of
Just n -> ActiveTexture $ fi n
Nothing -> ActiveTexture 0
gl . activeTexture $ gl_TEXTURE0 + fi atn
Draw . modify $ \ds ->
ds { activeTextures = ats V.// [(fi atn, Just t)] }
return at
where fi :: (Integral a, Integral b) => a -> b
fi = fromIntegral
renderLayer :: BackendIO => RenderLayer a -> Draw (a, [Texture])
renderLayer (RenderLayer stypes w' h' rx ry rw rh inspCol inspDepth layer f) =
do (ts, mcol, mdepth) <- layerToTexture stypes w h layer
(mayInspect inspCol)
(mayInspect inspDepth)
return (f ts mcol mdepth, ts)
where w = fromIntegral w'
h = fromIntegral h'
mayInspect :: Bool
-> Either (Maybe [r])
([r] -> Draw (Maybe [r]), Int, Int, Int, Int)
mayInspect True = Right (return . Just, rx, ry, rw, rh)
mayInspect False = Left Nothing
layerToTexture :: (GLES, BackendIO, Integral a)
=> [LayerType]
-> a
-> a
-> Layer
-> Either b ( [Color] -> Draw b
, Int, Int, Int, Int)
-> Either c ( [Word8] -> Draw c
, Int, Int, Int, Int)
-> Draw ([Texture], b ,c)
layerToTexture stypes wp hp layer einspc einspd = do
(ts, (colRes, depthRes)) <- renderToTexture (map arguments stypes) w h $
do drawLayer layer
colRes <- inspect einspc gl_RGBA wordsToColors 4
depthRes <- inspect einspd gl_DEPTH_COMPONENT id 1
return (colRes, depthRes)
return (map (TextureLoaded . LoadedTexture w h) ts, colRes, depthRes)
where (w, h) = (fromIntegral wp, fromIntegral hp)
arguments stype =
case stype of
ColorLayer -> ( fromIntegral gl_RGBA
, gl_RGBA
, gl_UNSIGNED_BYTE
, gl_COLOR_ATTACHMENT0 )
DepthLayer -> ( fromIntegral gl_DEPTH_COMPONENT
, gl_DEPTH_COMPONENT
, gl_UNSIGNED_SHORT
, gl_DEPTH_ATTACHMENT )
inspect :: Either c (a -> Draw c, Int, Int, Int, Int) -> GLEnum
-> ([Word8] -> a) -> Int -> Draw c
inspect (Left r) _ _ s = return r
inspect (Right (insp, x, y, rw, rh)) format trans s =
do arr <- liftIO . newByteArray $
fromIntegral rw * fromIntegral rh * s
gl $ readPixels (fromIntegral x)
(fromIntegral y)
(fromIntegral rw)
(fromIntegral rh)
format gl_UNSIGNED_BYTE arr
liftIO (decodeBytes arr) >>= insp . trans
wordsToColors (r : g : b : a : xs) = Color r g b a :
wordsToColors xs
wordsToColors _ = []
renderToTexture :: (GLES, BackendIO)
=> [(GLInt, GLEnum, GLEnum, GLEnum)]
-> GLSize -> GLSize -> Draw a -> Draw ([GL.Texture], a)
renderToTexture infos w h act = do
fb <- gl createFramebuffer
gl $ bindFramebuffer gl_FRAMEBUFFER fb
ts <- gl . flip mapM infos $
\(internalFormat, format, pixelType, attachment) ->
do t <- emptyTexture
arr <- liftIO $ noArray
bindTexture gl_TEXTURE_2D t
texImage2DBuffer gl_TEXTURE_2D 0 internalFormat w
h 0 format pixelType arr
framebufferTexture2D gl_FRAMEBUFFER attachment
gl_TEXTURE_2D t 0
return t
(sw, sh) <- viewportSize <$> Draw get
resizeViewport (fromIntegral w) (fromIntegral h)
drawBegin
ret <- act
drawEnd
resizeViewport sw sh
gl $ deleteFramebuffer fb
return (ts, ret)
getDrawResource :: (Resource i r m, Hashable i)
=> (m (ResStatus r, ResMap i r)
-> Draw (ResStatus r, ResMap i r))
-> (DrawState -> ResMap i r)
-> (ResMap i r -> DrawState -> DrawState)
-> i
-> Draw (ResStatus r)
getDrawResource lft mg ms i = getDrawResource' lft mg ms i $ const (return ())
getDrawResource' :: (Resource i r m, Hashable i)
=> (m (ResStatus r, ResMap i r)
-> Draw (ResStatus r, ResMap i r))
-> (DrawState -> ResMap i r)
-> (ResMap i r -> DrawState -> DrawState)
-> i
-> (Either String r -> m ())
-> Draw (ResStatus r)
getDrawResource' lft mg ms i f = do
s <- Draw get
(r, map) <- lft $ getResource' i (mg s) f
Draw . put $ ms map s
return r
removeDrawResource :: (Resource i r m, Hashable i)
=> (m (Bool, ResMap i r) -> Draw (Bool, ResMap i r))
-> (DrawState -> ResMap i r)
-> (ResMap i r -> DrawState -> DrawState)
-> i
-> Draw Bool
removeDrawResource lft mg ms i = do
s <- Draw get
(removed, map) <- lft . removeResource i $ mg s
Draw . put $ ms map s
return removed
drawGPUVAOGeometry :: GLES => GPUVAOGeometry -> Draw ()
drawGPUVAOGeometry (GPUVAOGeometry _ ec vao) = currentProgram <$> Draw get >>=
\mcp -> case mcp of
Just _ -> gl $ do bindVertexArray vao
drawElements gl_TRIANGLES
(fromIntegral ec)
gl_UNSIGNED_SHORT
nullGLPtr
bindVertexArray noVAO
Nothing -> return ()
instance GLES => Resource (LoadedProgram, String) UniformLocation GL where
loadResource (LoadedProgram prg _ _, g) f =
do loc <- getUniformLocation prg $ toGLString g
f . Right $ UniformLocation loc
unloadResource _ _ = return ()
instance (GLES, BackendIO) => Resource (Geometry '[]) GPUVAOGeometry Draw where
loadResource g f = (>> return ()) . getGPUBufferGeometry' g $
\ge -> case ge of
Left err -> drawInGL . f $ Left err
Right buf -> loadResource buf $ drawInGL . f
where drawInGL = flip evalDraw $
error "drawInGL: can't access draw state"
unloadResource _ =
gl . unloadResource (Nothing :: Maybe GPUBufferGeometry)
gl :: GL a -> Draw a
gl = Draw . lift