{-| Application-static Vulkan handles plus the recycle channel ends used by
the recycling 'Vulkan.Utils.Frame.Frame' machinery.

Constructed once at boot, never modified. Sub-systems (swapchain, frame loop,
window loop) accept a 'VulkanContext' so they don't need their own copies of
device/queues plumbing.
-}
module Vulkan.Utils.VulkanContext
  ( VulkanContext (..)
  , RecycledResources (..)
  , mkVulkanContext
  ) where

import Control.Concurrent.Chan.Unagi
import qualified Vulkan.Core10 as Vk
import Vulkan.Utils.QueueAssignment (QueueFamilyIndex)
import Vulkan.Utils.Queues (Queues (..))

{- | A bunch of long-lived handles that the application carries around. The
recycle channel ends carry per-frame 'RecycledResources' between the frame
loop and the wait-and-recycle thread.
-}
data VulkanContext = VulkanContext
  { VulkanContext -> Instance
vcInstance :: Vk.Instance
  , VulkanContext -> PhysicalDevice
vcPhysicalDevice :: Vk.PhysicalDevice
  , VulkanContext -> Device
vcDevice :: Vk.Device
  , VulkanContext -> Queues (QueueFamilyIndex, Queue)
vcQueues :: Queues (QueueFamilyIndex, Vk.Queue)
  , VulkanContext -> RecycledResources -> IO ()
vcRecycleBin :: RecycledResources -> IO ()
  {- ^ Drop a frame's reusable bits back into the pool. Called from the
  per-frame wait thread once the GPU is done with the frame.
  -}
  , VulkanContext
-> IO (Either (IO RecycledResources) RecycledResources)
vcRecycleNib :: IO (Either (IO RecycledResources) RecycledResources)
  {- ^ Pull a frame's reusable bits out. 'Right' if available immediately;
  'Left' is a blocking read.
  -}
  }

{- | The bits of state recycled between frames: a binary image-available
semaphore (signalled by image acquisition, waited on by the frame's submit)
and the command pool the frame's commands are recorded into.

The render-finished / present-wait semaphore is /not/ here — it is per
swapchain image (see 'Vulkan.Utils.Swapchain.sRenderFinished'), because a
present-wait semaphore is only safe to reuse once its image is re-acquired,
not when the frame's render completes.
-}
data RecycledResources = RecycledResources
  { RecycledResources -> Semaphore
rrImageAvailable :: Vk.Semaphore
  , RecycledResources -> CommandPool
rrCommandPool :: Vk.CommandPool
  }

{- | Assemble a 'VulkanContext' from already-constructed handles. Builds the
recycle channel internally; the channel starts empty and is populated by
'Vulkan.Utils.Frame.initialFrame'.
-}
mkVulkanContext
  :: Vk.Instance
  -> Vk.PhysicalDevice
  -> Vk.Device
  -> Queues (QueueFamilyIndex, Vk.Queue)
  -> IO VulkanContext
mkVulkanContext :: Instance
-> PhysicalDevice
-> Device
-> Queues (QueueFamilyIndex, Queue)
-> IO VulkanContext
mkVulkanContext Instance
vcInstance PhysicalDevice
vcPhysicalDevice Device
vcDevice Queues (QueueFamilyIndex, Queue)
vcQueues = do
  (binW, binR) <- IO (InChan RecycledResources, OutChan RecycledResources)
forall a. IO (InChan a, OutChan a)
newChan
  let
    vcRecycleBin = InChan RecycledResources -> RecycledResources -> IO ()
forall a. InChan a -> a -> IO ()
writeChan InChan RecycledResources
binW
    vcRecycleNib = do
      (try, block) <- OutChan RecycledResources
-> IO (Element RecycledResources, IO RecycledResources)
forall a. OutChan a -> IO (Element a, IO a)
tryReadChan OutChan RecycledResources
binR
      maybe (Left block) Right <$> tryRead try
  pure VulkanContext{..}