module CacheView (cInitialize) where import Control.Concurrent (forkIO) import Control.Monad (forM_, liftM2, when) import Data.IORef (IORef, newIORef, readIORef, atomicModifyIORef) import qualified Data.Map as M import Data.Map (Map) import Data.Maybe (catMaybes, mapMaybe) import System.FilePath ((), dropExtension) import Foreign (alloca, peek, nullPtr) import Graphics.Rendering.OpenGL hiding (Position, Size) import qualified Graphics.Rendering.OpenGL as GL import Graphics.Rendering.OpenGL.Raw ( glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D , gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, gl_TEXTURE_2D , glTexImage2D, gl_R32F, gl_LUMINANCE, gl_FLOAT , gl_FALSE, glClampColor, gl_CLAMP_VERTEX_COLOR, gl_CLAMP_READ_COLOR , gl_CLAMP_FRAGMENT_COLOR ) import Graphics.UI.Gtk hiding (Region, Size) import Numeric.QD (QuadDouble) import GLUTGtk import Shader (shader) import QuadTree (Child(..), unsafeName, Quad, Square(..), square, child, root, outside, Region(..)) --import Tile (rootSquare) import Utils (getFilesRecursive) import Paths_gruff (getDataFileName) rootSquare :: Square rootSquare = Square{ squareSize = 8, squareWest = -4, squareNorth = -4 } names :: Map Char [Child] names = M.fromList names1 `M.union` M.fromList names2 where names1 = [ (unsafeName [i], [i]) | i <- [minBound..maxBound] ] names2 = [ (unsafeName [i,j], [i,j]) | i <- [minBound..maxBound], j <- [minBound..maxBound] ] fromPath :: ([FilePath], FilePath) -> Maybe [Child] fromPath (ps, f) = let s = concat ps ++ dropExtension f p = concat $ mapMaybe (`M.lookup` names) s in if all (`M.member` names) s then Just p else Nothing cDisplay :: IORef GruffCache -> IO () cDisplay cR = do c <- readIORef cR let Size w h = cSize c TextureObject tex = cTex c when (cRecalc c) $ do viewport $= (GL.Position 0 0, GL.Size (fromIntegral w) (fromIntegral h)) loadIdentity ortho2D 0 (fromIntegral w) (fromIntegral h) 0 withFBO (cFBO c) tex $ do clearColor $= Color4 0 0 0 1 clear [ColorBuffer] let toPixel' = toPixel c qs = cQuads c zm = fromIntegral . snd . head $ qs withBlend (One, One) $ renderPrimitive Quads $ forM_ qs $ \(q, z) -> do let sq = square rootSquare q s = squareSize sq `max` cSquareSize c (x0, y0) = toPixel' (squareWest sq) (squareNorth sq) (x1, y1) = toPixel' (squareWest sq + s) (squareNorth sq + s) v x y = vertex $ Vertex2 (fromRational x :: GLdouble) (fromRational y :: GLdouble) k = fromIntegral z / zm :: GLdouble color $ Color3 k k k v x0 y1 >> v x0 y0 >> v x1 y0 >> v x1 y1 atomicModifyIORef cR $ \c' -> (c'{ cRecalc = False }, ()) -- draw texture loadIdentity ortho2D 0 1 1 0 textureBinding Texture2D $= Just (cTex c) currentProgram $= Just (cProg c) lt <- GL.get $ uniformLocation (cProg c) "tex" uniform lt $= TexCoord1 (0 :: GLint) renderPrimitive Quads $ do let Size tw th = cTexSize c let tx = fromIntegral w / fromIntegral tw ty = fromIntegral h / fromIntegral th v :: GLdouble -> GLdouble -> IO () v x y = do texCoord $ TexCoord2 (tx * x) (ty * (1-y)) vertex $ Vertex2 x y v 0 1 >> v 0 0 >> v 1 0 >> v 1 1 currentProgram $= Nothing textureBinding Texture2D $= Nothing withFBO :: GLuint -> GLuint -> IO () -> IO () withFBO fbo tex act = do glBindFramebuffer gl_FRAMEBUFFER fbo glFramebufferTexture2D gl_FRAMEBUFFER gl_COLOR_ATTACHMENT0 gl_TEXTURE_2D tex 0 act glFramebufferTexture2D gl_FRAMEBUFFER gl_COLOR_ATTACHMENT0 gl_TEXTURE_2D 0 0 glBindFramebuffer gl_FRAMEBUFFER 0 withBlend :: (BlendingFactor, BlendingFactor) -> IO a -> IO a withBlend bf act = do ob <- GL.get blend obf <- GL.get blendFunc blend $= Enabled blendFunc $= bf r <- act blendFunc $= obf blend $= ob return r data GruffCache = GruffCache { cSize :: Size , cCenter :: (Rational, Rational) , cLevel :: Int , cCacheDir :: FilePath , cGL :: GLUTGtk , cProg :: Program , cTex :: TextureObject , cTexSize :: Size , cFBO :: GLuint , cRecalc :: Bool , cQTree :: QTree Integer } fromPixel :: GruffCache -> Int -> Int -> (Rational, Rational) fromPixel c x y = (x', y') where Size w h = cSize c a = fromIntegral w / fromIntegral h (cx, cy) = cCenter c r = 8 / 2 ^ cLevel c * fromIntegral h / (2 * fromIntegral tileSize) x' = cx + r * (fromIntegral x / fromIntegral w - 0.5) * a y' = cy + r * (fromIntegral y / fromIntegral h - 0.5) toPixel :: GruffCache -> Rational -> Rational -> (Rational, Rational) toPixel c = f where f x y = {-# SCC "toPixel'" #-} (x', y') where x' = ((x - cx) / r' + 0.5) * w' y' = ((y - cy) / r + 0.5) * h' Size w h = cSize c w' = fromIntegral w h' = fromIntegral h a = w' / h' (cx, cy) = cCenter c r = 8 / 2 ^ cLevel c * h' / (2 * fromIntegral tileSize) r' = a * r tileSize :: Int tileSize = 256 cRealize :: IORef GruffCache -> IO () cRealize cR = do f <- getDataFileName "cache.frag" prog <- shader Nothing (Just f) drawBuffer $= BackBuffers glClampColor gl_CLAMP_VERTEX_COLOR gl_FALSE glClampColor gl_CLAMP_READ_COLOR gl_FALSE glClampColor gl_CLAMP_FRAGMENT_COLOR gl_FALSE [tex] <- genObjectNames 1 texture Texture2D $= Enabled textureBinding Texture2D $= Just tex textureFilter Texture2D $= ((Nearest, Nothing), Nearest) textureWrapMode Texture2D S $= (Repeated, ClampToEdge) textureWrapMode Texture2D T $= (Repeated, ClampToEdge) textureBinding Texture2D $= Nothing texture Texture2D $= Disabled fbo <- alloca $ \p -> glGenFramebuffers 1 p >> peek p atomicModifyIORef cR $ \c -> (c{ cProg = prog, cFBO = fbo, cTex = tex }, ()) c <- readIORef cR cReshape cR (cSize c) cSquareSize :: GruffCache -> Rational cSquareSize c = squareSize rootSquare / 2 ^ cLevel c / (2 * fromIntegral tileSize) cInitialize :: GLUTGtk -> Pixbuf -> FilePath -> IO ( Window , Maybe QuadDouble -> Maybe QuadDouble -> Maybe QuadDouble -> IO () , IO () -> IO () ) cInitialize gl' icon cacheDir' = do -- image window cw <- windowNew let Size defW defH = defSize windowSetDefaultSize cw defW defH set cw [ containerBorderWidth := 0 , containerChild := widget gl' , windowIcon := Just icon , windowTitle := "gruff cache" ] cR <- newIORef GruffCache { cCenter = (0, 0) , cLevel = 0 , cSize = defSize , cQTree = QTree 0 Nothing Nothing Nothing Nothing , cCacheDir = cacheDir' , cGL = gl' , cProg = undefined , cFBO = 0 , cTex = TextureObject 0 , cTexSize = roundUpSize defSize , cRecalc = False } realizeCallback gl' $= cRealize cR reshapeCallback gl' $= cReshape cR displayCallback gl' $= cDisplay cR keyboardMouseCallback gl' $= cMouse cR let cUpdateCoords mre mim mz = do case liftM2 (,) mre mim of Just (r, i) -> atomicModifyIORef cR $ \s -> ( s { cCenter = (toRational r, negate $ toRational i) }, () ) Nothing -> return () case mz of Just z | z > 0 -> atomicModifyIORef cR $ \s -> ( s { cLevel = max 0 . min maxLevel . floor . negate . logBase 2 $ z }, () ) _ -> return () cUpdate cR True cInitializeLate aExit = do _ <- cw `onDestroy` aExit _ <- forkIO $ cRescan cR return () return (cw, cUpdateCoords, cInitializeLate) cRescan :: IORef GruffCache -> IO () cRescan cR = do c' <- readIORef cR fs <- getFilesRecursive (cCacheDir c' ".") let q = depthQ . treeQ . mapMaybe fromPath $ fs atomicModifyIORef cR $ \c -> (c{ cQTree = q }, ()) postGUISync (cUpdate cR True) maxLevel :: Int maxLevel = 200 defSize :: Size defSize = Size 320 200 cUpdate :: IORef GruffCache -> Bool -> IO () cUpdate cR rec = do c' <- readIORef cR atomicModifyIORef cR $ \c -> (c{ cRecalc = cRecalc c || rec }, ()) postRedisplay (cGL c') cReshape :: IORef GruffCache -> Size -> IO () cReshape iR size' = do c <- readIORef iR let tsize@(Size tw th) = roundUpSize size' textureBinding Texture2D $= Just (cTex c) glTexImage2D gl_TEXTURE_2D 0 (fromIntegral gl_R32F) (fromIntegral tw) (fromIntegral th) 0 gl_LUMINANCE gl_FLOAT nullPtr textureBinding Texture2D $= Nothing atomicModifyIORef iR $ \s -> (s{ cSize = size', cTexSize = tsize }, ()) cUpdate iR True cMouse :: IORef GruffCache -> Key -> KeyState -> [Modifier] -> Position -> IO () cMouse sR (MouseButton LeftButton ) Down _ (Position x y) = do atomicModifyIORef sR $ \s -> let level' = (cLevel s + 1) `min` maxLevel in ( s{ cCenter = fromPixel s (round x) (round y), cLevel = level' } , ()) cUpdate sR True cMouse sR (MouseButton MiddleButton) Down _ (Position x y) = do atomicModifyIORef sR $ \s -> ( s{ cCenter = fromPixel s (round x) (round y) } , ()) cUpdate sR True cMouse sR (MouseButton RightButton ) Down _ (Position x y) = do atomicModifyIORef sR $ \s -> let level' = (cLevel s - 1) `max` 0 in ( s{ cCenter = fromPixel s (round x) (round y), cLevel = level' } , ()) cUpdate sR True cMouse _ _ _ _ _ = return () roundUpSize :: Size -> Size roundUpSize (Size w h) = Size (roundUp w) (roundUp h) roundUp :: Int -> Int roundUp x = head . filter (>= x) . iterate (2*) $ 1 data QTree a = QTree a (Maybe (QTree a)) (Maybe (QTree a)) (Maybe (QTree a)) (Maybe (QTree a)) payloadQ :: QTree a -> a payloadQ (QTree w _ _ _ _) = w childQ :: Child -> QTree a -> Maybe (QTree a) childQ NorthWest (QTree _ a _ _ _) = a childQ NorthEast (QTree _ _ b _ _) = b childQ SouthWest (QTree _ _ _ c _) = c childQ SouthEast (QTree _ _ _ _ d) = d emptyQ :: QTree Bool emptyQ = QTree False Nothing Nothing Nothing Nothing insertQ :: [Child] -> QTree Bool -> QTree Bool insertQ [] (QTree _ a b c d) = QTree True a b c d insertQ (x:xs) (QTree w a b c d) = case x of NorthWest -> case a of Nothing -> QTree w (Just $ insertQ xs emptyQ) b c d Just a' -> QTree w (Just $ insertQ xs a' ) b c d NorthEast -> case b of Nothing -> QTree w a (Just $ insertQ xs emptyQ) c d Just b' -> QTree w a (Just $ insertQ xs b' ) c d SouthWest -> case c of Nothing -> QTree w a b (Just $ insertQ xs emptyQ) d Just c' -> QTree w a b (Just $ insertQ xs c' ) d SouthEast -> case d of Nothing -> QTree w a b c (Just $ insertQ xs emptyQ) Just d' -> QTree w a b c (Just $ insertQ xs d' ) treeQ :: [[Child]] -> QTree Bool treeQ = foldr insertQ emptyQ {- sizeQ :: QTree Bool -> QTree Integer sizeQ (QTree w a b c d) = QTree n a' b' c' d' where s@[a', b', c', d'] = fmap sizeQ `map` [a, b, c, d] n = (if w then 1 else 0) + (sum . map payloadQ . catMaybes) s -} depthQ :: QTree a -> QTree Integer depthQ = depthQ' 0 where depthQ' n (QTree _ Nothing Nothing Nothing Nothing) = QTree n Nothing Nothing Nothing Nothing depthQ' n (QTree _ a b c d) = QTree m a' b' c' d' where s@[a', b', c', d'] = fmap (depthQ' (n + 1)) `map` [a, b, c, d] m = maximum . map payloadQ . catMaybes $ s pruneQ :: Int -> QTree a -> QTree a pruneQ 0 (QTree w _ _ _ _) = QTree w Nothing Nothing Nothing Nothing pruneQ n (QTree w a b c d) = QTree w a' b' c' d' where [a', b', c', d'] = fmap (pruneQ (n - 1)) `map` [a, b, c, d] cQuads :: GruffCache -> [(Quad, Integer)] cQuads c = map (fmap payloadQ) . concat . quadsQ view . pruneQ (cLevel c + 12) . cQTree $ c where Size w h = cSize c ((bw,bn),(be,bs)) = ( fromPixel c 0 0 , fromPixel c (fromIntegral w) (fromIntegral h) ) view = Region { regionWest = bw, regionNorth = bn , regionEast = be, regionSouth = bs } quadsQ :: Region -> QTree a -> [[(Quad, QTree a)]] quadsQ view q = takeWhile (not . null) . iterate (filter keep . children') $ [(root, q)] where keep = not . outside view . square rootSquare . fst children' = catMaybes . liftM2 child' [minBound .. maxBound] child' c (quad, qtree) = (,) (child c quad) `fmap` childQ c qtree