module Caramia.Context
(
giveContext
, currentContextID
, ContextID
, runPendingFinalizers
, scheduleFinalizer
, storeContextLocalData
, retrieveContextLocalData
, TooOldOpenGL(..) )
where
import Caramia.Prelude
import Caramia.Internal.ContextLocalData
import Caramia.Internal.OpenGLDebug
import Caramia.Internal.FlextGL
import Control.Concurrent
import Control.Exception
import System.IO.Unsafe
import System.Environment
import Graphics.Rendering.OpenGL.Raw.GetProcAddress
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal.Alloc
import qualified Data.Map.Strict as M
import qualified Data.IntMap.Strict as IM
data TooOldOpenGL = TooOldOpenGL
{ wantedVersion :: (Int, Int)
, reportedVersion :: (Int, Int)
}
deriving ( Eq, Show, Read, Typeable )
instance Exception TooOldOpenGL
giveContext :: IO a -> IO a
giveContext action = mask $ \restore -> do
is_bound_thread <- isCurrentThreadBound
unless is_bound_thread $
error $ "giveContext: current thread is not bound. How can it have " <>
"an OpenGL context?"
flextInit (\str -> castFunPtrToPtr <$> getProcAddress str)
(\_ -> return True) >>= \case
f@(Failure _) -> throwIO f
_ -> return ()
checkOpenGLVersion33
cid <- atomicModifyIORef' nextContextID $ \old -> ( old+1, old )
tid <- myThreadId
atomicModifyIORef' runningContexts $ \old_map ->
( M.insert tid cid old_map, () )
finally (restore insides) (flushDebugMessages >> scrapContext)
where
insides = do
should_activate_debug_mode <- isJust <$> lookupEnv "CARAMIA_OPENGL_DEBUG"
when should_activate_debug_mode activateDebugMode
glEnable gl_FRAMEBUFFER_SRGB
glEnable gl_BLEND
action
checkOpenGLVersion33 :: IO ()
checkOpenGLVersion33 =
#ifdef MAC_OPENGL
return ()
#else
alloca $ \major_ptr -> alloca $ \minor_ptr -> do
poke major_ptr 0
poke minor_ptr 0
glGetIntegerv gl_MAJOR_VERSION major_ptr
glGetIntegerv gl_MAJOR_VERSION minor_ptr
major <- peek major_ptr
minor <- peek minor_ptr
unless (major > 3 ||
(major == 3 && minor >= 3)) $
throwIO
TooOldOpenGL { wantedVersion = (3, 3)
, reportedVersion = ( fromIntegral major
, fromIntegral minor )
}
#endif
scrapContext :: IO ()
scrapContext = mask_ $ do
maybe_cid <- currentContextID
tid <- myThreadId
case maybe_cid of
Nothing -> return ()
Just cid -> do
atomicModifyIORef' runningContexts $ \old_map ->
( M.delete tid old_map, () )
atomicModifyIORef' pendingFinalizers $ \old_map ->
( IM.delete cid old_map, () )
atomicModifyIORef' contextLocalData $ \old_map ->
( IM.delete cid old_map, () )
runPendingFinalizers :: IO ()
runPendingFinalizers = mask_ $ do
maybe_cid <- currentContextID
case maybe_cid of
Nothing -> return ()
Just cid -> do
finalizers <- atomicModifyIORef' pendingFinalizers $
IM.delete cid &&&
IM.findWithDefault (return ())
cid
result <- try finalizers
case result of
Left exc -> scrapContext >> throwIO (exc :: SomeException)
Right () -> return ()
flushDebugMessages
scheduleFinalizer :: ContextID -> IO () -> IO ()
scheduleFinalizer cid finalizer =
atomicModifyIORef' pendingFinalizers $ \old ->
( IM.insertWith
(flip (>>))
cid
finalizer
old, () )
pendingFinalizers :: IORef (IM.IntMap (IO ()))
pendingFinalizers = unsafePerformIO $ newIORef IM.empty