packages feed

vulkan-utils-0.5.11.0: src/Vulkan/Utils/Requirements.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}

module Vulkan.Utils.Requirements
  ( -- * Instance requirements
    checkInstanceRequirements

    -- * Device requirements
  , checkDeviceRequirements

    -- * Results
  , RequirementResult (..)
  , Unsatisfied (..)
  , requirementReport
  , prettyRequirementResult
  ) where

import Control.Arrow (Arrow ((***)))
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.State
import Data.Bifunctor
import Data.ByteString (ByteString)
import Data.Foldable
import Data.Functor.Product (Product (..))
import qualified Data.HashMap.Strict as Map
import Data.Kind (Type)
import Data.List (intercalate, intersect)
import Data.List.Extra (nubOrd)
import qualified Data.Map.Strict as TRMap
import Data.Proxy
import Data.Semigroup (Endo (..))
import Data.Traversable
import Data.Typeable (eqT)
import Data.Vector (Vector)
import qualified Data.Vector as V
import Data.Word
import Foreign.Ptr
  ( FunPtr
  , Ptr
  , nullFunPtr
  )
import GHC.Base (Proxy#)
import GHC.Exts (proxy#)
import Type.Reflection
import Vulkan.CStruct
  ( FromCStruct
  , ToCStruct
  )
import Vulkan.CStruct.Extends
import Vulkan.Core10
import qualified Vulkan.Core10 as Device
  ( DeviceCreateInfo (..)
  )
import qualified Vulkan.Core10 as Extension
  ( ExtensionProperties (..)
  )
import qualified Vulkan.Core10 as Instance
  ( InstanceCreateInfo (..)
  )
import qualified Vulkan.Core10 as PhysicalDevice
  ( PhysicalDevice (instanceCmds)
  , PhysicalDeviceProperties (..)
  )
import Vulkan.Core11.DeviceInitialization
import Vulkan.Core11.Promoted_From_VK_KHR_get_physical_device_properties2
import qualified Vulkan.Core11.Promoted_From_VK_KHR_get_physical_device_properties2 as PhysicalDevice
  ( PhysicalDeviceProperties2 (..)
  , features
  )
import Vulkan.Dynamic
  ( InstanceCmds
      ( pVkGetPhysicalDeviceFeatures2
      , pVkGetPhysicalDeviceProperties2
      )
  )
import Vulkan.NamedType
import Vulkan.Requirement
import Vulkan.Version
import Vulkan.Zero (Zero (..))

----------------------------------------------------------------

-- * Instance Creation

----------------------------------------------------------------

checkInstanceRequirements
  :: forall m o r es
   . (MonadIO m, Traversable r, Traversable o)
  => r InstanceRequirement
  -- ^ Required requests
  -> o InstanceRequirement
  -- ^ Optional requests
  -> InstanceCreateInfo es
  {- ^ An 'InstanceCreateInfo', this will be returned appropriately modified by
  the requirements
  -}
  -> m
       ( Maybe (InstanceCreateInfo es)
       , r RequirementResult
       , o RequirementResult
       )
checkInstanceRequirements required optional baseCreateInfo = do
  let
    requiredList = toList required
    allAsList = requiredList <> toList optional
  foundVersion <- enumerateInstanceVersion
  (_, layerProps) <- enumerateInstanceLayerProperties
  lookupExtension <-
    getLookupExtension
      layerProps
      Nothing
      [ instanceExtensionLayerName
      | RequireInstanceExtension{instanceExtensionLayerName} <- allAsList
      ]

  (r, continue) <- flip runStateT True $ for required $ \r ->
    case checkInstanceRequest foundVersion layerProps lookupExtension r of
      res -> do
        unless (res == Satisfied) (put False)
        pure res

  (o, goodOptions) <- flip runStateT mempty $ for optional $ \o ->
    case checkInstanceRequest foundVersion layerProps lookupExtension o of
      res -> do
        when (res == Satisfied) $ modify (o :)
        pure res

  let ici = do
        guard continue
        pure $
          makeInstanceCreateInfo
            (requiredList <> goodOptions)
            baseCreateInfo
  pure (ici, r, o)

{- | Insert the settings of the requirements in to the provided instance create
info
-}
makeInstanceCreateInfo
  :: forall es
   . [InstanceRequirement]
  -> InstanceCreateInfo es
  -> InstanceCreateInfo es
makeInstanceCreateInfo reqs baseCreateInfo =
  let
    layers = [instanceLayerName | RequireInstanceLayer{..} <- reqs]
    extensions =
      [instanceExtensionName | RequireInstanceExtension{..} <- reqs]
  in
    baseCreateInfo
      { Instance.enabledLayerNames =
          Instance.enabledLayerNames baseCreateInfo
            <> V.fromList layers
      , Instance.enabledExtensionNames =
          Instance.enabledExtensionNames baseCreateInfo
            <> V.fromList extensions
      }

checkInstanceRequest
  :: ("apiVersion" ::: Word32)
  -> ("properties" ::: Vector LayerProperties)
  -> ( ("layerName" ::: Maybe ByteString)
       -> ByteString
       -> Maybe ExtensionProperties
     )
  -> InstanceRequirement
  -> RequirementResult
checkInstanceRequest foundVersion layerProps lookupExtension = \case
  RequireInstanceVersion minVersion ->
    if foundVersion >= minVersion
      then Satisfied
      else UnsatisfiedInstanceVersion (Unsatisfied minVersion foundVersion)
  RequireInstanceLayer{instanceLayerName, instanceLayerMinVersion}
    | Just props <- find ((== instanceLayerName) . layerName) layerProps
    , foundLayerVersion <- implementationVersion props ->
        if foundVersion >= instanceLayerMinVersion
          then Satisfied
          else
            UnsatisfiedLayerVersion
              instanceLayerName
              (Unsatisfied instanceLayerMinVersion foundLayerVersion)
    | otherwise ->
        MissingLayer instanceLayerName
  RequireInstanceExtension{instanceExtensionLayerName, instanceExtensionName, instanceExtensionMinVersion}
    | Just eProps <-
        lookupExtension
          instanceExtensionLayerName
          instanceExtensionName ->
        let foundInstanceExtensionVersion =
              Extension.specVersion eProps
        in if foundInstanceExtensionVersion >= instanceExtensionMinVersion
             then Satisfied
             else
               UnsatisfiedInstanceExtensionVersion
                 instanceExtensionName
                 ( Unsatisfied
                     instanceExtensionMinVersion
                     foundInstanceExtensionVersion
                 )
    | otherwise ->
        UnsatisfiedInstanceExtension instanceExtensionName

----------------------------------------------------------------
-- Device
----------------------------------------------------------------

checkDeviceRequirements
  :: forall m o r
   . (MonadIO m, Traversable r, Traversable o)
  => r DeviceRequirement
  -- ^ Required requests
  -> o DeviceRequirement
  -- ^ Optional requests
  -> PhysicalDevice
  -> DeviceCreateInfo '[]
  {- ^ A deviceCreateInfo with no extensions. If you need elements in the
  struct chain you can add them later with
  'Vulkan.CStruct.Extends.extendSomeStruct'
  -}
  -> m
       ( Maybe (SomeStruct DeviceCreateInfo)
       , r RequirementResult
       , o RequirementResult
       )
checkDeviceRequirements required optional phys baseCreateInfo = do
  let
    requiredList = toList required
    allAsList = requiredList <> toList optional

  --
  -- First collect the types and properties that we'll need to query using
  -- getPhysicalDeviceProperties2 and getPhysicalDeviceFeatures2
  --
  withDeviceFeatureStructs allAsList $ \(_ :: Proxy fs) ->
    withDevicePropertyStructs allAsList $ \(_ :: Proxy ps) -> do
      --
      -- Fetch everything
      --
      feats <- getPhysicalDeviceFeaturesMaybe @fs phys
      props <- getPhysicalDevicePropertiesMaybe @ps phys
      (_, layerProps) <- enumerateDeviceLayerProperties phys
      lookupExtension <-
        getLookupExtension
          layerProps
          (Just phys)
          [ deviceExtensionLayerName
          | RequireDeviceExtension{deviceExtensionLayerName} <- allAsList
          ]

      (r, continue) <- flip runStateT True $ for required $ \r ->
        case checkDeviceRequest feats props lookupExtension r of
          res -> do
            unless (res == Satisfied) (put False)
            pure res

      (o, goodOptions) <- flip runStateT mempty $ for optional $ \o ->
        case checkDeviceRequest feats props lookupExtension o of
          res -> do
            when (res == Satisfied) $ modify (o :)
            pure res

      --
      -- Now create the types for just the available features
      --
      let dci = do
            guard continue
            pure $
              makeDeviceCreateInfo
                (requiredList <> goodOptions)
                baseCreateInfo
      pure (dci, r, o)

{-# ANN makeDeviceCreateInfo ("HLint: ignore Move guards forward" :: String) #-}

{- | Generate 'DeviceCreateInfo' from some requirements.

The returned struct chain will enable all required features and extensions.
-}
makeDeviceCreateInfo
  :: [DeviceRequirement] -> DeviceCreateInfo '[] -> SomeStruct DeviceCreateInfo
makeDeviceCreateInfo allReqs baseCreateInfo =
  let
    featureSetters :: DMap (Product (Has KnownFeatureStruct) Endo)
    featureSetters =
      dmapFromListWith
        catProducts
        [ typeRep :=> Pair Has (Endo enableFeature)
        | RequireDeviceFeature{enableFeature} <- allReqs
        ]

    makeZeroFeatureExts :: [Endo (SomeStruct DeviceCreateInfo)]
    makeZeroFeatureExts =
      [ Endo (extendSomeStruct s)
      | _ :=> Pair Has (f :: Endo s) <- dmapToList featureSetters
      , ExtendedFeatureStruct <- pure $ sFeatureStruct @s
      , let s = appEndo f zero
      ]

    addBasicFeatures :: Endo (SomeStruct DeviceCreateInfo)
    addBasicFeatures =
      case dmapLookup (typeRep @PhysicalDeviceFeatures) featureSetters of
        Nothing -> mempty
        Just (Pair _ s) ->
          Endo
            ( extendSomeStruct
                ((zero :: PhysicalDeviceFeatures2 '[]){features = appEndo s zero})
            )

    extensionNames :: [ByteString]
    extensionNames =
      [ deviceExtensionName
      | RequireDeviceExtension{deviceExtensionName} <- allReqs
      ]

    newFeatures :: SomeStruct DeviceCreateInfo
    newFeatures =
      appEndo
        (fold (addBasicFeatures : makeZeroFeatureExts))
        ( SomeStruct
            (baseCreateInfo :: DeviceCreateInfo '[])
              { Device.enabledExtensionNames = V.fromList extensionNames
              }
        )
  in
    newFeatures

checkDeviceRequest
  :: forall fs ps
   . (KnownChain fs, KnownChain ps)
  => Maybe (PhysicalDeviceFeatures2 fs)
  -> Maybe (PhysicalDeviceProperties2 ps)
  -> ( ("layerName" ::: Maybe ByteString)
       -> ("extensionName" ::: ByteString)
       -> Maybe ExtensionProperties
     )
  -- ^ Lookup an extension
  -> DeviceRequirement
  -> RequirementResult
checkDeviceRequest mbFeats mbProps lookupExtension = \case
  RequireDeviceVersion minVersion
    | Just props <- mbProps
    , foundVersion <- PhysicalDevice.apiVersion (PhysicalDevice.properties props) ->
        if foundVersion >= minVersion
          then Satisfied
          else UnsatisfiedDeviceVersion (Unsatisfied minVersion foundVersion)
    | otherwise ->
        UnattemptedProperties "apiVersion"
  RequireDeviceFeature{featureName, checkFeature}
    | Just feats <- mbFeats -> case getFeatureStruct feats of
        Nothing ->
          error "Impossible: didn't find requested feature in struct chain"
        Just s ->
          if checkFeature s then Satisfied else UnsatisfiedFeature featureName
    | otherwise -> UnattemptedFeatures featureName
  RequireDeviceProperty{propertyName, checkProperty}
    | Just props <- mbProps -> case getPropertyStruct props of
        Nothing ->
          error "Impossible: didn't find requested property in struct chain"
        Just s ->
          if checkProperty s then Satisfied else UnsatisfiedProperty propertyName
    | otherwise -> UnattemptedProperties propertyName
  RequireDeviceExtension{deviceExtensionLayerName, deviceExtensionName, deviceExtensionMinVersion}
    | Just eProps <-
        lookupExtension
          deviceExtensionLayerName
          deviceExtensionName ->
        let foundVersion = Extension.specVersion eProps
        in if foundVersion >= deviceExtensionMinVersion
             then Satisfied
             else
               UnsatisfiedDeviceExtensionVersion
                 deviceExtensionName
                 (Unsatisfied deviceExtensionMinVersion foundVersion)
    | otherwise ->
        UnsatisfiedDeviceExtension deviceExtensionName

----------------------------------------------------------------
-- Results
----------------------------------------------------------------

-- TODO, better version reporting for extensions
-- TODO, better reporting for properties
data RequirementResult
  = -- | All the requirements were met
    Satisfied
  | {- | Didn't attempt this check because it required
    getPhysicalDeviceProperties2 which wasn't loaded
    -}
    UnattemptedProperties ByteString
  | {- | Didn't attempt this check because it required
    getPhysicalDeviceFeatures2 which wasn't loaded
    -}
    UnattemptedFeatures ByteString
  | -- | A Layer was not found
    MissingLayer ByteString
  | -- | A device version didn't meet the minimum requested
    UnsatisfiedDeviceVersion (Unsatisfied Word32)
  | -- | The instance version didn't meet the minimum requested
    UnsatisfiedInstanceVersion (Unsatisfied Word32)
  | -- | A layer version didn't meet the minimum requested
    UnsatisfiedLayerVersion ByteString (Unsatisfied Word32)
  | -- | A feature was missing
    UnsatisfiedFeature ByteString
  | -- | A propery was not an appropriate value
    UnsatisfiedProperty ByteString
  | -- | A device extension was missing
    UnsatisfiedDeviceExtension ByteString
  | -- | A device extension was found but the version didn't meet requirements
    UnsatisfiedDeviceExtensionVersion ByteString (Unsatisfied Word32)
  | -- | An instance extension was missing
    UnsatisfiedInstanceExtension ByteString
  | -- | An instance extension was found but the version didn't meet requirements
    UnsatisfiedInstanceExtensionVersion ByteString (Unsatisfied Word32)
  deriving (Eq, Ord)

data Unsatisfied a = Unsatisfied
  { unsatisfiedMinimum :: a
  -- ^ The minimum value to be accepted
  , unsatisfiedActual :: a
  -- ^ The value we got, less than 'unsatisfiedMinumum'
  }
  deriving (Eq, Ord)

{- | Generate a string describing which requirements were not met, if
everything was satisfied return 'Nothing'.
-}
requirementReport
  :: (Foldable r, Foldable o)
  => r RequirementResult
  -> o RequirementResult
  -> Maybe String
requirementReport required optional =
  let
    pList xs =
      nubOrd [prettyRequirementResult r | r <- toList xs, r /= Satisfied]
    reqStrings = pList required
    optStrings = pList optional
    withHeader s = \case
      [] -> []
      xs -> (s <> " requirements not met:") : (("  " <>) <$> xs)
    reportLines =
      withHeader "Required" reqStrings <> withHeader "Optional" optStrings
  in
    if null reportLines then Nothing else Just $ unlines reportLines

prettyRequirementResult :: RequirementResult -> String
prettyRequirementResult = \case
  Satisfied -> "Satisfied"
  UnattemptedProperties n ->
    "Did not attempt to check "
      <> show n
      <> " because the 'getPhysicalDeviceProperties' function was not loaded"
  UnattemptedFeatures n ->
    "Did not attempt to check "
      <> show n
      <> " because the 'getPhysicalDeviceFeatures' function was not loaded"
  MissingLayer n -> "Couldn't find layer: " <> show n
  UnsatisfiedInstanceVersion u -> "Unsatisfied Instance version: " <> p u
  UnsatisfiedDeviceVersion u -> "Unsatisfied Device version: " <> p u
  UnsatisfiedLayerVersion n u ->
    "Unsatisfied layer version for " <> show n <> ": " <> p u
  UnsatisfiedFeature n -> "Missing feature: " <> show n
  UnsatisfiedProperty n -> "Unsatisfied property: " <> show n
  UnsatisfiedInstanceExtension n ->
    "Couldn't find instance extension: " <> show n
  UnsatisfiedInstanceExtensionVersion n u ->
    "Unsatisfied Instance extension version " <> show n <> " " <> p u
  UnsatisfiedDeviceExtension n -> "Couldn't find device extension: " <> show n
  UnsatisfiedDeviceExtensionVersion n u ->
    "Unsatisfied Device extension version " <> show n <> " " <> p u
  where
    p = prettyUnsatisfied showVersion

-- How I'm feeling after writing all this type level nonsense
prettyUnsatisfied :: (t -> String) -> Unsatisfied t -> String
prettyUnsatisfied s Unsatisfied{..} =
  "Wanted minimum of "
    <> s unsatisfiedMinimum
    <> ", got: "
    <> s unsatisfiedActual

----------------------------------------------------------------
-- Chain lenses
----------------------------------------------------------------

-- | Enough information to focus on any structure within a Vulkan structure chain.
class (PeekChain xs, PokeChain xs) => KnownChain (xs :: [Type]) where
  {- | If the given structure can be found within a chain, return a lens to it.
  Otherwise, return 'Nothing'.
  -}
  has :: forall a. (Typeable a) => Proxy# a -> Maybe (Chain xs -> a, (a -> a) -> (Chain xs -> Chain xs))

  -- | Is this chain empty?
  knownChainNull :: Maybe (xs :~: '[])

instance KnownChain '[] where
  has _ = Nothing
  knownChainNull = Just Refl

instance (Typeable x, ToCStruct x, FromCStruct x, KnownChain xs) => KnownChain (x ': xs) where
  has (px :: Proxy# a)
    | Just Refl <- eqT @a @x = Just (fst, first)
    | otherwise = ((. snd) *** (second .)) <$> has px
  knownChainNull = Nothing

getPropertyStruct
  :: forall s es
   . (Typeable s, KnownChain es)
  => PhysicalDeviceProperties2 es
  -> Maybe s
getPropertyStruct c = case eqT @PhysicalDeviceProperties @s of
  Just Refl -> Just $ PhysicalDevice.properties c
  Nothing -> getStruct c

getFeatureStruct
  :: forall s es
   . (Typeable s, KnownChain es)
  => PhysicalDeviceFeatures2 es
  -> Maybe s
getFeatureStruct c = case eqT @PhysicalDeviceFeatures @s of
  Just Refl -> Just $ PhysicalDevice.features c
  Nothing -> getStruct c

getStruct
  :: forall s h es
   . (Typeable h, Typeable s, KnownChain es, Extensible h)
  => h es
  -> Maybe s
getStruct c = ($ getNext c) . fst <$> has (proxy# :: Proxy# s)

----------------------------------------------------------------
-- Helpers for 'Device' and 'Instance' extensions
----------------------------------------------------------------

{- | Make a lookup function for extensions in layers. Ignores layers not
present in the instance/device
-}
getLookupExtension
  :: (MonadIO m)
  => Vector LayerProperties
  -> Maybe PhysicalDevice
  {- ^ Pass 'Nothing' for 'Instance' extensions, pass a PhysicalDevice for
  'Device' extensions.
  -}
  -> ["layerName" ::: Maybe ByteString]
  -> m
       ( ("layerName" ::: Maybe ByteString)
         -> ByteString
         -> Maybe ExtensionProperties
       )
getLookupExtension layerProps mbPhys extensionLayers = do
  let
    enumerate =
      maybe
        enumerateInstanceExtensionProperties
        enumerateDeviceExtensionProperties
        mbPhys
    availableLayers = Nothing : ((Just . layerName) <$> V.toList layerProps)
    searchedLayers = availableLayers `intersect` extensionLayers
  extensions <- for searchedLayers $ \layer -> do
    (_, props) <- enumerate layer
    pure (layer, props)
  let extensionMap = Map.fromListWith (<>) extensions
  pure $ \layer name -> do
    es <- Map.lookup layer extensionMap
    find ((== name) . extensionName) es

----------------------------------------------------------------
-- Helpers for extracting the type of chain used by a set of requirements
----------------------------------------------------------------

withDevicePropertyStructs
  :: forall a. [DeviceRequirement] -> ChainCont DevicePropertyChain a -> a
withDevicePropertyStructs = go @'[] []
  where
    go
      :: forall (fs :: [Type])
       . (DevicePropertyChain fs)
      => [SomeTypeRep]
      -> [DeviceRequirement]
      -> ChainCont DevicePropertyChain a
      -> a
    go seen reqs f = case reqs of
      -- We've been through all the reqs, call the continuation with the types
      [] -> f (Proxy @fs)
      -- This is a device property, add it to the list if we've not seen it before
      (RequireDeviceProperty _ (_ :: s -> Bool)) : rs
        | ExtendedPropertyStruct <- sPropertyStruct @s
        , sRep <- SomeTypeRep (typeRep @s)
        , sRep `notElem` seen ->
            go @(s : fs) (sRep : seen) rs f
      -- Otherwise skip
      _ : rs -> go @fs seen rs f

withDeviceFeatureStructs
  :: forall a. [DeviceRequirement] -> ChainCont DeviceFeatureChain a -> a
withDeviceFeatureStructs = go @'[] []
  where
    go
      :: forall (fs :: [Type])
       . (DeviceFeatureChain fs)
      => [SomeTypeRep]
      -> [DeviceRequirement]
      -> ChainCont DeviceFeatureChain a
      -> a
    go seen reqs f = case reqs of
      -- We've been through all the reqs, call the continuation with the types
      [] -> f (Proxy @fs)
      -- This is a device feature, add it to the list if we've not seen it before
      (RequireDeviceFeature _ _ (_ :: s -> s)) : rs
        | ExtendedFeatureStruct <- sFeatureStruct @s
        , sRep <- SomeTypeRep (typeRep @s)
        , sRep `notElem` seen ->
            go @(s : fs) (sRep : seen) rs f
      -- Otherwise skip
      _ : rs -> go @fs seen rs f

class (KnownChain es, Extendss PhysicalDeviceFeatures2 es, Show (Chain es)) => DeviceFeatureChain es
instance (KnownChain es, Extendss PhysicalDeviceFeatures2 es, Show (Chain es)) => DeviceFeatureChain es

class (KnownChain es, Extendss PhysicalDeviceProperties2 es) => DevicePropertyChain es
instance (KnownChain es, Extendss PhysicalDeviceProperties2 es) => DevicePropertyChain es

type ChainCont c a = forall (es :: [Type]). (c es) => Proxy es -> a

----------------------------------------------------------------
-- Helpers for getting features and properties without using the extended
-- versions of the functions if possible.
----------------------------------------------------------------

getPhysicalDeviceFeaturesMaybe
  :: forall fs m
   . (MonadIO m, KnownChain fs, Extendss PhysicalDeviceFeatures2 fs)
  => PhysicalDevice
  -> m (Maybe (PhysicalDeviceFeatures2 fs))
getPhysicalDeviceFeaturesMaybe =
  getMaybe
    pVkGetPhysicalDeviceFeatures2
    (PhysicalDeviceFeatures2 ())
    getPhysicalDeviceFeatures
    getPhysicalDeviceFeatures2

getPhysicalDevicePropertiesMaybe
  :: forall fs m
   . (MonadIO m, KnownChain fs, Extendss PhysicalDeviceProperties2 fs)
  => PhysicalDevice
  -> m (Maybe (PhysicalDeviceProperties2 fs))
getPhysicalDevicePropertiesMaybe =
  getMaybe
    pVkGetPhysicalDeviceProperties2
    (PhysicalDeviceProperties2 ())
    getPhysicalDeviceProperties
    getPhysicalDeviceProperties2

getMaybe
  :: forall fs s1 s2 m
   . (MonadIO m, KnownChain fs, Extendss s2 fs)
  => ( InstanceCmds
       -> FunPtr (Ptr PhysicalDevice_T -> Ptr (SomeStruct s2) -> IO ())
     )
  -> (s1 -> s2 '[])
  -> (PhysicalDevice -> m s1)
  -> (PhysicalDevice -> m (s2 fs))
  -> PhysicalDevice
  -> m (Maybe (s2 fs))
getMaybe funPtr wrapper2 get1 get2 phys =
  let hasFunPtr = funPtr (PhysicalDevice.instanceCmds phys) /= nullFunPtr
  in case knownChainNull @fs of
       Just Refl -> Just . wrapper2 <$> get1 phys
       Nothing -> if hasFunPtr then Just <$> get2 phys else pure Nothing

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

showVersion :: Word32 -> String
showVersion ver = intercalate "." [show ma, show mi, show pa]
  where
    MAKE_API_VERSION ma mi pa = ver

data Has c a where
  Has :: (c a) => Has c a

instance Semigroup (Has c a) where
  Has <> _ = Has

-- | There is no Semigroup instance for 'Product' in base
catProducts
  :: (Semigroup (f a), Semigroup (g a))
  => Product f g a
  -> Product f g a
  -> Product f g a
catProducts (Pair a1 b1) (Pair a2 b2) = Pair (a1 <> a2) (b1 <> b2)

----------------------------------------------------------------
-- A minimal dependent map keyed by 'TypeRep'
--
-- This is just enough to replace the uses of @dependent-map@ and
-- @dependent-sum@ in 'makeDeviceCreateInfo':
-- group the per-feature-struct setters by their
-- (existentially quantified) struct type, recovering that type
-- later to extend the chain.
----------------------------------------------------------------

{- | A dependent pair: a 'TypeRep' tag together with a value whose type is
determined by the tag.
-}
data DSum f = forall a. (TypeRep a) :=> (f a)

infixr 1 :=>

{- | A map from a type (witnessed by its 'TypeRep') to a value whose type
depends on that key.
-}
newtype DMap f = DMap (TRMap.Map SomeTypeRep (DSum f))

{- | Build a 'DMap' from a list of dependent pairs, combining the values of
any entries that share a key.
-}
dmapFromListWith :: (forall a. f a -> f a -> f a) -> [DSum f] -> DMap f
dmapFromListWith combine =
  DMap
    . TRMap.fromListWith merge
    . map (\d@(tr :=> _) -> (SomeTypeRep tr, d))
  where
    -- Both entries are stored under the same 'SomeTypeRep', so their tags
    -- necessarily witness the same type; 'eqTypeRep' recovers that equality
    -- so the values can be combined. The 'Nothing' case is unreachable.
    merge (trNew :=> vNew) (trOld :=> vOld) = case eqTypeRep trNew trOld of
      Just HRefl -> trNew :=> combine vNew vOld
      Nothing -> trNew :=> vNew

-- | The contents of a 'DMap', as dependent pairs.
dmapToList :: DMap f -> [DSum f]
dmapToList (DMap m) = TRMap.elems m

-- | Look up the value stored under a given type.
dmapLookup :: forall a f. TypeRep a -> DMap f -> Maybe (f a)
dmapLookup tr (DMap m) = do
  (trStored :=> v) <- TRMap.lookup (SomeTypeRep tr) m
  HRefl <- eqTypeRep tr trStored
  pure v