diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,11 @@
 # Changelog for keid-core
 
+## 0.1.3.1
+
+- Added `Engine.Setup.setupHeadless` to use without a surface.
+- Added queue assignment fallback when there are no distinct queues for compute and transfer.
+  Fixes startup crash on Intel iGPUs.
+
 ## 0.1.3.0
 
 - Changed mesh codec to use `Serialise` class instead of `Storable`.
diff --git a/keid-core.cabal b/keid-core.cabal
--- a/keid-core.cabal
+++ b/keid-core.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           keid-core
-version:        0.1.3.0
+version:        0.1.3.1
 synopsis:       Core parts of Keid engine.
 category:       Game Engine
 author:         IC Rainbow
diff --git a/src/Engine/Setup.hs b/src/Engine/Setup.hs
--- a/src/Engine/Setup.hs
+++ b/src/Engine/Setup.hs
@@ -19,7 +19,7 @@
 import Engine.Types (GlobalHandles(..))
 import Engine.Types.Options (Options(..))
 import Engine.Vulkan.Swapchain (SwapchainResources)
-import Engine.Vulkan.Types (PhysicalDeviceInfo(..))
+import Engine.Vulkan.Types (PhysicalDeviceInfo(..), Queues)
 import Engine.Worker qualified as Worker
 import Engine.StageSwitch (newStageSwitchVar)
 
@@ -49,7 +49,7 @@
   logDebug "Creating physical device"
   (ghPhysicalDeviceInfo, ghPhysicalDevice) <- allocatePhysical
     ghInstance
-    ghSurface
+    (Just ghSurface)
     pdiTotalMemory
 
   logDebug "Creating logical device"
@@ -79,6 +79,57 @@
   ghStageSwitch <- newStageSwitchVar
 
   pure (GlobalHandles{..}, Nothing)
+
+setupHeadless
+  :: ( HasLogFunc env
+     , MonadResource (RIO env)
+     )
+  => Options
+  -> RIO env Headless
+setupHeadless opts = do
+  logDebug $ displayShow opts
+
+  let windowReqs = []
+
+  logDebug "Creating instance"
+  ghInstance <- createInstanceFromRequirements
+    (deviceProps : debugUtils : windowReqs)
+    mempty
+    zero
+
+  logDebug "Creating physical device"
+  (ghPhysicalDeviceInfo, ghPhysicalDevice) <- allocatePhysical
+    ghInstance
+    Nothing
+    pdiTotalMemory
+
+  logDebug "Creating logical device"
+  ghDevice <- allocateLogical ghPhysicalDeviceInfo ghPhysicalDevice
+
+  logDebug "Creating VMA"
+  let
+    allocatorCI :: VMA.AllocatorCreateInfo
+    allocatorCI = zero
+      { VMA.physicalDevice = Vk.physicalDeviceHandle ghPhysicalDevice
+      , VMA.device         = Vk.deviceHandle ghDevice
+      , VMA.instance'      = Vk.instanceHandle ghInstance
+      }
+  (_vmaKey, ghAllocator) <- VMA.withAllocator allocatorCI Resource.allocate
+  toIO (logDebug "Releasing VMA") >>= Resource.register
+
+  ghQueues <- liftIO $ pdiGetQueues ghPhysicalDeviceInfo ghDevice
+  logDebug $ "Got command queues: " <> displayShow (fmap (unQueueFamilyIndex . fst) ghQueues)
+
+  pure (ghInstance, ghPhysicalDeviceInfo, ghPhysicalDevice, ghDevice, ghAllocator, ghQueues)
+
+type Headless =
+  ( Vk.Instance
+  , PhysicalDeviceInfo
+  , Vk.PhysicalDevice
+  , Vk.Device
+  , VMA.Allocator
+  , Queues (QueueFamilyIndex, Vk.Queue)
+  )
 
 deviceProps :: InstanceRequirement
 deviceProps = RequireInstanceExtension
diff --git a/src/Engine/Setup/Device.hs b/src/Engine/Setup/Device.hs
--- a/src/Engine/Setup/Device.hs
+++ b/src/Engine/Setup/Device.hs
@@ -6,6 +6,7 @@
 
 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
@@ -34,16 +35,16 @@
      , MonadResource m
      )
   => Vk.Instance
-  -> Khr.SurfaceKHR
+  -> Maybe Khr.SurfaceKHR
   -> (PhysicalDeviceInfo -> Word64)
   -> m (PhysicalDeviceInfo, Vk.PhysicalDevice)
-allocatePhysical vkInstance surface score = do
+allocatePhysical vkInstance presentSurface score = do
   UnliftIO unliftIO <- askUnliftIO
 
   let
     create = unliftIO do
       logDebug "Picking physical device..."
-      pickPhysicalDevice vkInstance (physicalDeviceInfo surface) score >>= \case
+      pickPhysicalDevice vkInstance (physicalDeviceInfo presentSurface) score >>= \case
         Nothing ->
           noSuchThing "Unable to find appropriate PhysicalDevice"
         Just res@(pdi, _dev) -> do
@@ -63,11 +64,22 @@
      , MonadReader env m
      , HasLogFunc env
      )
-  => Khr.SurfaceKHR -> Vk.PhysicalDevice -> m (Maybe PhysicalDeviceInfo)
-physicalDeviceInfo surf phys = runMaybeT do
+  => Maybe Khr.SurfaceKHR
+  -> Vk.PhysicalDevice
+  -> m (Maybe PhysicalDeviceInfo)
+physicalDeviceInfo presentSurface phys = runMaybeT do
   pdiName <- physicalDeviceName phys
-  logDebug $ "Considering " <> displayShow pdiName
 
+  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
@@ -75,6 +87,7 @@
       , displayShow pdiName
       , " because it doesn't support timeline semaphores"
       ]
+    mzero
 
   hasSwapchainSupport <- deviceHasSwapchain phys
   unless hasSwapchainSupport do
@@ -83,10 +96,29 @@
       , displayShow pdiName
       , " because it doesn't support swapchains"
       ]
+    mzero
 
-  (pdiQueueCreateInfos, pdiGetQueues) <- MaybeT $
-    Utils.assignQueues phys (queueRequirements phys surf)
+  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 $
@@ -97,6 +129,9 @@
   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
@@ -107,17 +142,24 @@
 -}
 queueRequirements
   :: MonadIO m
-  => Vk.PhysicalDevice -> Khr.SurfaceKHR -> Queues (QueueSpec m)
-queueRequirements phys surf = Queues
+  => 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 = do
-    pq <- Utils.isPresentQueueFamily phys surf queueFamilyIndex
-    let gq = Utils.isGraphicsQueueFamily queueFamilyProperties
-    pure $ pq && gq
+  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
