{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE CPP #-} module Call ( runCallDefault , readBitmap -- * The system , Call , Foundation , runCall , WindowMode(..) -- * Time , stand , wait , getTime , setFPS , getSlowdown , getFPS -- * Raw input , keyPress , mousePosition , mouseButton , enableCursor , hideCursor , disableCursor , getGamepads , gamepadButtons , gamepadAxes -- * Component , linkGraphic , linkPicture , linkAudio , linkKeyboard , linkMouse , linkGamepad -- * Others , setTitle , clearColor , getBoundingBox , setBoundingBox , takeScreenshot -- * Reexports , module Data.Audio , module Data.Graphics , module Data.Input.Event , module Control.Monad , module Control.Applicative , module Control.Bool , module Data.Monoid , module Data.Color , module Data.Color.Names , module Linear , module Control.Object , module Control.Monad.IO.Class ) where import Control.Applicative import Control.Bool import Control.Concurrent import Control.Exception import Control.Lens import Control.Monad hiding (sequence, sequence_, mapM, mapM_, forM, forM_, msum) import Control.Monad.IO.Class import Control.Object import Data.Audio import Data.BoundingBox import Data.Color import Data.Color.Names import Data.Traversable as T import Data.Foldable as F hiding (foldr) import Data.Typeable import Data.Graphics as U import Data.Graphics import Data.Graphics.Bitmap as Bitmap import Data.Input.Event import Data.IORef import Data.Maybe import Data.Monoid import Data.Reflection import Foreign (castPtr, sizeOf, with) import Graphics.Rendering.OpenGL.GL.StateVar import Linear import qualified Call.Internal.GLFW as G import qualified Call.Internal.PortAudio as PA import qualified Codec.Picture as C import qualified Data.Map.Strict as Map import qualified Data.IntMap.Strict as IM import qualified Data.Vector.Storable as V import qualified Graphics.Rendering.OpenGL.GL as GL import qualified Graphics.Rendering.OpenGL.Raw as GL import qualified Graphics.UI.GLFW as GLFW import Unsafe.Coerce data WindowMode = Windowed | Resizable | FullScreen deriving (Show, Eq, Ord, Read, Typeable) runCallDefault :: (Call => IO a) -> IO (Maybe a) runCallDefault = runCall Windowed (Box (V2 0 0) (V2 640 480)) readBitmap :: MonadIO m => FilePath -> m Bitmap.Bitmap readBitmap = Bitmap.readFile setFPS :: Call => Float -> IO () setFPS f = writeIORef (targetFPS given) f -- | @(Actual FPS) = (Target FPS) / (1 + (Slowdown))@ getFPS :: Call => IO Float getFPS = do t <- readIORef (targetFPS given) r <- getSlowdown return $ t / (1 + r) type Call = Given Foundation linkGraphic :: Call => (Time -> IO Sight) -> IO () linkGraphic f = do g <- readIORef $ coreGraphic given writeIORef (coreGraphic given) $ \dt -> liftA2 (<>) (f dt) (g dt) linkPicture :: Call => (Time -> IO Picture) -> IO () linkPicture f = linkGraphic (fmap viewPicture . f) linkAudio :: Call => (Time -> Int -> IO (V.Vector Stereo)) -> IO () linkAudio f = do g <- readIORef $ coreAudio given writeIORef (coreAudio given) $ \dt n -> liftA2 (V.zipWith (+)) (f dt n) (g dt n) linkKeyboard :: Call => (Chatter Key -> IO ()) -> IO () linkKeyboard f = do g <- readIORef $ coreKeyboard given writeIORef (coreKeyboard given) $ \k -> f k >> g k linkMouse :: Call => (MouseEvent -> IO ()) -> IO () linkMouse f = do g <- readIORef $ coreMouse given writeIORef (coreMouse given) $ \k -> f k >> g k linkGamepad :: Call => (GamepadEvent -> IO ()) -> IO () linkGamepad f = do g <- readIORef $ coreJoypad given writeIORef (coreJoypad given) $ \k -> f k >> g k data Foundation = Foundation { sampleRate :: Float , coreGraphic :: IORef (Time -> IO Sight) , coreAudio :: IORef (Time -> Int -> IO (V.Vector (V2 Float))) , coreKeyboard :: IORef (Chatter Key -> IO ()) , coreMouse :: IORef (MouseEvent -> IO ()) , coreJoypad :: IORef (GamepadEvent -> IO ()) , theTime :: MVar Time , theSystem :: G.System , targetFPS :: IORef Float , textures :: IORef (IM.IntMap G.Texture) , theEnd :: MVar () , theGamepadButtons :: IORef (IM.IntMap (String, IM.IntMap Bool)) , slowdown :: IORef (Map.Map Time Time) } runCall :: WindowMode -> Box V2 Float -> (Call => IO a) -> IO (Maybe a) runCall mode box m = do sys <- G.beginGLFW (mode == FullScreen) (mode == Resizable) box fd <- Foundation <$> pure 44100 -- FIX THIS <*> newIORef (const $ return mempty) <*> newIORef (\_ n -> return $ V.replicate n zero) <*> newIORef (const $ return ()) <*> newIORef (const $ return ()) <*> newIORef (const $ return ()) <*> newMVar 0 <*> pure sys <*> newIORef 60 <*> newIORef IM.empty <*> newEmptyMVar <*> newIORef IM.empty <*> newIORef Map.empty let win = G.theWindow sys give fd $ do GLFW.setKeyCallback win $ Just keyCallback GLFW.setMouseButtonCallback win $ Just mouseButtonCallback GLFW.setCursorPosCallback win $ Just cursorPosCallback GLFW.setScrollCallback win $ Just scrollCallback GL.UniformLocation loc <- GL.get $ GL.uniformLocation (G.theProgram sys) "color" with (V4 1 1 1 1 :: V4 Float) $ \ptr -> GL.glUniform4fv loc 1 (castPtr ptr) print =<< GLFW.getWindowClientAPI win putStr "OpenGL Version: " cv0 <- GLFW.getWindowContextVersionMajor win cv1 <- GLFW.getWindowContextVersionMinor win cv2 <- GLFW.getWindowContextVersionRevision win putStrLn $ show cv0 ++ "." ++ show cv1 ++ "." ++ show cv2 print =<< GLFW.getWindowContextRobustness win putStr "Forward compat: " print =<< GLFW.getWindowOpenGLForwardCompat win putStr "Debug context: " print =<< GLFW.getWindowOpenGLDebugContext win print =<< GLFW.getWindowOpenGLProfile win ref <- newEmptyMVar _ <- flip forkFinally (either throwIO (putMVar ref)) (give fd m) give fd $ PA.with 44100 512 audioProcess $ liftIO $ do GLFW.setTime 0 runGraphic 0 G.endGLFW sys tryTakeMVar ref stand :: Call => IO () stand = takeMVar (theEnd given) wait :: Call => Time -> IO () wait dt = do t0 <- takeMVar (theTime given) Just t <- GLFW.getTime threadDelay $ floor $ (t0 - realToFrac t + dt) * 1000 * 1000 putMVar (theTime given) $ t0 + dt getTime :: Call => IO Time getTime = readMVar (theTime given) keyPress :: Call => Key -> IO Bool keyPress k = fmap (/=GLFW.KeyState'Released) $ GLFW.getKey (G.theWindow $ theSystem given) (toEnum . fromEnum $ k) mousePosition :: Call => IO (V2 Float) mousePosition = do (x, y) <- GLFW.getCursorPos (G.theWindow $ theSystem given) return $ V2 (realToFrac x) (realToFrac y) hideCursor :: Call => IO () hideCursor = GLFW.setCursorInputMode (G.theWindow $ theSystem given) GLFW.CursorInputMode'Hidden disableCursor :: Call => IO () disableCursor = GLFW.setCursorInputMode (G.theWindow $ theSystem given) GLFW.CursorInputMode'Disabled enableCursor :: Call => IO () enableCursor = GLFW.setCursorInputMode (G.theWindow $ theSystem given) GLFW.CursorInputMode'Normal mouseButton :: Int -> Call => IO Bool mouseButton b = fmap (/=GLFW.MouseButtonState'Released) $ GLFW.getMouseButton (G.theWindow $ theSystem given) (toEnum b) getGamepads :: Call => IO [Gamepad] getGamepads = fmap catMaybes $ forM [(GLFW.Joystick'1)..] $ \j -> fmap (Gamepad (fromEnum j)) <$> GLFW.getJoystickName j gamepadAxes :: Call => Gamepad -> IO [Float] gamepadAxes (Gamepad i _) = maybe [] (map realToFrac) <$> GLFW.getJoystickAxes (toEnum i) gamepadButtons :: Call => Gamepad -> IO [Bool] gamepadButtons (Gamepad i _) = maybe [] (map (==GLFW.JoystickButtonState'Pressed)) <$> GLFW.getJoystickButtons (toEnum i) clearColor :: Call => V4 Float -> IO () clearColor (V4 r g b a) = liftIO $ GL.clearColor $= GL.Color4 (realToFrac r) (realToFrac g) (realToFrac b) (realToFrac a) setBoundingBox :: Call => Box V2 Float -> IO () setBoundingBox box@(Box (V2 x0 y0) (V2 x1 y1)) = do GLFW.setWindowSize (G.theWindow $ theSystem given) (floor (x1 - x0)) (floor (y1 - y0)) writeIORef (G.refRegion $ theSystem given) box getBoundingBox :: Call => IO (Box V2 Float) getBoundingBox = readIORef (G.refRegion $ theSystem given) takeScreenshot :: Call => IO Bitmap takeScreenshot = G.screenshot (theSystem given) >>= liftImage' setTitle :: Call => String -> IO () setTitle str = GLFW.setWindowTitle (G.theWindow $ theSystem given) str pollGamepad :: Call => IO () pollGamepad = do m <- readIORef (coreJoypad given) ps <- IM.fromList <$> map (\p@(Gamepad i _) -> (i, p)) <$> getGamepads bs0 <- readIORef (theGamepadButtons given) bs0' <- forM (IM.toList $ ps IM.\\ bs0) $ \(i, p@(Gamepad _ s)) -> do m $ PadConnection $ Up p return (i, (s, IM.empty)) bs0_ <- forM (IM.toList $ bs0 IM.\\ ps) $ \(i, (s, _)) -> do m $ PadConnection $ Down $ Gamepad i s return (i, ()) let bs1 = bs0 `IM.union` IM.fromList bs0' IM.\\ IM.fromList bs0_ ls <- forM (IM.toList ps) $ \(j, p@(Gamepad _ s)) -> do bs <- zip [0..] <$> gamepadButtons p forM_ bs $ \(i, v) -> case (v, maybe False id (bs1 ^? ix j . _2 . ix i)) of (False, True) -> m $ PadButton p (Up i) (True, False) -> m $ PadButton p (Down i) _ -> return () return (j, (s, IM.fromList bs)) writeIORef (theGamepadButtons given) $ foldr (uncurry IM.insert) bs1 ls getSlowdown :: Call => IO Float getSlowdown = do m <- readIORef (slowdown given) Just t0 <- GLFW.getTime return $ F.sum m runGraphic :: Call => Time -> IO () runGraphic t0 = do pollGamepad fps <- readIORef (targetFPS given) G.beginFrame (theSystem given) m <- readIORef (coreGraphic given) pic <- m (1/fps) -- is it appropriate? drawSight pic b <- G.endFrame (theSystem given) Just t <- fmap (fmap realToFrac) $ GLFW.getTime case t0 + 1/fps - realToFrac t of dt | dt > 0 -> threadDelay $ floor $ dt * 1000 * 1000 | otherwise -> modifyIORef' (slowdown given) $ \m -> m & at t ?~ negate dt & Map.split (t - 1) & snd tryTakeMVar (theEnd given) >>= \case Just _ -> return () _ | b -> putMVar (theEnd given) () | otherwise -> runGraphic (max t (t0 + 1/fps)) audioProcess :: Call => Int -> IO (V.Vector Stereo) audioProcess n = do let dt = fromIntegral n / sampleRate given m <- readIORef (coreAudio given) m dt n keyCallback :: Call => GLFW.KeyCallback keyCallback _ k _ st _ = do m <- readIORef (coreKeyboard given) m $ case st of GLFW.KeyState'Released -> Up (toEnum . fromEnum $ k :: Key) _ -> Down (toEnum . fromEnum $ k :: Key) mouseButtonCallback :: Call => GLFW.MouseButtonCallback mouseButtonCallback _ btn st _ = do m <- readIORef (coreMouse given) m $ case st of GLFW.MouseButtonState'Released -> Button $ Up (fromEnum btn) _ -> Button $ Down (fromEnum btn) cursorPosCallback :: Call => GLFW.CursorPosCallback cursorPosCallback _ x y = do m <- readIORef (coreMouse given) m $ Cursor $ fmap realToFrac $ V2 x y scrollCallback :: Call => GLFW.ScrollCallback scrollCallback _ x y = do m <- readIORef (coreMouse given) m $ Scroll $ fmap realToFrac $ V2 x y fetchTexture :: Call => C.Image C.PixelRGBA8 -> Int -> IO G.Texture fetchTexture bmp h = do st <- readIORef (textures given) case IM.lookup h st of Just t -> return t Nothing -> do t <- G.installTexture bmp writeIORef (textures given) $ IM.insert h t st return t drawScene :: Call => Box V2 Float -> M44 Float -> Bool -> Scene -> IO () drawScene (fmap round -> Box (V2 x0 y0) (V2 x1 y1)) proj _ (Scene s) = do GL.viewport $= (GL.Position x0 y0, GL.Size (x1 - x0) (y1 - y0)) GL.currentProgram $= Just shaderProg GL.UniformLocation loc <- GL.get (GL.uniformLocation shaderProg "projection") with proj $ \ptr -> GL.glUniformMatrix4fv loc 1 1 $ castPtr ptr GL.UniformLocation locTextureMix <- GL.get $ GL.uniformLocation shaderProg "textureMix" GL.UniformLocation locMats <- GL.get $ GL.uniformLocation shaderProg "matrices" GL.UniformLocation locLevel <- GL.get $ GL.uniformLocation shaderProg "level" GL.UniformLocation locDiffuse <- GL.get $ GL.uniformLocation shaderProg "diffuse" with (V4 1 1 (1 :: Float) 1) $ \ptr -> GL.glUniform4fv locDiffuse 1 (castPtr ptr) s (pure $ return ()) (liftA2 (>>)) (prim locTextureMix) (fx locDiffuse) (trans locMats locLevel) (V4 1 1 1 1, 0) where shaderProg = G.theProgram $ theSystem given prim locTextureMix Blank mode vs _ = do GL.glUniform1f locTextureMix 0 V.unsafeWith vs $ \v -> GL.bufferData GL.ArrayBuffer $= (fromIntegral $ V.length vs * sizeOf (undefined :: Vertex), v, GL.StaticDraw) GL.drawArrays (convPrimitiveMode mode) 0 $ fromIntegral $ V.length vs prim locTextureMix (Bitmap bmp _ h) mode vs _ = do GL.glUniform1f locTextureMix 1 (tex, _, _) <- fetchTexture bmp h GL.activeTexture $= GL.TextureUnit 0 GL.textureBinding GL.Texture2D $= Just tex GL.textureFilter GL.Texture2D $= ((GL.Linear', Nothing), GL.Linear') V.unsafeWith vs $ \v -> GL.bufferData GL.ArrayBuffer $= (fromIntegral $ V.length vs * sizeOf (undefined :: Vertex), v, GL.StaticDraw) GL.drawArrays (convPrimitiveMode mode) 0 $ fromIntegral $ V.length vs trans locMats locLevel f m (color0, n) = do with f $ \ptr -> GL.glUniformMatrix4fv (locMats + n) 1 1 (castPtr ptr) GL.glUniform1i locLevel (unsafeCoerce $ n + 1) () <- m (color0, n + 1) GL.glUniform1i locLevel (unsafeCoerce n) fx locDiffuse (EmbedIO m) c = m >>= ($ c) fx locDiffuse (Diffuse col m) (color0, n) = do let c = col * color0 with c $ \ptr -> GL.glUniform4fv locDiffuse 1 (castPtr ptr) m (c, n) with color0 $ \ptr -> GL.glUniform4fv locDiffuse 1 (castPtr ptr) drawSight :: Call => Sight -> IO () drawSight (Sight s) = do b <- readIORef $ G.refRegion $ theSystem given s b (return ()) (>>) drawScene convPrimitiveMode :: U.PrimitiveMode -> GL.PrimitiveMode convPrimitiveMode U.LineStrip = GL.LineStrip convPrimitiveMode U.TriangleFan = GL.TriangleFan convPrimitiveMode U.TriangleStrip = GL.TriangleStrip convPrimitiveMode U.LineLoop = GL.LineLoop