module Browser (Browser(..), browserNew, browserRenders) where import Prelude hiding (log, lines) import Control.Concurrent ( forkIO, MVar, newEmptyMVar, newMVar, takeMVar, putMVar, tryTakeMVar, threadDelay ) import Control.Monad (foldM, forever, forM_, replicateM, unless, when) import Data.IORef (IORef, newIORef, readIORef, atomicModifyIORef) import Data.List (sortBy) import qualified Data.Map as M import Data.Map (Map) import Data.Maybe (fromMaybe) import Data.Ord (comparing) import qualified Data.Set as S import Data.Set ((\\)) import Foreign (Ptr, malloc, alloca, peek, poke, nullPtr) import Foreign.C (CFloat, CInt) import GHC.Conc (numCapabilities) import Graphics.Rendering.OpenGL hiding ( viewport, Angle, Color, Point, Position, Size, S, R ) import qualified Graphics.Rendering.OpenGL as GL import Graphics.Rendering.OpenGL.Raw ( glTexImage2D, gl_TEXTURE_2D, gl_LUMINANCE32F, gl_RGBA, gl_LUMINANCE , gl_FLOAT, gl_UNSIGNED_BYTE, gl_FALSE, glClampColor , gl_CLAMP_VERTEX_COLOR, gl_CLAMP_READ_COLOR, gl_CLAMP_FRAGMENT_COLOR , glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D , gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, glGenerateMipmap , gl_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus , glGetError ) import Graphics.UI.Gtk hiding ( get, Window, Viewport, Region, Size, Action, Image, Label, labelText ) import qualified Graphics.UI.Gtk as GTK import Graphics.Rendering.FTGL as FTGL import Fractal.RUFF.Types.Complex (Complex((:+))) import GLUTGtk ( GLUTGtk, Size(Size), Key(MouseButton), KeyState(Down), Position(Position) , postRedisplay, widget, realizeCallback, reshapeCallback , displayCallback, keyboardMouseCallback ) import Shader (shader) import QuadTree (Quad(..)) import Tile (Tile(Tile), getTile, freeTile) import Logger (Logger, LogLevel(Debug)) import qualified Logger as Log import Snapshot (writeSnapshot) import View ( Image(..), Window(..), Viewport(..), Label(..), Line(..) , BufferSize(..), bufferSize, tileSize, delta , Colours(..), Colour(..) , pixelLocation, originQuad, visibleQuads, locationPixel , defImage, defWindow ) import Interact (MouseCallbacks, Mod(..), But(..)) import Paths_gruff (getDataFileName) type TextureObject3 = (TextureObject, TextureObject, TextureObject) type QuadList = [(Complex Int, Quad)] data GruffImage = GruffImage { image :: Image -- tile cache , tiles :: Map Quad TextureObject3 , queue :: MVar [Tile] , jobs :: MVar [Quad] , viewQuads :: (QuadList, QuadList) , workers :: [Ptr CInt] , progress :: Map (Ptr CInt) Quad , gl :: GLUTGtk , cacheDir :: FilePath , log :: LogLevel -> String -> IO () , prog :: Program , combineProg :: Program , tsheet0 :: TextureObject , tsheet1 :: TextureObject , sheetCount0 :: Int , sheetCount1 :: Int , sheetCountTarget :: Int , fbo :: GLuint , cacheSizeMin :: Int , cacheSizeMax :: Int -- callbacks , exitCallback :: Maybe (IO ()) , reshapeCallback :: Maybe (Int -> Int -> IO ()) , mouseCallback :: MouseCallbacks , doneCallback :: Maybe (IO ()) , abortCallback :: Maybe (IO ()) , doClear :: Bool , font :: FTGL.Font } sheetSize :: GruffImage -> BufferSize sheetSize g = bufferSize (sheetOffset g) (imageWindow (image g)) zoomPhase :: GruffImage -> Double zoomPhase = delta . imageLocation . image rotationA :: GruffImage -> Double rotationA = orient . imageViewport . image sheetBlend :: GruffImage -> (Int, (Double, Double)) sheetBlend g = let z = zoomPhase g s = supersamples (imageWindow (image g)) b = logBase 4 (4 ** z * s) h = floor b d = fromIntegral h - b in (h, (d, 1 - (1 - 4**d) / (4**(d+1) - 4**d))) sheetOffset :: GruffImage -> Int sheetOffset = fst . sheetBlend {- sheetDelta :: GruffImage -> Double sheetDelta = fst . snd . sheetBlend -} blendFactor :: GruffImage -> Double blendFactor = snd . snd . sheetBlend iDisplay :: IORef GruffImage -> IO () iDisplay iR = do checkError "display begin" s0 <- readIORef iR mtls <- tryTakeMVar (queue s0) case mtls of Just tls -> do putMVar (queue s0) [] forM_ tls $ \tile@(Tile q ns ds ts) -> do tde <- upload ds checkError "upload ds" tit <- upload ns checkError "upload ns" ttt <- upload ts checkError "upload ts" freeTile tile atomicModifyIORef iR $ \s' -> ( s'{ tiles = M.insert q (tde, tit, ttt) (tiles s') , progress = M.filter (/= q) (progress s') } , ()) Nothing -> return () s <- readIORef iR log s Debug $ "displayCallback " ++ show (imageLocation (image s)) c <- atomicModifyIORef iR $ \s' -> (s'{ doClear = False }, doClear s') when c $ do clearSheet s False checkError "clear sheet F" clearSheet s True checkError "clear sheet T" atomicModifyIORef iR $ \s' -> (s'{ sheetCount0 = 0, sheetCount1 = 0 }, ()) (todo0, upped0) <- renderSheet s False checkError "render sheet F" (todo1, upped1) <- renderSheet s True checkError "render sheet T" atomicModifyIORef iR $ \s' -> ( s'{ viewQuads = (todo0, todo1) , sheetCount0 = sheetCount0 s' + upped0 , sheetCount1 = sheetCount1 s' + upped1 }, () ) let w = width (imageWindow (image s)) h = height (imageWindow (image s)) GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral w) (fromIntegral h)) checkError "clear V" loadIdentity checkError "clear I" ortho2D 0 (fromIntegral w) 0 (fromIntegral h) checkError "clear O" clearColor $= Color4 0.5 0.5 0.5 1 checkError "clear CC" clear [ColorBuffer] checkError "clear C" currentProgram $= Just (combineProg s) checkError "shader 1" lsheet0 <- get $ uniformLocation (combineProg s) "sheet0" checkError "shader 2a" lsheet1 <- get $ uniformLocation (combineProg s) "sheet1" checkError "shader 2b" lblend <- get $ uniformLocation (combineProg s) "blend" checkError "shader 2c" uniform lsheet0 $= TexCoord1 (0 :: GLint) checkError "shader 3a" uniform lsheet1 $= TexCoord1 (1 :: GLint) checkError "shader 3b" uniform lblend $= TexCoord1 (realToFrac (blendFactor s) :: GLfloat) checkError "shader 3c" activeTexture $= TextureUnit 0 checkError "shader 4" textureBinding Texture2D $= Just (tsheet0 s) checkError "shader 5" activeTexture $= TextureUnit 1 checkError "shader 6" textureBinding Texture2D $= Just (tsheet1 s) checkError "shader 7" let t x0 y0 = texCoord $ TexCoord2 (0.5 + x' :: GLdouble) (0.5 + y' :: GLdouble) where p = realToFrac . sqrt . aspect . imageViewport . image $ s x = k * x0 * p y = k * y0 / p a = - rotationA s co = realToFrac $ cos a si = realToFrac $ sin a x' = co * x + si * y y' = -si * x + co * y v :: Int -> Int -> IO () v x y = vertex $ Vertex2 (fromIntegral x :: GLdouble) (fromIntegral y :: GLdouble) k = 0.125 * realToFrac (0.5 ** zoomPhase s) :: GLdouble renderPrimitive Quads $ do t (-1) 1 >> v 0 h t (-1) (-1) >> v 0 0 t 1 (-1) >> v w 0 t 1 1 >> v w h checkError "quad" textureBinding Texture2D $= Nothing checkError "shader 8" activeTexture $= TextureUnit 0 checkError "shader 9" textureBinding Texture2D $= Nothing checkError "shader A" currentProgram $= Nothing checkError "shader B" -- save state m <- get matrixMode matrixMode $= Modelview 0 colour0 <- get currentColor -- pixel mapping let i = image s locP = locationPixel (imageWindow i) (imageViewport i) (imageLocation i) locL (a, b) = [locP a, locP b] -- draw lines forM_ (imageLines i) $ \l -> do let ps = concatMap locL (lineSegments l) Colour r g b = lineColour l currentColor $= Color4 (realToFrac r) (realToFrac g) (realToFrac b) 1 renderPrimitive Lines $ forM_ ps $ \(x, y) -> do vertex $ Vertex2 (realToFrac x) (fromIntegral h - realToFrac y :: GLdouble) -- draw labels forM_ (imageLabels i) $ \l -> do let (x, y) = locP (labelCoords l) Colour r g b = labelColour l currentColor $= Color4 (realToFrac r) (realToFrac g) (realToFrac b) 1 translate (Vector3 (realToFrac x) (fromIntegral h - realToFrac y) (0 :: GLdouble)) renderFont (font s) (labelText l) All translate (Vector3 (-realToFrac x) (realToFrac y - fromIntegral h) (0 :: GLdouble)) -- restore state currentColor $= colour0 matrixMode $= m s'' <- readIORef iR when (sheetCount0 s'' + sheetCount1 s'' >= sheetCountTarget s'') $ do case doneCallback s of Nothing -> return () Just act -> do atomicModifyIORef iR $ \s' -> (s'{ doneCallback = Nothing, abortCallback = Nothing }, ()) act prune iR checkError "display end" clearSheet :: GruffImage -> Bool -> IO () clearSheet s b = do checkError "clearSheet begin" let tw' = texels $ sheetSize s th' = tw' tw = if b then tw' * 2 else tw' th = if b then th' * 2 else th' tsheet = (if b then tsheet1 else tsheet0) s bindFBO (fbo s) tsheet checkError "clearSheet fbo bind" GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral tw) (fromIntegral th)) checkError "clearSheet V" loadIdentity checkError "clearSheet I" clearColor $= Color4 0 0 1 1 checkError "clearSheet CC" clear [ColorBuffer] checkError "clearSheet C" unbindFBO checkError "clearSheet fbo unbind" textureBinding Texture2D $= Just tsheet checkError "clearSheet tex bind" glGenerateMipmap gl_TEXTURE_2D checkError "clearSheet tex mipmap" textureBinding Texture2D $= Nothing checkError "clearSheet tex unbind" checkError "clear end" renderSheet :: GruffImage -> Bool -> IO ([(Complex Int, Quad)], Int) renderSheet s b = do checkError "renderSheet begin" let tw' = texels $ sheetSize s th' = tw' tw = if b then tw' * 2 else tw' th = if b then th' * 2 else th' tsheet = (if b then tsheet1 else tsheet0) s vquads = (if b then snd else fst) (viewQuads s) if null vquads then return ([], 0) else do bindFBO (fbo s) tsheet checkError "renderSheet fbo bind" GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral tw) (fromIntegral th)) checkError "renderSheet V" loadIdentity checkError "renderSheet I" ortho2D 0 (fromIntegral tw) 0 (fromIntegral th) checkError "renderSheet O" currentProgram $= Just (prog s) checkError "renderSheet shader 1" lde <- get $ uniformLocation (prog s) "de" checkError "renderSheet shader u1" lit <- get $ uniformLocation (prog s) "it" checkError "renderSheet shader u2" ltt <- get $ uniformLocation (prog s) "tt" checkError "renderSheet shader u3" lint <- get $ uniformLocation (prog s) "interior" checkError "renderSheet shader u4" lbrd <- get $ uniformLocation (prog s) "border" checkError "renderSheet shader u5" lext <- get $ uniformLocation (prog s) "exterior" checkError "renderSheet shader u6" uniform lde $= TexCoord1 (0 :: GLint) checkError "renderSheet shader t1" uniform lit $= TexCoord1 (1 :: GLint) checkError "renderSheet shader t2" uniform ltt $= TexCoord1 (2 :: GLint) checkError "renderSheet shader t3" let (ci, cb, ce) = fromColours . imageColours . image $ s uniform lint $= ci checkError "renderSheet shader s1" uniform lbrd $= cb checkError "renderSheet shader s2" uniform lext $= ce checkError "renderSheet shader s3" todo <- foldM (\a t -> do a' <- drawQuad (tiles s) t ; return (a' ++ a)) [] vquads checkError "renderSheet drawQuads" currentProgram $= Nothing checkError "renderSheet shader 2" unbindFBO checkError "renderSheet fbo unbind" let upped = length vquads - length todo when (upped > 0) $ do textureBinding Texture2D $= Just tsheet checkError "renderSheet tex bind" glGenerateMipmap gl_TEXTURE_2D checkError "renderSheet tex mipmap" textureBinding Texture2D $= Nothing checkError "renderSheet tex unbind" checkError "renderSheet end" return (todo, upped) drawQuad :: Map Quad TextureObject3 -> (Complex Int, Quad) -> IO [(Complex Int, Quad)] drawQuad m ijq@(i :+ j, q) = case q `M.lookup` m of Nothing -> return [ijq] Just (tde, tit, ttt) -> do checkError "drawQuad begin" let t x y = texCoord $ TexCoord2 (x :: GLdouble) (y :: GLdouble) v x y = vertex $ Vertex2 (x :: GLdouble) (y :: GLdouble) x0 = fromIntegral i y0 = fromIntegral j x1 = x0 + fromIntegral tileSize y1 = y0 + fromIntegral tileSize activeTexture $= TextureUnit 0 checkError "drawQuad tex 0" textureBinding Texture2D $= Just tde checkError "drawQuad tex 0b" activeTexture $= TextureUnit 1 checkError "drawQuad tex 1" textureBinding Texture2D $= Just tit checkError "drawQuad tex 1b" activeTexture $= TextureUnit 2 checkError "drawQuad tex 2" textureBinding Texture2D $= Just ttt checkError "drawQuad tex 2b" renderPrimitive Quads $ do color $ Color3 1 1 (1::GLdouble) t 0 1 >> v x0 y1 t 0 0 >> v x0 y0 t 1 0 >> v x1 y0 t 1 1 >> v x1 y1 checkError "drawQuad render" textureBinding Texture2D $= Nothing checkError "drawQuad tex 3b" activeTexture $= TextureUnit 1 checkError "drawQuad tex 3" textureBinding Texture2D $= Nothing checkError "drawQuad tex 4b" activeTexture $= TextureUnit 0 checkError "drawQuad tex 4" textureBinding Texture2D $= Nothing checkError "drawQuad tex 5b" return [] iReshape :: IORef GruffImage -> Maybe Double -> Size -> IO () iReshape iR ms size'@(Size w h) = do s' <- readIORef iR log s' Debug $ "reshapeCallback " ++ show size' let ss = case ms of Nothing -> supersamples (imageWindow (image s')) ; Just r -> r atomicModifyIORef iR $ \s -> (s{ image = (image s) { imageWindow = (imageWindow (image s)){ width = w, height = h, supersamples = ss } , imageViewport = (imageViewport (image s)){ aspect = fromIntegral w / fromIntegral h } } }, ()) s'' <- readIORef iR unless (sheetSize s' == sheetSize s'') (reallocateBuffers iR) case Browser.reshapeCallback s' of Nothing -> return () Just act -> act w h iMouse :: IORef GruffImage -> Key -> KeyState -> [Modifier] -> Position -> IO () iMouse sR (MouseButton but) Down mods p@(Position x y) | but `elem` buts = do let shift = Shift `elem` mods ctrl = Control `elem` mods m | shift && ctrl = SC | ctrl = C | shift = S | otherwise = U b = case but of LeftButton -> L MiddleButton -> M RightButton -> R _ -> error "weasels!!!!111one" s <- readIORef sR log s Debug $ "mouse " ++ show (b, m) ++ " " ++ show p case (b, m) `M.lookup` mouseCallback s of Nothing -> return () Just cb -> do let i = image s c = pixelLocation (imageWindow i) (imageViewport i) (imageLocation i) x y cb c i where buts = [LeftButton, MiddleButton, RightButton] iMouse _ _ _ _ _ = return () quadDistance :: Quad -> Quad -> Double quadDistance q0 q1 = let Quad{ quadLevel = l0, quadWest = r0, quadNorth = i0 } = q0 Quad{ quadLevel = l, quadWest = r, quadNorth = i} = q1 dl = sqr (fromIntegral l - fromIntegral l0) d x x0 | l > l0 = fromIntegral $ sqr (x - x0 * 2 ^ (l - l0)) | l == l0 = fromIntegral $ sqr (x - x0) | l < l0 = fromIntegral $ sqr (x0 - x * 2 ^ (l0 - l)) d _ _ = error "score" in dl + d r r0 + d i i0 sqr :: Num a => a -> a sqr x = x * x prune :: IORef GruffImage -> IO () prune sR = do s0 <- readIORef sR let cacheSize = M.size (tiles s0) when (cacheSize > cacheSizeMax s0) $ do checkError "prune begin" log s0 Debug . concat $ [ "pruning texture cache " , show cacheSize, " > ", show (cacheSizeMax s0) , " --> ", show (cacheSizeMin s0) ] bad <- atomicModifyIORef sR $ \s -> let Just q0 = originQuad (imageLocation (image s)) (sheetSize s) score = quadDistance q0 o = comparing (score . fst) (good, bad) = splitAt (cacheSizeMin s) . sortBy o . M.toList . tiles $ s in (s{ tiles = M.fromList good }, bad) let t (_, (t1, t2, t3)) = [t1, t2, t3] deleteObjectNames $ concatMap t bad checkError "prune end" update :: IORef GruffImage -> IO () update sR = do s' <- readIORef sR log s' Debug $ "updateCallback " todo' <- atomicModifyIORef sR $ \s -> let i = image s vq@(qs0, qs1) = fromMaybe ([],[]) $ visibleQuads (imageWindow i) (imageViewport i) (imageLocation i) (sheetOffset s) qs = qs0 ++ qs1 todo = S.filter (`M.notMember` tiles s) (S.fromList (map snd qs)) in (s{ viewQuads = vq, sheetCountTarget = length qs }, todo) -- cancel in-progress jobs that aren't still needed _ <- takeMVar (jobs s') p <- atomicModifyIORef sR $ \s -> (s{ progress = M.filter (`S.member` todo') (progress s) }, progress s) mapM_ (`poke` 1) . filter (\w -> case M.lookup w p of Nothing -> False Just q -> q `S.notMember` todo') . workers $ s' -- set new jobs s <- readIORef sR let Just q0 = originQuad (imageLocation (image s)) (sheetSize s) score = quadDistance q0 putMVar (jobs s') . sortBy (comparing score) . S.toList $ todo' \\ S.fromList (M.elems p) putTile :: IORef GruffImage -> Ptr CInt -> Tile -> IO () putTile sR p t = do qu <- atomicModifyIORef sR $ \s -> (s{ progress = M.delete p (progress s) }, queue s) ts <- takeMVar qu putMVar qu (t:ts) putJobs :: IORef GruffImage -> [Quad] -> IO () putJobs sR qs = do s <- readIORef sR _ <- takeMVar (jobs s) putMVar (jobs s) qs takeJob :: IORef GruffImage -> Ptr CInt -> IO Quad takeJob sR p = do s <- readIORef sR qs <- takeMVar (jobs s) case qs of [] -> do putMVar (jobs s) [] threadDelay 10000 takeJob sR p (q:qs') -> do atomicModifyIORef sR $ \s' -> ( s'{ progress = M.insert p q (progress s') }, ()) putMVar (jobs s) qs' return q worker :: IORef GruffImage -> Ptr CInt -> IO () worker sR p = forever $ do s <- readIORef sR q <- takeJob sR p mt <- getTile (log s Debug) (cacheDir s) p q case mt of Nothing -> return () Just t -> putTile sR p t timer :: IORef GruffImage -> IO () timer sR = do s <- readIORef sR mtls <- tryTakeMVar (queue s) case mtls of Just tls -> do putMVar (queue s) tls unless (null tls) $ postRedisplay (gl s) _ -> return () upload :: Ptr CFloat -> IO TextureObject upload p = do checkError "upload begin" [tex] <- genObjectNames 1 checkError "upload 1" texture Texture2D $= Enabled checkError "upload 2" textureBinding Texture2D $= Just tex checkError "upload 3" glTexImage2D gl_TEXTURE_2D 0 (fromIntegral gl_LUMINANCE32F) (fromIntegral tileSize) (fromIntegral tileSize) 0 gl_LUMINANCE gl_FLOAT p checkError "upload 4" textureFilter Texture2D $= ((Nearest, Nothing), Nearest) checkError "upload 5" textureWrapMode Texture2D GL.S $= (Repeated, ClampToEdge) checkError "upload 6" textureWrapMode Texture2D GL.T $= (Repeated, ClampToEdge) checkError "upload 7" textureBinding Texture2D $= Nothing checkError "upload 7" texture Texture2D $= Disabled checkError "upload end" return tex msPerFrame :: Int msPerFrame = 200 data Browser = Browser { browserWindow :: GTK.Window , browserGL :: GLUTGtk , browserRender :: Image -> IO () -> IO () -> IO () , browserAbort :: IO () , browserSaveImage :: FilePath -> IO () , browserSetExitCallback :: IO () -> IO () , browserSetReshapeCallback :: (Int -> Int -> IO ()) -> IO () , browserSetMouseCallback :: MouseCallbacks -> IO () } browserRenders :: Browser -> [(Image, FilePath)] -> IO () browserRenders _ [] = print "done" browserRenders b ((i, f):ifs) = do result <- newEmptyMVar postGUISync $ do browserRender b i (browserSaveImage b f >> putMVar result True) (putMVar result False) postRedisplay (browserGL b) r <- takeMVar result if r then browserRenders b ifs else print "aborted" browserNew :: GLUTGtk -> Pixbuf -> Logger -> FilePath -> IO Browser browserNew gl' icon lg cacheDir' = do -- image window iw <- windowNew let defW = width defWindow defH = height defWindow windowSetDefaultSize iw defW defH windowSetGeometryHints iw (Nothing `asTypeOf` Just iw) (Just (160, 120)) (Just (4096, 4096)) Nothing Nothing Nothing set iw [ containerBorderWidth := 0 , containerChild := widget gl' , windowIcon := Just icon , windowTitle := "gruff browser" ] queue' <- newMVar [] jobs' <- newMVar [] iR <- newIORef GruffImage -- image parameters { image = defImage -- callbacks , exitCallback = Nothing , Browser.reshapeCallback = Nothing , mouseCallback = M.empty , doneCallback = Nothing , abortCallback = Nothing -- job queue , tiles = M.empty , queue = queue' , jobs = jobs' , progress = M.empty , viewQuads = ([], []) , gl = gl' , cacheDir = cacheDir' , log = Log.log lg , workers = [] , prog = error "prog" , fbo = 0 , combineProg = error "combineProg" , tsheet0 = TextureObject 0 , tsheet1 = TextureObject 0 , sheetCount0 = 0 , sheetCount1 = 0 , sheetCountTarget = maxBound , cacheSizeMin = 1000 , cacheSizeMax = 1500 , doClear = False , font = error "font" } realizeCallback gl' $= iRealize iR GLUTGtk.reshapeCallback gl' $= iReshape iR Nothing displayCallback gl' $= iDisplay iR let browserAbort' = do i <- readIORef iR putJobs iR [] mapM_ (`poke` 1) . workers $ i act <- atomicModifyIORef iR $ \s -> (s { progress = M.empty , doneCallback = Nothing , abortCallback = Nothing }, abortCallback s) fromMaybe (return ()) act atExit = do i <- readIORef iR case exitCallback i of Nothing -> return () Just act -> act browserRender' img done aborted = do s <- readIORef iR let wr = width (imageWindow img) hr = height (imageWindow img) unless (width (imageWindow (image s)) == wr && height (imageWindow (image s)) == hr) $ do windowResize iw wr hr atomicModifyIORef iR $ \s' -> (s'{ image = img }, ()) s' <- readIORef iR unless (sheetSize s == sheetSize s') (reallocateBuffers iR) atomicModifyIORef iR $ \i -> (i { doneCallback = Just done , abortCallback = Just aborted , doClear = True }, ()) update iR browserSaveImage' fname = do s <- readIORef iR writeSnapshot fname (GL.Position 0 0) (GL.Size (fromIntegral (width (imageWindow (image s)))) (fromIntegral (height (imageWindow (image s))))) browserSetExitCallback' act = atomicModifyIORef iR $ \i -> ( i{ exitCallback = Just act }, () ) browserSetReshapeCallback' act = atomicModifyIORef iR $ \i -> ( i{ Browser.reshapeCallback = Just act }, () ) browserSetMouseCallback' act = atomicModifyIORef iR $ \i -> ( i{ mouseCallback = act }, () ) keyboardMouseCallback gl' $= iMouse iR _ <- timeoutAdd (timer iR >> return True) msPerFrame _ <- iw `onDestroy` atExit return Browser { browserWindow = iw , browserGL = gl' , browserRender = browserRender' , browserAbort = browserAbort' , browserSaveImage = browserSaveImage' , browserSetExitCallback = browserSetExitCallback' , browserSetReshapeCallback = browserSetReshapeCallback' , browserSetMouseCallback = browserSetMouseCallback' } fromColours :: Colours -> (Color3 GLfloat, Color3 GLfloat, Color3 GLfloat) fromColours c = ( fromColour (colourInterior c) , fromColour (colourBoundary c) , fromColour (colourExterior c) ) fromColour :: Colour -> Color3 GLfloat fromColour (Colour r g b) = Color3 (realToFrac r) (realToFrac g) (realToFrac b) iRealize :: IORef GruffImage -> IO () iRealize iR = do checkError "realize begin" s <- readIORef iR log s Debug "realizeCallback" drawBuffer $= BackBuffers checkError "realize db" glClampColor gl_CLAMP_VERTEX_COLOR gl_FALSE checkError "realize clv" glClampColor gl_CLAMP_READ_COLOR gl_FALSE checkError "realize clr" glClampColor gl_CLAMP_FRAGMENT_COLOR gl_FALSE checkError "realize clf" f <- getDataFileName "minimal.frag" prog' <- shader Nothing (Just f) checkError "realize s1" f' <- getDataFileName "merge.frag" combineProg' <- shader Nothing (Just f') checkError "realize s2" fbo' <- newFBO checkError "realize fbo" [tsheet0', tsheet1'] <- genObjectNames 2 checkError "realize sheets" -- FIXME hardcoded font path font' <- createTextureFont "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono-Bold.ttf" -- FIXME destroyFont (font s) at exit _ <- setFontFaceSize font' 16 72 ps <- replicateM numCapabilities $ do p <- malloc _ <- forkIO (worker iR p) return p atomicModifyIORef iR $ \i -> (i { prog = prog' , workers = ps , fbo = fbo' , combineProg = combineProg' , tsheet0 = tsheet0' , tsheet1 = tsheet1' , sheetCount0 = 0 , sheetCount1 = 0 , sheetCountTarget = maxBound , font = font' }, ()) reallocateBuffers iR checkError "realize end" reallocateBuffers :: IORef GruffImage -> IO () reallocateBuffers iR = do checkError "reallocate begin" s <- readIORef iR let tw' = texels $ sheetSize s th' = tw' ts = [tsheet0 s, tsheet1 s] forM_ (ts `zip` [(tw', th'), (tw' * 2, th' * 2)]) $ \(t, (tw, th)) -> do texture Texture2D $= Enabled checkError "reallocate 1" textureBinding Texture2D $= Just t checkError "reallocate 2" glTexImage2D gl_TEXTURE_2D 0 (fromIntegral gl_RGBA) (fromIntegral tw) (fromIntegral th) 0 gl_RGBA gl_UNSIGNED_BYTE nullPtr checkError "reallocate 3" textureFilter Texture2D $= ((Linear', Just Linear'), Linear') checkError "reallocate 4" textureWrapMode Texture2D GL.S $= (Repeated, ClampToEdge) checkError "reallocate 5" textureWrapMode Texture2D GL.T $= (Repeated, ClampToEdge) checkError "reallocate 6" textureBinding Texture2D $= Nothing checkError "reallocate 7" texture Texture2D $= Disabled checkError "reallocate 8" atomicModifyIORef iR $ \s' -> (s'{ sheetCount0 = 0, sheetCount1 = 0, sheetCountTarget = maxBound }, ()) checkError "reallocate end" newFBO :: IO GLuint newFBO = alloca $ \p -> glGenFramebuffers 1 p >> peek p bindFBO :: GLuint -> TextureObject -> IO () bindFBO f (TextureObject t) = do checkError "bindFBO begin" glBindFramebuffer gl_FRAMEBUFFER f checkError "bindFBO 1" glFramebufferTexture2D gl_FRAMEBUFFER gl_COLOR_ATTACHMENT0 gl_TEXTURE_2D t 0 checkError "bindFBO 2" err <- glCheckFramebufferStatus gl_FRAMEBUFFER when (err /= gl_FRAMEBUFFER_COMPLETE) $ putStrLn ("OpenGL framebuffer error: " ++ show err) checkError "bindFBO end" unbindFBO :: IO () unbindFBO = do checkError "unbindFBO begin" glFramebufferTexture2D gl_FRAMEBUFFER gl_COLOR_ATTACHMENT0 gl_TEXTURE_2D 0 0 checkError "unbindFBO 1" glBindFramebuffer gl_FRAMEBUFFER 0 checkError "unbindFBO end" checkError :: String -> IO () checkError s = do err <- glGetError when (err /= 0) $ putStrLn ("OpenGL error (" ++ s ++ "): " ++ show err)