packages feed

vulkan-utils-0.4: src/Vulkan/Utils/Initialization.hs

{-# LANGUAGE OverloadedLists #-}

module Vulkan.Utils.Initialization
  ( -- * Instance creation
    createInstanceFromRequirements
  , createDebugInstanceFromRequirements
    -- * Device creation
  , createDeviceFromRequirements
  , -- * Physical device selection
    pickPhysicalDevice
  , physicalDeviceName
  ) where

import           Control.Monad.IO.Class
import           Control.Monad.Trans.Resource
import           Data.Bits
import           Data.Foldable
import           Data.Maybe
import           Data.Ord
import           Data.Text                      ( Text )
import           Data.Text.Encoding             ( decodeUtf8 )
import           Vulkan.CStruct.Extends
import           Vulkan.Core10
import           Vulkan.Extensions.VK_EXT_debug_utils
import           Vulkan.Extensions.VK_EXT_validation_features
import           Vulkan.Requirement
import           Vulkan.Utils.Debug
import           Vulkan.Utils.Internal
import           Vulkan.Utils.Requirements
import           Vulkan.Zero

----------------------------------------------------------------
-- Instance
----------------------------------------------------------------

-- | Like 'createInstanceFromRequirements' 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.
createDebugInstanceFromRequirements
  :: forall m es
   . (MonadResource m, Extendss InstanceCreateInfo es, PokeChain es)
  => [InstanceRequirement]
  -- ^ Required
  -> [InstanceRequirement]
  -- ^ Optional
  -> InstanceCreateInfo es
  -> m Instance
createDebugInstanceFromRequirements required optional baseCreateInfo = 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 = baseCreateInfo
        { next = debugMessengerCreateInfo :& validationFeatures :& next
                   (baseCreateInfo :: InstanceCreateInfo es)
        }
      additionalRequirements =
        [ RequireInstanceExtension
            { instanceExtensionLayerName  = Nothing
            , instanceExtensionName       = EXT_DEBUG_UTILS_EXTENSION_NAME
            , instanceExtensionMinVersion = minBound
            }
        ]
      additionalOptionalRequirements =
        [ RequireInstanceLayer
          { instanceLayerName       = "VK_LAYER_KHRONOS_validation"
          , instanceLayerMinVersion = minBound
          }
        , RequireInstanceExtension
          { instanceExtensionLayerName  = Just "VK_LAYER_KHRONOS_validation"
          , instanceExtensionName       = EXT_VALIDATION_FEATURES_EXTENSION_NAME
          , instanceExtensionMinVersion = minBound
          }
        ]
  inst <- createInstanceFromRequirements
    (additionalRequirements <> toList required)
    (additionalOptionalRequirements <> toList optional)
    instanceCreateInfo
  _ <- withDebugUtilsMessengerEXT inst debugMessengerCreateInfo Nothing allocate
  pure inst

-- | Create an 'Instance from some requirements.
--
-- Will throw an 'IOError in the case of unsatisfied non-optional requirements.
-- Unsatisfied requirements will be listed on stderr.
createInstanceFromRequirements
  :: (MonadResource m, Extendss InstanceCreateInfo es, PokeChain es)
  => [InstanceRequirement]
  -- ^ Required
  -> [InstanceRequirement]
  -- ^ Optional
  -> InstanceCreateInfo es
  -> m Instance
createInstanceFromRequirements required optional baseCreateInfo = do
  (mbICI, rrs, ors) <- checkInstanceRequirements required
                                                 optional
                                                 baseCreateInfo
  traverse_ sayErr (requirementReport rrs ors)
  case mbICI of
    Nothing  -> liftIO $ unsatisfiedConstraints "Failed to create instance"
    Just ici -> snd <$> withInstance ici Nothing allocate

----------------------------------------------------------------
-- * Device creation
----------------------------------------------------------------

-- | Create a 'Device' from some requirements.
--
-- Will throw an 'IOError in the case of unsatisfied non-optional requirements.
-- Unsatisfied requirements will be listed on stderr.
createDeviceFromRequirements
  :: forall m
   . MonadResource m
  => [DeviceRequirement]
  -- ^ Required
  -> [DeviceRequirement]
  -- ^ Optional
  -> PhysicalDevice
  -> DeviceCreateInfo '[]
  -> m Device
createDeviceFromRequirements required optional phys baseCreateInfo = do
  (mbDCI, rrs, ors) <- checkDeviceRequirements required
                                               optional
                                               phys
                                               baseCreateInfo
  traverse_ sayErr (requirementReport rrs ors)
  case mbDCI of
    Nothing -> liftIO $ unsatisfiedConstraints "Failed to create instance"
    Just (SomeStruct dci) -> snd <$> withDevice phys dci Nothing allocate

----------------------------------------------------------------
-- * 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 suitable devices.
--
-- As an example, the suitability 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. Consider using
-- 'Vulkan.Utils.QueueAssignment.assignQueues' to find your desired queues in
-- the suitability function.
--
-- Pehaps also use the functionality in 'Vulkan.Utils.Requirements' and return
-- the 'DeviceCreateInfo' too.
--
-- If no devices are deemed suitable then a 'NoSuchThing' 'IOError' is thrown.
pickPhysicalDevice
  :: (MonadIO m, Ord b)
  => Instance
  -> (PhysicalDevice -> m (Maybe a))
  -- ^ A suitability funcion for a 'PhysicalDevice', 'Nothing' if it is not to
  -- be chosen.
  -> (a -> b)
  -- ^ Scoring function to rate this result
  -> m (Maybe (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 ]
  pure $ maximumByMay (comparing (score . fst)) infos

-- | Extract the name of a 'PhysicalDevice' with 'getPhysicalDeviceProperties'
physicalDeviceName :: MonadIO m => PhysicalDevice -> m Text
physicalDeviceName =
  fmap (decodeUtf8 . deviceName) . getPhysicalDeviceProperties


----------------------------------------------------------------
-- Utils
----------------------------------------------------------------

maximumByMay :: Foldable t => (a -> a -> Ordering) -> t a -> Maybe a
maximumByMay f xs = if null xs then Nothing else Just (maximumBy f xs)