{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE Strict #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -- | This module allows to load vulkan symbols at runtime. -- -- It is based on Vulkan API function -- -- that is a part of Vulkan core 1.0. -- Also, have a look at -- -- page to see other reasons to load symbols manually. -- -- All FFI functions are present in two variants: -- @xxx@ and @xxxSafe@, the names stand for @foreign import unsafe xxx@ -- @foreign import safe xxx@ respectively. -- In particular, that does not mean that @vkGetXxxProcSafe@ function cannot fail; -- it does error if the symbol is not present in the implementation! module Graphics.Vulkan.Marshal.Proc ( VulkanProc (..) , vkGetInstanceProc, vkGetInstanceProcSafe , vkLookupInstanceProc, vkLookupInstanceProcSafe , vkGetDeviceProc, vkGetDeviceProcSafe , vkLookupDeviceProc, vkLookupDeviceProcSafe , vkGetProc, vkGetProcSafe , vkLookupProc, vkLookupProcSafe -- * Re-export `Foreign.Ptr` , FunPtr, nullFunPtr ) where import Control.Monad (when) import Data.Void (Void) import Foreign.C.String (CString, peekCString) import Foreign.ForeignPtr (ForeignPtr, newForeignPtr, withForeignPtr) import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (FunPtr, nullFunPtr, nullPtr) import Foreign.Storable (peek) import GHC.Ptr (Ptr (..)) import GHC.TypeLits (Symbol) import System.IO.Unsafe (unsafePerformIO) import Graphics.Vulkan.Types.Handles (VkDevice, VkInstance) -- | Some of the vulkan functions defined in vulkan extensions are not -- available at the program linking time. -- These functions should be discovered at runtime. -- Vulkan api provides special functions for this, -- called @vkGetInstanceProcAddr@ and @vkGetDeviceProcAddr@. -- This class provides a simpler discovery mechanism based on that function. -- For example, you can get @vkCreateDebugReportCallbackEXT@ function -- as follows: -- -- > vkGetInstanceProc @VkCreateDebugReportCallbackEXT vkInstance class VulkanProc (proc :: Symbol) where -- | Haskell signature for the vulkan function type VkProcType proc -- | Name of the vulkan function vkProcSymbol :: CString -- | Convert C function pointer to an ordinary haskell function. -- Use unsafe FFI (@foreign import unsafe "dynamic" ...@). unwrapVkProcPtr :: FunPtr (VkProcType proc) -> VkProcType proc -- | Convert C function pointer to an ordinary haskell function. -- Use safe FFI (@foreign import safe "dynamic" ...@). unwrapVkProcPtrSafe :: FunPtr (VkProcType proc) -> VkProcType proc -------------------------------------------------------------------------------- -- Unsafe FFI version -------------------------------------------------------------------------------- -- | An alternative to @vkGetInstanceProcAddr@ with type inference -- and protection against typos. -- -- Note, this is an unsafe function; -- it does not check if the result of @vkGetInstanceProcAddr@ -- is a null function pointer. vkGetInstanceProc :: forall proc . VulkanProc proc => VkInstance -> IO (VkProcType proc) vkGetInstanceProc i = unwrapVkProcPtr @proc <$> c'vkGetInstanceProcAddr i (vkProcSymbol @proc) {-# INLINE vkGetInstanceProc #-} -- | An alternative to @vkGetInstanceProcAddr@ with type inference -- and protection against typos. vkLookupInstanceProc :: forall proc . VulkanProc proc => VkInstance -> IO (Maybe (VkProcType proc)) vkLookupInstanceProc i = f <$> c'vkGetInstanceProcAddr i (vkProcSymbol @proc) where f p = if p == nullFunPtr then Nothing else Just (unwrapVkProcPtr @proc p) {-# INLINE vkLookupInstanceProc #-} -- | An alternative to @vkGetDeviceProcAddr@ with type inference -- and protection against typos. -- -- Note, this is an unsafe function; -- it does not check if the result of @vkGetInstanceProcAddr@ -- is a null function pointer. vkGetDeviceProc :: forall proc . VulkanProc proc => VkDevice -> IO (VkProcType proc) vkGetDeviceProc i = unwrapVkProcPtr @proc <$> c'vkGetDeviceProcAddr i (vkProcSymbol @proc) {-# INLINE vkGetDeviceProc #-} -- | An alternative to @vkGetDeviceProcAddr@ with type inference -- and protection against typos. vkLookupDeviceProc :: forall proc . VulkanProc proc => VkDevice -> IO (Maybe (VkProcType proc)) vkLookupDeviceProc i = f <$> c'vkGetDeviceProcAddr i (vkProcSymbol @proc) where f p = if p == nullFunPtr then Nothing else Just (unwrapVkProcPtr @proc p) {-# INLINE vkLookupDeviceProc #-} -- | Locate Vulkan symbol dynamically at runtime using platform-dependent machinery, -- such as @dlsym@ or @GetProcAddress@. -- This function throws an error on failure. -- -- Consider using `vkGetDeviceProc` or `vkGetInstanceProc` for loading a symbol, -- because they can return a more optimized version of a function. -- Also note, you are likely not able to lookup an extension funcion using -- `vkGetProc`, because a corresponding symbol is simply not present in the -- vulkan loader library. vkGetProc :: forall proc . VulkanProc proc => IO (VkProcType proc) vkGetProc = alloca $ \errPtr -> do fp <- withForeignPtr _vkDlHandle $ \h -> c'vkdll_dlsym h (vkProcSymbol @proc) errPtr when (fp == nullFunPtr) $ peek errPtr >>= peekCString >>= fail . ("An error happened while trying to load vulkan symbol dynamically: " ++) return $ unwrapVkProcPtr @proc fp {-# INLINE vkGetProc #-} -- | Locate Vulkan symbol dynamically at runtime using platform-dependent machinery, -- such as @dlsym@ or @GetProcAddress@. -- This function returns @Nothing@ on failure ignoring an error message. -- -- Consider using `vkGetDeviceProc` or `vkGetInstanceProc` for loading a symbol, -- because they can return a more optimized version of a function. -- Also note, you are likely not able to lookup an extension funcion using -- `vkLookupProc`, because a corresponding symbol is simply not present in the -- vulkan loader library. vkLookupProc :: forall proc . VulkanProc proc => IO (Maybe (VkProcType proc)) vkLookupProc = alloca $ \errPtr -> do fp <- withForeignPtr _vkDlHandle $ \h -> c'vkdll_dlsym h (vkProcSymbol @proc) errPtr return $ if fp == nullFunPtr then Nothing else Just (unwrapVkProcPtr @proc fp) {-# INLINE vkLookupProc #-} -------------------------------------------------------------------------------- -- Safe FFI version -------------------------------------------------------------------------------- -- | An alternative to @vkGetInstanceProcAddr@ with type inference -- and protection against typos. -- -- Note, this is an unsafe function; -- it does not check if the result of @vkGetInstanceProcAddr@ -- is a null function pointer. vkGetInstanceProcSafe :: forall proc . VulkanProc proc => VkInstance -> IO (VkProcType proc) vkGetInstanceProcSafe i = unwrapVkProcPtrSafe @proc <$> c'vkGetInstanceProcAddr i (vkProcSymbol @proc) {-# INLINE vkGetInstanceProcSafe #-} -- | An alternative to @vkGetInstanceProcAddr@ with type inference -- and protection against typos. vkLookupInstanceProcSafe :: forall proc . VulkanProc proc => VkInstance -> IO (Maybe (VkProcType proc)) vkLookupInstanceProcSafe i = f <$> c'vkGetInstanceProcAddr i (vkProcSymbol @proc) where f p = if p == nullFunPtr then Nothing else Just (unwrapVkProcPtrSafe @proc p) {-# INLINE vkLookupInstanceProcSafe #-} -- | An alternative to @vkGetDeviceProcAddr@ with type inference -- and protection against typos. -- -- Note, this is an unsafe function; -- it does not check if the result of @vkGetInstanceProcAddr@ -- is a null function pointer. vkGetDeviceProcSafe :: forall proc . VulkanProc proc => VkDevice -> IO (VkProcType proc) vkGetDeviceProcSafe i = unwrapVkProcPtrSafe @proc <$> c'vkGetDeviceProcAddr i (vkProcSymbol @proc) {-# INLINE vkGetDeviceProcSafe #-} -- | An alternative to @vkGetDeviceProcAddr@ with type inference -- and protection against typos. vkLookupDeviceProcSafe :: forall proc . VulkanProc proc => VkDevice -> IO (Maybe (VkProcType proc)) vkLookupDeviceProcSafe i = f <$> c'vkGetDeviceProcAddr i (vkProcSymbol @proc) where f p = if p == nullFunPtr then Nothing else Just (unwrapVkProcPtrSafe @proc p) {-# INLINE vkLookupDeviceProcSafe #-} -- | Locate Vulkan symbol dynamically at runtime using platform-dependent machinery, -- such as @dlsym@ or @GetProcAddress@. -- This function throws an error on failure. -- -- Consider using `vkGetDeviceProc` or `vkGetInstanceProc` for loading a symbol, -- because they can return a more optimized version of a function. -- Also note, you are likely not able to lookup an extension funcion using -- `vkGetProc`, because a corresponding symbol is simply not present in the -- vulkan loader library. vkGetProcSafe :: forall proc . VulkanProc proc => IO (VkProcType proc) vkGetProcSafe = alloca $ \errPtr -> do fp <- withForeignPtr _vkDlHandle $ \h -> c'vkdll_dlsym h (vkProcSymbol @proc) errPtr when (fp == nullFunPtr) $ peek errPtr >>= peekCString >>= fail . ("An error happened while trying to load vulkan symbol dynamically: " ++) return $ unwrapVkProcPtrSafe @proc fp {-# INLINE vkGetProcSafe #-} -- | Locate Vulkan symbol dynamically at runtime using platform-dependent machinery, -- such as @dlsym@ or @GetProcAddress@. -- This function returns @Nothing@ on failure ignoring an error message. -- -- Consider using `vkGetDeviceProc` or `vkGetInstanceProc` for loading a symbol, -- because they can return a more optimized version of a function. -- Also note, you are likely not able to lookup an extension funcion using -- `vkLookupProc`, because a corresponding symbol is simply not present in the -- vulkan loader library. vkLookupProcSafe :: forall proc . VulkanProc proc => IO (Maybe (VkProcType proc)) vkLookupProcSafe = alloca $ \errPtr -> do fp <- withForeignPtr _vkDlHandle $ \h -> c'vkdll_dlsym h (vkProcSymbol @proc) errPtr return $ if fp == nullFunPtr then Nothing else Just (unwrapVkProcPtrSafe @proc fp) {-# INLINE vkLookupProcSafe #-} -------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- #ifdef VK_NO_PROTOTYPES c'vkGetInstanceProcAddr :: VkInstance -> CString -> IO (FunPtr a) c'vkGetInstanceProcAddr = unsafePerformIO $ alloca $ \errPtr -> do fp <- withForeignPtr _vkDlHandle $ \h -> c'vkdll_dlsym h (Ptr "vkGetInstanceProcAddr"#) errPtr when (fp == nullFunPtr) $ peek errPtr >>= peekCString >>= fail . ("Could not load 'vkGetInstanceProcAddr' C function from vulkan library dynamically: " ++) return $ unwrap'vkGetInstanceProcAddr fp c'vkGetDeviceProcAddr :: VkDevice -> CString -> IO (FunPtr a) c'vkGetDeviceProcAddr = unsafePerformIO $ alloca $ \errPtr -> do fp <- withForeignPtr _vkDlHandle $ \h -> c'vkdll_dlsym h (Ptr "vkGetDeviceProcAddr"#) errPtr when (fp == nullFunPtr) $ peek errPtr >>= peekCString >>= fail . ("Could not load 'vkGetDeviceProcAddr' C function from vulkan library dynamically: " ++) return $ unwrap'vkGetDeviceProcAddr fp foreign import ccall unsafe "dynamic" unwrap'vkGetInstanceProcAddr :: FunPtr (VkInstance -> CString -> IO (FunPtr a)) -> VkInstance -> CString -> IO (FunPtr a) foreign import ccall unsafe "dynamic" unwrap'vkGetDeviceProcAddr :: FunPtr (VkDevice -> CString -> IO (FunPtr a)) -> VkDevice -> CString -> IO (FunPtr a) #else foreign import ccall unsafe "vkGetInstanceProcAddr" c'vkGetInstanceProcAddr :: VkInstance -> CString -> IO (FunPtr a) foreign import ccall unsafe "vkGetDeviceProcAddr" c'vkGetDeviceProcAddr :: VkDevice -> CString -> IO (FunPtr a) #endif foreign import ccall safe "_vkdll_dlinit" c'vkdll_dlinit :: Ptr CString -> IO (Ptr Void) foreign import ccall safe "_vkdll_dlsym" c'vkdll_dlsym :: Ptr Void -> CString -> Ptr CString -> IO (FunPtr a) foreign import ccall safe "&_vkdll_dlclose" p'vk_dlclose :: FunPtr (Ptr Void -> IO ()) _vkDlHandle :: ForeignPtr Void _vkDlHandle = unsafePerformIO $ alloca $ \errPtr -> do handle <- c'vkdll_dlinit errPtr if handle == nullPtr then peek errPtr >>= peekCString >>= fail . ("An error happened while trying to load vulkan library dynamically: " ++) else newForeignPtr p'vk_dlclose handle {-# NOINLINE _vkDlHandle #-}