vulkan-utils-0.2: src/Vulkan/Utils/Initialization.hs
{-# LANGUAGE OverloadedLists #-}
module Vulkan.Utils.Initialization
( createDebugInstanceWithExtensions
, createInstanceWithExtensions
, pickPhysicalDevice
, physicalDeviceName
, createDeviceWithExtensions
) where
import Control.Exception ( throwIO )
import Control.Monad.IO.Class
import Control.Monad.Trans.Resource
import Data.Bits
import Data.ByteString ( ByteString )
import Data.Foldable
import Data.Maybe
import Data.Ord
import Data.Text ( Text )
import Data.Text.Encoding ( decodeUtf8 )
import qualified Data.Vector as V
import GHC.IO.Exception ( IOErrorType(NoSuchThing)
, IOException(..)
)
import Vulkan.CStruct.Extends
import Vulkan.Core10
import Vulkan.Extensions.VK_EXT_debug_utils
import Vulkan.Extensions.VK_EXT_validation_features
import Vulkan.Utils.Debug
import Vulkan.Utils.Misc
import Vulkan.Zero
----------------------------------------------------------------
-- * Instance Creation
----------------------------------------------------------------
-- | Like 'createInstanceWithExtensions' except it will create a debug utils
-- messenger (from the @VK_EXT_debug_utils@ extension).
--
-- If the @VK_EXT_validation_features@ extension (from the
-- @VK_LAYER_KHRONOS_validation@ layer) is available is it will be enabled and
-- best practices messages enabled.
createDebugInstanceWithExtensions
:: forall es m
. (Extendss InstanceCreateInfo es, PokeChain es, MonadResource m)
=> [ByteString]
-- ^ Required layers
-> [ByteString]
-- ^ Optional layers
-> [ByteString]
-- ^ Required extensions
-> [ByteString]
-- ^ Optional extensions
-> InstanceCreateInfo es
-> m Instance
createDebugInstanceWithExtensions requiredLayers optionalLayers requiredExtensions optionalExtensions instanceCreateInfo
= do
let debugMessengerCreateInfo = zero
{ messageSeverity = DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
.|. DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
, messageType = DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
.|. DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
.|. DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
, pfnUserCallback = debugCallbackPtr
}
validationFeatures = ValidationFeaturesEXT
[VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT]
[]
instanceCreateInfo'
:: InstanceCreateInfo
(DebugUtilsMessengerCreateInfoEXT : ValidationFeaturesEXT : es)
instanceCreateInfo' = instanceCreateInfo
{ next = debugMessengerCreateInfo :& validationFeatures :& next
(instanceCreateInfo :: InstanceCreateInfo es)
}
inst <- createInstanceWithExtensions
requiredLayers
("VK_LAYER_KHRONOS_validation" : optionalLayers)
(EXT_DEBUG_UTILS_EXTENSION_NAME : requiredExtensions)
(EXT_VALIDATION_FEATURES_EXTENSION_NAME : optionalExtensions)
instanceCreateInfo'
_ <- withDebugUtilsMessengerEXT inst
debugMessengerCreateInfo
Nothing
allocate
pure inst
-- | Create an 'Instance' with some layers and extensions, the layers and
-- extensions will be added to the provided 'InstanceCreateInfo'.
--
-- Will throw an 'IOError in the case of missing layers or extensions. Details
-- on missing layers and extensions will be reported in stderr.
createInstanceWithExtensions
:: forall es m
. (Extendss InstanceCreateInfo es, PokeChain es, MonadResource m)
=> [ByteString]
-- ^ Required layers
-> [ByteString]
-- ^ Optional layers
-> [ByteString]
-- ^ Required extensions
-> [ByteString]
-- ^ Optional extensions
-> InstanceCreateInfo es
-> m Instance
createInstanceWithExtensions requiredLayers optionalLayers requiredExtensions optionalExtensions instanceCreateInfo
= do
--
-- First get the layers, they're needed to get the list of supported
-- extensions, as some of them may only be present in layers.
--
availableLayerNames <-
toList . fmap layerName . snd <$> enumerateInstanceLayerProperties
layers <- partitionOptReqIO "layer"
availableLayerNames
optionalLayers
requiredLayers
-- Run 'enumerateInstanceExtensionProperties' once for the instance itself,
-- and once for each layer and collect the results.
availableExtensionNames <- concat <$> traverse
( fmap (toList . fmap extensionName . snd)
. enumerateInstanceExtensionProperties
)
(Nothing : (Just <$> layers))
extensions <- partitionOptReqIO "instance extension"
availableExtensionNames
optionalExtensions
requiredExtensions
let
instanceCreateInfo' :: InstanceCreateInfo es
instanceCreateInfo' = instanceCreateInfo
{ enabledLayerNames =
enabledLayerNames (instanceCreateInfo :: InstanceCreateInfo es)
<> V.fromList layers
, enabledExtensionNames =
enabledExtensionNames (instanceCreateInfo :: InstanceCreateInfo es)
<> V.fromList extensions
}
(_, inst) <- withInstance instanceCreateInfo' Nothing allocate
pure inst
----------------------------------------------------------------
-- * Physical device selection
----------------------------------------------------------------
-- | Get a single 'PhysicalDevice' deciding with a scoring function
--
-- Pass a function which will extract any required values from a device in the
-- spirit of parse-don't validate. Also provide a function to compare these
-- results for sorting multiple devices.
--
-- For example the result function could return a tuple of device memory and
-- the compute queue family index, and the scoring function could be 'fst' to
-- select devices based on their memory capacity.
--
-- If no devices are deemed suitable then an 'IOError' is thrown.
pickPhysicalDevice
:: (MonadIO m, Ord b)
=> Instance
-> (PhysicalDevice -> m (Maybe a))
-- ^ Some result for a PhysicalDevice, Nothing if it is not to be chosen.
-> (a -> b)
-- ^ Scoring function to rate this result
-> m (a, PhysicalDevice)
-- ^ The score and the device
pickPhysicalDevice inst devInfo score = do
(_, devs) <- enumeratePhysicalDevices inst
infos <- catMaybes
<$> sequence [ fmap (, d) <$> devInfo d | d <- toList devs ]
case maximumByMay (comparing (score.fst)) infos of
Nothing -> liftIO $ noSuchThing "Unable to find appropriate PhysicalDevice"
Just d -> pure d
-- | Extract the name of a 'PhysicalDevice' with 'getPhysicalDeviceProperties'
physicalDeviceName :: MonadIO m => PhysicalDevice -> m Text
physicalDeviceName =
fmap (decodeUtf8 . deviceName) . getPhysicalDeviceProperties
----------------------------------------------------------------
-- * Device initialization
----------------------------------------------------------------
-- | Create a 'Device' with some extensions, the extensions will be added to
-- the provided 'DeviceCreateInfo'.
--
-- Will throw an 'IOError in the case of missing extensions. Missing extensions
-- will be listed on stderr.
createDeviceWithExtensions
:: forall es m
. (Extendss DeviceCreateInfo es, PokeChain es, MonadResource m)
=> PhysicalDevice
-> [ByteString]
-- ^ Required extensions
-> [ByteString]
-- ^ Optional extensions
-> DeviceCreateInfo es
-> m Device
createDeviceWithExtensions phys requiredExtensions optionalExtensions deviceCreateInfo
= do
availableExtensionNames <-
fmap extensionName
. snd
<$> enumerateDeviceExtensionProperties phys Nothing
extensions <- partitionOptReqIO "device extension"
(toList availableExtensionNames)
requiredExtensions
optionalExtensions
let
deviceCreateInfo' :: DeviceCreateInfo es
deviceCreateInfo' = deviceCreateInfo
{ enabledExtensionNames =
enabledExtensionNames (deviceCreateInfo :: DeviceCreateInfo es)
<> V.fromList extensions
}
(_, dev) <- withDevice phys deviceCreateInfo' Nothing allocate
pure dev
----------------------------------------------------------------
-- Utils
----------------------------------------------------------------
noSuchThing :: String -> IO a
noSuchThing message =
throwIO $ IOError Nothing NoSuchThing "" message Nothing Nothing
maximumByMay :: Foldable t => (a -> a -> Ordering) -> t a -> Maybe a
maximumByMay f xs = if null xs then Nothing else Just (maximumBy f xs)