keid-core-0.1.11.0: src/Engine/Setup/Device.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedRecordDot #-}
-- | Physical device tools
module Engine.Setup.Device where
import RIO
import Control.Monad.Trans.Maybe (MaybeT(..))
import GHC.IO.Exception (IOException(..), IOErrorType(NoSuchThing))
import RIO.Text qualified as Text
import RIO.Vector qualified as V
import UnliftIO.Resource (MonadResource)
import UnliftIO.Resource qualified as Resource
import Vulkan.Core10 qualified as Vk
import Vulkan.Core11.Promoted_From_VK_KHR_multiview (PhysicalDeviceMultiviewFeatures(..))
import Vulkan.Core12.Promoted_From_VK_EXT_descriptor_indexing (PhysicalDeviceDescriptorIndexingFeatures(..))
import Vulkan.Core12.Promoted_From_VK_KHR_timeline_semaphore (PhysicalDeviceTimelineSemaphoreFeatures(..))
import Vulkan.CStruct.Extends ( SomeStruct(SomeStruct), pattern (:&), pattern (::&))
import Vulkan.Extensions.VK_KHR_get_physical_device_properties2 (getPhysicalDeviceFeatures2KHR)
import Vulkan.Extensions.VK_KHR_surface qualified as Khr
import Vulkan.Extensions.VK_KHR_swapchain (pattern KHR_SWAPCHAIN_EXTENSION_NAME)
import Vulkan.Extensions.VK_KHR_timeline_semaphore (pattern KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME)
import Vulkan.Requirement (DeviceRequirement)
import Vulkan.Utils.Initialization (createDeviceFromRequirements, physicalDeviceName, pickPhysicalDevice)
import Vulkan.Utils.QueueAssignment (QueueSpec(..))
import Vulkan.Utils.QueueAssignment qualified as Utils
import Vulkan.Utils.Requirements.TH qualified as Utils
import Vulkan.Core10 (PhysicalDeviceFeatures(..))
import Vulkan.Zero (zero)
import Engine.Vulkan.Types (PhysicalDeviceInfo(..), Queues(..))
allocatePhysical
:: ( MonadUnliftIO m, MonadThrow m
, MonadReader env m
, HasLogFunc env
, MonadResource m
)
=> Vk.Instance
-> Maybe Khr.SurfaceKHR
-> (PhysicalDeviceInfo -> Word64)
-> m (PhysicalDeviceInfo, Vk.PhysicalDevice)
allocatePhysical vkInstance presentSurface score = do
UnliftIO unliftIO <- askUnliftIO
let
create = unliftIO do
logDebug "Picking physical device..."
pickPhysicalDevice vkInstance (physicalDeviceInfo presentSurface) score >>= \case
Nothing ->
noSuchThing "Unable to find appropriate PhysicalDevice"
Just res@(pdi, _dev) -> do
logInfo $ mconcat
[ "Using physical device: "
, displayShow (pdiName pdi)
]
pure res
destroy _res = unliftIO $
logDebug "Destroying physical device"
fmap snd $ Resource.allocate create destroy
physicalDeviceInfo
:: ( MonadIO m
, MonadReader env m
, HasLogFunc env
)
=> Maybe Khr.SurfaceKHR
-> Vk.PhysicalDevice
-> m (Maybe PhysicalDeviceInfo)
physicalDeviceInfo presentSurface phys = runMaybeT do
pdiName <- physicalDeviceName phys
let
ignoreDevice =
"llvmpipe" `Text.isPrefixOf` pdiName
if ignoreDevice then do
logDebug $ "Ignoring " <> displayShow pdiName
mzero
else
logDebug $ "Considering " <> displayShow pdiName
hasTimelineSemaphores <- deviceHasTimelineSemaphores phys
unless hasTimelineSemaphores do
logWarn $ mconcat
[ "Not using physical device "
, displayShow pdiName
, " because it doesn't support timeline semaphores"
]
mzero
hasSwapchainSupport <- deviceHasSwapchain phys
unless hasSwapchainSupport do
logWarn $ mconcat
[ "Not using physical device "
, displayShow pdiName
, " because it doesn't support swapchains"
]
mzero
assigned <- Utils.assignQueues phys (queueRequirements phys presentSurface)
(pdiQueueCreateInfos, pdiGetQueues) <- case assigned of
Nothing -> do
logDebug "Queue assignment failed"
fallback <- Utils.assignQueues @_ @_ @IO phys (Identity $ QueueSpec 1.0 isFallbackQ)
case fallback of
Nothing -> do
logWarn "Fallback assignment failed too"
mzero
Just (infos, getQueues) -> do
logDebug "Fallback assignment succeeded"
pure
( infos
, \dev -> do
Identity q <- getQueues dev
pure $ Queues q q q
)
Just queues ->
pure queues
pdiTotalMemory <- do
props <- Vk.getPhysicalDeviceMemoryProperties phys
pure . sum $
fmap (.size) (Vk.memoryHeaps props)
pdiProperties <- Vk.getPhysicalDeviceProperties phys
pure PhysicalDeviceInfo{..}
where
isFallbackQ _queueFamilyIndex queueFamilyProperties =
pure $ Utils.isGraphicsQueueFamily queueFamilyProperties
{- |
Requirements for a 'Queue' which has graphics support and can present to
the specified surface.
Priorities are ranged 0.0 to 1.0 with higher number means higher priority.
https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#devsandqueues-priority
-}
queueRequirements
:: MonadIO m
=> Vk.PhysicalDevice
-> Maybe Khr.SurfaceKHR
-> Queues (QueueSpec m)
queueRequirements phys presentSurface = Queues
{ qGraphics = QueueSpec 1.0 isGraphicsPresentQueue
, qCompute = QueueSpec 0.5 isComputeQueue
, qTransfer = QueueSpec 0.0 isTransferQueue
}
where
isGraphicsPresentQueue queueFamilyIndex queueFamilyProperties =
case presentSurface of
Just surf -> do
pq <- Utils.isPresentQueueFamily phys surf queueFamilyIndex
pure $ pq && gq
Nothing ->
pure gq
where
gq = Utils.isGraphicsQueueFamily queueFamilyProperties
isTransferQueue _queueFamilyIndex queueFamilyProperties =
pure $ Utils.isTransferQueueFamily queueFamilyProperties
isComputeQueue _queueFamilyIndex queueFamilyProperties =
pure $ Utils.isComputeQueueFamily queueFamilyProperties
deviceHasSwapchain :: MonadIO m => Vk.PhysicalDevice -> m Bool
deviceHasSwapchain dev = do
(_, extensions) <- Vk.enumerateDeviceExtensionProperties dev Nothing
pure $ V.any
((KHR_SWAPCHAIN_EXTENSION_NAME ==) . Vk.extensionName)
extensions
deviceHasTimelineSemaphores :: MonadIO m => Vk.PhysicalDevice -> m Bool
deviceHasTimelineSemaphores phys = do
(_, extensions) <- Vk.enumerateDeviceExtensionProperties phys Nothing
let
hasExt = V.any
((KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME ==) . Vk.extensionName)
extensions
hasFeat <- getPhysicalDeviceFeatures2KHR phys >>= \case
_ ::& (PhysicalDeviceTimelineSemaphoreFeatures hasTimelineSemaphores :& ()) ->
pure hasTimelineSemaphores
pure $ hasExt && hasFeat
allocateLogical
:: ( MonadUnliftIO m
, MonadReader env m, HasLogFunc env
, MonadResource m
)
=> PhysicalDeviceInfo -> Vk.PhysicalDevice -> m Vk.Device
allocateLogical pdi pd = do
logDebug "Creating logical device"
ld <- createDeviceFromRequirements reqs opts pd deviceCI
toIO (logDebug "Destroying logical device") >>=
Resource.register
pure ld
where
deviceCI = zero
{ Vk.queueCreateInfos = fmap SomeStruct (pdiQueueCreateInfos pdi)
}
reqs :: [DeviceRequirement]
reqs =
#ifdef darwin_HOST_OS
[Utils.req| VK_KHR_portability_subset |] :
#endif
[Utils.reqs|
1.2
VK_KHR_maintenance3
VK_KHR_swapchain
-- PhysicalDeviceFeatures.robustBufferAccess
PhysicalDeviceFeatures.textureCompressionBC
VK_KHR_multiview
PhysicalDeviceMultiviewFeatures.multiview
VK_EXT_descriptor_indexing
PhysicalDeviceDescriptorIndexingFeatures.descriptorBindingPartiallyBound
PhysicalDeviceDescriptorIndexingFeatures.descriptorBindingVariableDescriptorCount
PhysicalDeviceDescriptorIndexingFeatures.runtimeDescriptorArray
PhysicalDeviceDescriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing
VK_KHR_timeline_semaphore
PhysicalDeviceTimelineSemaphoreFeatures.timelineSemaphore
|]
opts :: [DeviceRequirement]
opts =
[Utils.reqs|
PhysicalDeviceFeatures.samplerAnisotropy
PhysicalDeviceFeatures.sampleRateShading
|]
noSuchThing :: MonadThrow m => String -> m a
noSuchThing message =
throwM $
IOError Nothing NoSuchThing "" message Nothing Nothing