mirror of
				https://github.com/Drezil/dear-imgui.hs.git
				synced 2025-10-30 20:51:07 +01:00 
			
		
		
		
	Vulkan backend (#2)
This commit adds the Vulkan backend and SDL2 integration, and provides the `vulkan` example project.
This commit is contained in:
		| @@ -1,3 +1,3 @@ | |||||||
| packages: *.cabal | packages: *.cabal | ||||||
| package dear-imgui | package dear-imgui | ||||||
|   flags: +sdl2 +opengl |   flags: +sdl2 +opengl +vulkan | ||||||
|   | |||||||
| @@ -11,6 +11,14 @@ flag opengl | |||||||
|   manual: |   manual: | ||||||
|     False |     False | ||||||
|  |  | ||||||
|  | flag vulkan | ||||||
|  |   description: | ||||||
|  |     Enable Vulkan backend. | ||||||
|  |   default: | ||||||
|  |     False | ||||||
|  |   manual: | ||||||
|  |     True | ||||||
|  |  | ||||||
| flag sdl | flag sdl | ||||||
|   description: |   description: | ||||||
|     Enable SDL backend. |     Enable SDL backend. | ||||||
| @@ -64,6 +72,27 @@ library | |||||||
|         extra-libraries: |         extra-libraries: | ||||||
|           GL |           GL | ||||||
|  |  | ||||||
|  |   if flag(vulkan) | ||||||
|  |     exposed-modules: | ||||||
|  |       DearImGui.Vulkan | ||||||
|  |     other-modules: | ||||||
|  |       DearImGui.Vulkan.Types | ||||||
|  |     build-depends: | ||||||
|  |         vulkan | ||||||
|  |       , unliftio | ||||||
|  |     cxx-sources: | ||||||
|  |       imgui/backends/imgui_impl_vulkan.cpp | ||||||
|  |     if os(windows) | ||||||
|  |       extra-libraries: | ||||||
|  |         vulkan-1 | ||||||
|  |     else | ||||||
|  |       if os(darwin) | ||||||
|  |         extra-libraries: | ||||||
|  |           vulkan | ||||||
|  |       else | ||||||
|  |         pkgconfig-depends: | ||||||
|  |           vulkan | ||||||
|  |  | ||||||
|   if flag(sdl) |   if flag(sdl) | ||||||
|     exposed-modules: |     exposed-modules: | ||||||
|       DearImGui.SDL |       DearImGui.SDL | ||||||
| @@ -83,6 +112,10 @@ library | |||||||
|       exposed-modules: |       exposed-modules: | ||||||
|         DearImGui.SDL.OpenGL |         DearImGui.SDL.OpenGL | ||||||
|  |  | ||||||
|  |     if flag(vulkan) | ||||||
|  |       exposed-modules: | ||||||
|  |         DearImGui.SDL.Vulkan | ||||||
|  |  | ||||||
|  |  | ||||||
| executable test | executable test | ||||||
|   main-is: Main.hs |   main-is: Main.hs | ||||||
| @@ -97,3 +130,38 @@ executable readme | |||||||
|   default-language: Haskell2010 |   default-language: Haskell2010 | ||||||
|   build-depends: base, sdl2, gl, dear-imgui, managed |   build-depends: base, sdl2, gl, dear-imgui, managed | ||||||
|   ghc-options: -Wall |   ghc-options: -Wall | ||||||
|  |  | ||||||
|  | executable vulkan | ||||||
|  |   main-is: Main.hs | ||||||
|  |   other-modules: Attachments, Backend, Input, Util | ||||||
|  |   hs-source-dirs: examples/vulkan | ||||||
|  |   default-language: Haskell2010 | ||||||
|  |   build-depends: | ||||||
|  |       dear-imgui | ||||||
|  |     , base | ||||||
|  |         >= 4.13 && < 4.16 | ||||||
|  |     , bytestring | ||||||
|  |         >= 0.10.10.0 && < 0.12 | ||||||
|  |     , containers | ||||||
|  |        ^>= 0.6.2.1 | ||||||
|  |     , logging-effect | ||||||
|  |        ^>= 1.3.12 | ||||||
|  |     , resourcet | ||||||
|  |        ^>= 1.2.4.2 | ||||||
|  |     , sdl2 | ||||||
|  |        ^>= 2.5.3.0 | ||||||
|  |     , text-short | ||||||
|  |        ^>= 0.1.3 | ||||||
|  |     , transformers | ||||||
|  |        ^>= 0.5.6.2 | ||||||
|  |     , unliftio | ||||||
|  |         >= 0.2.13 && < 0.2.15 | ||||||
|  |     , unliftio-core | ||||||
|  |        ^>= 0.2.0.1 | ||||||
|  |     , vector | ||||||
|  |        ^>= 0.12.1.2 | ||||||
|  |     , vulkan | ||||||
|  |        ^>= 3.9 | ||||||
|  |     , vulkan-utils | ||||||
|  |        ^>= 0.4.1 | ||||||
|  |   ghc-options: -Wall | ||||||
|   | |||||||
							
								
								
									
										246
									
								
								examples/vulkan/Attachments.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								examples/vulkan/Attachments.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,246 @@ | |||||||
|  | {-# LANGUAGE BlockArguments      #-} | ||||||
|  | {-# LANGUAGE DataKinds           #-} | ||||||
|  | {-# LANGUAGE DeriveTraversable   #-} | ||||||
|  | {-# LANGUAGE DerivingStrategies  #-} | ||||||
|  | {-# LANGUAGE PatternSynonyms     #-} | ||||||
|  | {-# LANGUAGE RecordWildCards     #-} | ||||||
|  | {-# LANGUAGE ScopedTypeVariables #-} | ||||||
|  | {-# LANGUAGE TypeApplications    #-} | ||||||
|  |  | ||||||
|  | module Attachments where | ||||||
|  |  | ||||||
|  | -- base | ||||||
|  | import Data.Word | ||||||
|  |   ( Word32 ) | ||||||
|  |  | ||||||
|  | -- vector | ||||||
|  | import qualified Data.Vector as Boxed | ||||||
|  |   ( Vector ) | ||||||
|  | import qualified Data.Vector as Boxed.Vector | ||||||
|  |   ( empty ) | ||||||
|  |  | ||||||
|  | -- vulkan | ||||||
|  | import qualified Vulkan | ||||||
|  | import qualified Vulkan.Core10.Pass as Vulkan.AttachmentReference | ||||||
|  |   ( AttachmentReference(..) ) | ||||||
|  | import qualified Vulkan.Zero as Vulkan | ||||||
|  |  | ||||||
|  | -- dear-imgui | ||||||
|  | import Util | ||||||
|  |   ( iunzipWith ) | ||||||
|  |  | ||||||
|  | --------------------------------------------------------------- | ||||||
|  | -- Attachment types and their corresponding image layouts. | ||||||
|  |  | ||||||
|  | data AttachmentAccess | ||||||
|  |   = ReadAttachment | ||||||
|  |   | ReadWriteAttachment | ||||||
|  |   deriving stock ( Eq, Show ) | ||||||
|  |  | ||||||
|  | data DepthStencilType = | ||||||
|  |   DepthStencilType | ||||||
|  |     { depth   :: Maybe AttachmentAccess | ||||||
|  |     , stencil :: Maybe AttachmentAccess | ||||||
|  |     } | ||||||
|  |   deriving stock ( Eq, Show ) | ||||||
|  |  | ||||||
|  | data InputAttachmentType | ||||||
|  |   = ColorInputAttachment | ||||||
|  |   | DepthInputAttachment | ||||||
|  |   | StencilInputAttachment | ||||||
|  |   | DepthStencilInputAttachment | ||||||
|  |   deriving stock ( Eq, Show ) | ||||||
|  |  | ||||||
|  | data AttachmentType | ||||||
|  |   = ColorAttachment | ||||||
|  |   | DepthStencilAttachment DepthStencilType | ||||||
|  |   | InputAttachment InputAttachmentType | ||||||
|  |   deriving stock ( Eq, Show ) | ||||||
|  |  | ||||||
|  | data AttachmentUsage | ||||||
|  |   = UseAttachment | ||||||
|  |   | PreserveAttachment | ||||||
|  |   | ResolveAttachment | ||||||
|  |   deriving stock ( Eq, Show ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | depthStencilAttachmentLayout :: DepthStencilType -> Vulkan.ImageLayout | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType Nothing Nothing ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_GENERAL | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType (Just ReadAttachment) Nothing ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL_KHR | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType (Just ReadWriteAttachment) Nothing ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL_KHR | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType Nothing (Just ReadAttachment) ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL_KHR | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType Nothing (Just ReadWriteAttachment) ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL_KHR | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType (Just ReadAttachment) (Just ReadAttachment) ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType (Just ReadWriteAttachment) (Just ReadAttachment) ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL_KHR | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType (Just ReadAttachment) (Just ReadWriteAttachment) ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR | ||||||
|  | depthStencilAttachmentLayout | ||||||
|  |   ( DepthStencilType (Just ReadWriteAttachment) (Just ReadWriteAttachment) ) | ||||||
|  |     = Vulkan.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL | ||||||
|  |  | ||||||
|  |  | ||||||
|  | inputAttachmentLayout :: InputAttachmentType -> Vulkan.ImageLayout | ||||||
|  | inputAttachmentLayout ColorInputAttachment | ||||||
|  |   = Vulkan.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL | ||||||
|  | inputAttachmentLayout DepthInputAttachment | ||||||
|  |   = depthStencilAttachmentLayout ( DepthStencilType (Just ReadAttachment) Nothing ) | ||||||
|  | inputAttachmentLayout StencilInputAttachment | ||||||
|  |   = depthStencilAttachmentLayout ( DepthStencilType Nothing (Just ReadAttachment) ) | ||||||
|  | inputAttachmentLayout DepthStencilInputAttachment | ||||||
|  |   = depthStencilAttachmentLayout ( DepthStencilType (Just ReadAttachment) (Just ReadAttachment) ) | ||||||
|  |  | ||||||
|  | attachmentLayout :: AttachmentType -> Vulkan.ImageLayout | ||||||
|  | attachmentLayout ColorAttachment | ||||||
|  |   = Vulkan.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL | ||||||
|  | attachmentLayout (DepthStencilAttachment depthStencilType) | ||||||
|  |   = depthStencilAttachmentLayout depthStencilType | ||||||
|  | attachmentLayout (InputAttachment inputAttachmentType) | ||||||
|  |   = inputAttachmentLayout inputAttachmentType | ||||||
|  |  | ||||||
|  | --------------------------------------------------------------- | ||||||
|  | -- Some simple attachment descriptions, for convenience. | ||||||
|  |  | ||||||
|  | presentableColorAttachmentDescription :: Vulkan.Format -> ( Vulkan.AttachmentDescription, AttachmentType ) | ||||||
|  | presentableColorAttachmentDescription colorFormat = | ||||||
|  |   ( description, ColorAttachment ) | ||||||
|  |   where | ||||||
|  |     description = | ||||||
|  |       Vulkan.AttachmentDescription | ||||||
|  |         { flags          = Vulkan.zero | ||||||
|  |         , format         = colorFormat | ||||||
|  |         , samples        = Vulkan.SAMPLE_COUNT_1_BIT | ||||||
|  |         , loadOp         = Vulkan.ATTACHMENT_LOAD_OP_CLEAR | ||||||
|  |         , storeOp        = Vulkan.ATTACHMENT_STORE_OP_STORE | ||||||
|  |         , stencilLoadOp  = Vulkan.ATTACHMENT_LOAD_OP_DONT_CARE | ||||||
|  |         , stencilStoreOp = Vulkan.ATTACHMENT_STORE_OP_DONT_CARE | ||||||
|  |         , initialLayout  = Vulkan.IMAGE_LAYOUT_UNDEFINED | ||||||
|  |         , finalLayout    = Vulkan.IMAGE_LAYOUT_PRESENT_SRC_KHR | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |  | ||||||
|  | depthAttachmentDescription :: Vulkan.Format -> ( Vulkan.AttachmentDescription, AttachmentType ) | ||||||
|  | depthAttachmentDescription depthFormat = | ||||||
|  |   ( description, DepthStencilAttachment ( DepthStencilType (Just ReadWriteAttachment) Nothing ) ) | ||||||
|  |     where | ||||||
|  |       description = | ||||||
|  |         Vulkan.AttachmentDescription | ||||||
|  |           { flags          = Vulkan.zero | ||||||
|  |           , format         = depthFormat | ||||||
|  |           , samples        = Vulkan.SAMPLE_COUNT_1_BIT | ||||||
|  |           , loadOp         = Vulkan.ATTACHMENT_LOAD_OP_CLEAR | ||||||
|  |           , storeOp        = Vulkan.ATTACHMENT_STORE_OP_STORE | ||||||
|  |           , stencilLoadOp  = Vulkan.ATTACHMENT_LOAD_OP_DONT_CARE | ||||||
|  |           , stencilStoreOp = Vulkan.ATTACHMENT_STORE_OP_DONT_CARE | ||||||
|  |           , initialLayout  = Vulkan.IMAGE_LAYOUT_UNDEFINED | ||||||
|  |           , finalLayout    = Vulkan.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL | ||||||
|  |           } | ||||||
|  |  | ||||||
|  | msDepthAttachmentDescription | ||||||
|  |   :: Vulkan.SampleCountFlagBits | ||||||
|  |   -> Vulkan.Format | ||||||
|  |   -> ( Vulkan.AttachmentDescription, AttachmentType ) | ||||||
|  | msDepthAttachmentDescription samples depthFormat = | ||||||
|  |   ( description, DepthStencilAttachment ( DepthStencilType (Just ReadWriteAttachment) Nothing ) ) | ||||||
|  |     where | ||||||
|  |       description = | ||||||
|  |         Vulkan.AttachmentDescription | ||||||
|  |           { flags          = Vulkan.zero | ||||||
|  |           , format         = depthFormat | ||||||
|  |           , samples        = samples | ||||||
|  |           , loadOp         = Vulkan.ATTACHMENT_LOAD_OP_CLEAR | ||||||
|  |           , storeOp        = Vulkan.ATTACHMENT_STORE_OP_STORE | ||||||
|  |           , stencilLoadOp  = Vulkan.ATTACHMENT_LOAD_OP_DONT_CARE | ||||||
|  |           , stencilStoreOp = Vulkan.ATTACHMENT_STORE_OP_DONT_CARE | ||||||
|  |           , initialLayout  = Vulkan.IMAGE_LAYOUT_UNDEFINED | ||||||
|  |           , finalLayout    = Vulkan.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL | ||||||
|  |           } | ||||||
|  |  | ||||||
|  | msColorAttachmentDescription | ||||||
|  |   :: Vulkan.SampleCountFlagBits | ||||||
|  |   -> Vulkan.Format | ||||||
|  |   -> ( Vulkan.AttachmentDescription, AttachmentType ) | ||||||
|  | msColorAttachmentDescription samples colorFormat = | ||||||
|  |   ( description, ColorAttachment ) | ||||||
|  |     where | ||||||
|  |     description = | ||||||
|  |       Vulkan.AttachmentDescription | ||||||
|  |         { flags          = Vulkan.zero | ||||||
|  |         , format         = colorFormat | ||||||
|  |         , samples        = samples | ||||||
|  |         , loadOp         = Vulkan.ATTACHMENT_LOAD_OP_CLEAR | ||||||
|  |         , storeOp        = Vulkan.ATTACHMENT_STORE_OP_STORE | ||||||
|  |         , stencilLoadOp  = Vulkan.ATTACHMENT_LOAD_OP_DONT_CARE | ||||||
|  |         , stencilStoreOp = Vulkan.ATTACHMENT_STORE_OP_DONT_CARE | ||||||
|  |         , initialLayout  = Vulkan.IMAGE_LAYOUT_UNDEFINED | ||||||
|  |         , finalLayout    = Vulkan.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | --------------------------------------------------------------- | ||||||
|  | -- Set the attachments in a subpass. | ||||||
|  |  | ||||||
|  | data SubpassAttachments a | ||||||
|  |   = SubpassAttachments | ||||||
|  |   { colorAttachments         :: Boxed.Vector a | ||||||
|  |   , mbDepthStencilAttachment :: Maybe a | ||||||
|  |   , inputAttachments         :: Boxed.Vector a | ||||||
|  |   , preserveAttachments      :: Boxed.Vector a | ||||||
|  |   , resolveAttachments       :: Boxed.Vector a | ||||||
|  |   } deriving stock ( Functor, Foldable, Traversable ) | ||||||
|  |  | ||||||
|  | type SubpassAttachmentReferences = SubpassAttachments Vulkan.AttachmentReference | ||||||
|  |  | ||||||
|  |  | ||||||
|  | noAttachments :: SubpassAttachments a | ||||||
|  | noAttachments = | ||||||
|  |   SubpassAttachments | ||||||
|  |   Boxed.Vector.empty | ||||||
|  |   Nothing | ||||||
|  |   Boxed.Vector.empty | ||||||
|  |   Boxed.Vector.empty | ||||||
|  |   Boxed.Vector.empty  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | createSubpass | ||||||
|  |   :: SubpassAttachmentReferences | ||||||
|  |   -> Vulkan.SubpassDescription | ||||||
|  | createSubpass SubpassAttachments { .. } = | ||||||
|  |   Vulkan.SubpassDescription | ||||||
|  |     { flags                  = Vulkan.zero | ||||||
|  |     , colorAttachments       = colorAttachments | ||||||
|  |     , pipelineBindPoint      = Vulkan.PIPELINE_BIND_POINT_GRAPHICS | ||||||
|  |     , depthStencilAttachment = mbDepthStencilAttachment | ||||||
|  |     , inputAttachments       = inputAttachments | ||||||
|  |     , preserveAttachments    = fmap Vulkan.AttachmentReference.attachment preserveAttachments | ||||||
|  |     , resolveAttachments     = resolveAttachments | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | attachmentReference :: Word32 -> AttachmentType -> Vulkan.AttachmentReference | ||||||
|  | attachmentReference attachmentNumber attachmentType = | ||||||
|  |   Vulkan.AttachmentReference | ||||||
|  |     { attachment = attachmentNumber | ||||||
|  |     , layout     = attachmentLayout attachmentType | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | attachmentReferencesAndDescriptions | ||||||
|  |   :: forall t. Traversable t | ||||||
|  |   => t ( Vulkan.AttachmentDescription, AttachmentType ) | ||||||
|  |   -> ( t Vulkan.AttachmentReference, [ Vulkan.AttachmentDescription ] ) | ||||||
|  | attachmentReferencesAndDescriptions = | ||||||
|  |   iunzipWith | ||||||
|  |     ( \ i -> attachmentReference i . snd ) | ||||||
|  |     ( const fst ) | ||||||
							
								
								
									
										783
									
								
								examples/vulkan/Backend.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										783
									
								
								examples/vulkan/Backend.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,783 @@ | |||||||
|  | {-# LANGUAGE BlockArguments #-} | ||||||
|  | {-# LANGUAGE DataKinds #-} | ||||||
|  | {-# LANGUAGE DeriveTraversable #-} | ||||||
|  | {-# LANGUAGE DerivingStrategies #-} | ||||||
|  | {-# LANGUAGE DuplicateRecordFields #-} | ||||||
|  | {-# LANGUAGE FlexibleContexts #-} | ||||||
|  | {-# LANGUAGE FlexibleInstances #-} | ||||||
|  | {-# LANGUAGE LambdaCase #-} | ||||||
|  | {-# LANGUAGE MonoLocalBinds #-} | ||||||
|  | {-# LANGUAGE NamedFieldPuns #-} | ||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | {-# LANGUAGE RecordWildCards #-} | ||||||
|  | {-# LANGUAGE ScopedTypeVariables #-} | ||||||
|  | {-# LANGUAGE TypeApplications #-} | ||||||
|  | {-# LANGUAGE UndecidableInstances #-} | ||||||
|  |  | ||||||
|  | module Backend where | ||||||
|  |  | ||||||
|  | -- base | ||||||
|  | import Control.Category | ||||||
|  |   ( (>>>) ) | ||||||
|  | import Control.Monad | ||||||
|  |   ( guard, unless, void ) | ||||||
|  | import Data.Bits | ||||||
|  |   ( (.&.), (.|.) ) | ||||||
|  | import Data.Coerce | ||||||
|  |   ( coerce ) | ||||||
|  | import Data.Foldable | ||||||
|  |   ( toList ) | ||||||
|  | import Data.Functor | ||||||
|  |   ( (<&>) ) | ||||||
|  | import Data.List | ||||||
|  |   ( sortOn ) | ||||||
|  | import Data.Maybe | ||||||
|  |   ( fromMaybe ) | ||||||
|  | import Data.Ord | ||||||
|  |   ( Down(..) ) | ||||||
|  | import Data.Semigroup | ||||||
|  |   ( First(..) ) | ||||||
|  | import Data.String | ||||||
|  |   ( fromString ) | ||||||
|  | import Data.Traversable | ||||||
|  |   ( for ) | ||||||
|  | import Data.Word | ||||||
|  |   ( Word32 ) | ||||||
|  | import Foreign.C.String | ||||||
|  |   ( CString ) | ||||||
|  | import Foreign.C.Types | ||||||
|  |   ( CInt ) | ||||||
|  | import Foreign.Ptr | ||||||
|  |   ( castPtr ) | ||||||
|  |  | ||||||
|  | -- bytestring | ||||||
|  | import Data.ByteString | ||||||
|  |   ( ByteString ) | ||||||
|  | import qualified Data.ByteString.Short as ShortByteString | ||||||
|  |   ( packCString ) | ||||||
|  |  | ||||||
|  | -- containers | ||||||
|  | import qualified Data.Map.Strict as Map | ||||||
|  |   ( empty, insertWith, toList ) | ||||||
|  |  | ||||||
|  | -- logging-effect | ||||||
|  | import Control.Monad.Log | ||||||
|  |   ( MonadLog, Severity(..), WithSeverity(..) | ||||||
|  |   , logDebug, logInfo | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  | -- resourcet | ||||||
|  | import Control.Monad.Trans.Resource | ||||||
|  |   ( MonadResource ) | ||||||
|  | import qualified Control.Monad.Trans.Resource as ResourceT | ||||||
|  |   ( ReleaseKey, allocate ) | ||||||
|  |  | ||||||
|  | -- sdl2 | ||||||
|  | import qualified SDL | ||||||
|  | import qualified SDL.Raw | ||||||
|  | import qualified SDL.Video.Vulkan | ||||||
|  |  | ||||||
|  | -- text-short | ||||||
|  | import Data.Text.Short | ||||||
|  |   ( ShortText ) | ||||||
|  | import qualified Data.Text.Short as ShortText | ||||||
|  |   ( intercalate, pack, fromShortByteString, toByteString, unpack ) | ||||||
|  |  | ||||||
|  | -- transformers | ||||||
|  | import Control.Monad.IO.Class | ||||||
|  |   ( MonadIO(liftIO) ) | ||||||
|  |  | ||||||
|  | -- unliftio-core | ||||||
|  | import Control.Monad.IO.Unlift | ||||||
|  |   ( MonadUnliftIO ) | ||||||
|  |  | ||||||
|  | -- vector | ||||||
|  | import qualified Data.Vector as Boxed | ||||||
|  |   ( Vector ) | ||||||
|  | import qualified Data.Vector as Boxed.Vector | ||||||
|  |   ( (!?), empty, find, fromList, imap, imapMaybe, singleton, toList ) | ||||||
|  |  | ||||||
|  | -- vulkan | ||||||
|  | import qualified Vulkan | ||||||
|  | import qualified Vulkan.CStruct.Extends as Vulkan | ||||||
|  | import qualified Vulkan.Requirement     as Vulkan | ||||||
|  | import qualified Vulkan.Zero            as Vulkan | ||||||
|  |  | ||||||
|  | -- vulkan-utils | ||||||
|  | import qualified Vulkan.Utils.Initialization as Vulkan.Utils | ||||||
|  |   ( createInstanceFromRequirements, createDebugInstanceFromRequirements | ||||||
|  |   , createDeviceFromRequirements | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  | -- dear-imgui | ||||||
|  | import Attachments | ||||||
|  |   ( AttachmentType, SubpassAttachments, SubpassAttachmentReferences | ||||||
|  |   , attachmentReferencesAndDescriptions | ||||||
|  |   , createSubpass | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type LogMessage = WithSeverity ShortText | ||||||
|  | class    ( MonadUnliftIO m, MonadResource m, MonadLog LogMessage m ) => MonadVulkan m | ||||||
|  | instance ( MonadUnliftIO m, MonadResource m, MonadLog LogMessage m ) => MonadVulkan m | ||||||
|  |  | ||||||
|  | ---------------------------------------------------------------------------- | ||||||
|  | -- Logging. | ||||||
|  |  | ||||||
|  | logHandler :: MonadIO m => LogMessage -> m () | ||||||
|  | logHandler ( WithSeverity sev mess ) | ||||||
|  |   = liftIO . putStrLn . ShortText.unpack $ showSeverity sev <> " " <> mess | ||||||
|  |  | ||||||
|  | showSeverity :: Severity -> ShortText | ||||||
|  | showSeverity Emergency     = "! PANIC !" | ||||||
|  | showSeverity Alert         = "! ALERT !" | ||||||
|  | showSeverity Critical      = "! CRIT !" | ||||||
|  | showSeverity Error         = "[ERR]  " | ||||||
|  | showSeverity Warning       = "[WARN] " | ||||||
|  | showSeverity Notice        = "(note) " | ||||||
|  | showSeverity Informational = "(info) " | ||||||
|  | showSeverity Debug         = "(debug)" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | data VulkanContext = | ||||||
|  |   VulkanContext | ||||||
|  |     { instance'      :: !Vulkan.Instance | ||||||
|  |     , physicalDevice :: !Vulkan.PhysicalDevice | ||||||
|  |     , device         :: !Vulkan.Device | ||||||
|  |     , queueFamily    :: !Word32 | ||||||
|  |     , queue          :: !Vulkan.Queue | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | data InstanceType | ||||||
|  |   = NormalInstance | ||||||
|  |   | DebugInstance | ||||||
|  |   deriving stock Show | ||||||
|  |  | ||||||
|  | data VulkanRequirements = | ||||||
|  |   VulkanRequirements | ||||||
|  |     { instanceRequirements :: [ Vulkan.InstanceRequirement ] | ||||||
|  |     , deviceRequirements   :: [ Vulkan.DeviceRequirement   ] | ||||||
|  |     , queueFlags           :: Vulkan.QueueFlags | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | data ValidationLayerName | ||||||
|  |   = LunarG | ||||||
|  |   | Khronos | ||||||
|  |   deriving stock ( Eq, Show ) | ||||||
|  |  | ||||||
|  | initialiseVulkanContext :: MonadVulkan m => InstanceType -> ByteString -> VulkanRequirements -> m VulkanContext | ||||||
|  | initialiseVulkanContext instanceType appName ( VulkanRequirements { instanceRequirements, deviceRequirements, queueFlags } ) = do | ||||||
|  |   logDebug "Creating Vulkan instance" | ||||||
|  |   instanceInfo    <- vulkanInstanceInfo appName | ||||||
|  |   instance'       <- case instanceType of | ||||||
|  |     NormalInstance -> Vulkan.Utils.createInstanceFromRequirements      instanceRequirements [] instanceInfo | ||||||
|  |     DebugInstance  -> Vulkan.Utils.createDebugInstanceFromRequirements instanceRequirements [] instanceInfo | ||||||
|  |   physicalDevice  <- logDebug "Creating physical device"      *> createPhysicalDevice instance' | ||||||
|  |   queueFamily     <- logDebug "Finding suitable queue family" *> findQueueFamilyIndex physicalDevice queueFlags | ||||||
|  |   let | ||||||
|  |     queueCreateInfo :: Vulkan.DeviceQueueCreateInfo '[] | ||||||
|  |     queueCreateInfo = Vulkan.zero | ||||||
|  |       { Vulkan.queueFamilyIndex = fromIntegral queueFamily | ||||||
|  |       , Vulkan.queuePriorities  = Boxed.Vector.singleton ( 1.0 :: Float ) | ||||||
|  |       } | ||||||
|  |     deviceCreateInfo :: Vulkan.DeviceCreateInfo '[] | ||||||
|  |     deviceCreateInfo = Vulkan.zero { Vulkan.queueCreateInfos = Boxed.Vector.singleton ( Vulkan.SomeStruct queueCreateInfo ) } | ||||||
|  |     swapchainDeviceRequirements :: [ Vulkan.DeviceRequirement ] | ||||||
|  |     swapchainDeviceRequirements | ||||||
|  |       = Vulkan.RequireDeviceExtension Nothing Vulkan.KHR_SWAPCHAIN_EXTENSION_NAME 0 | ||||||
|  |       : deviceRequirements | ||||||
|  |   device <- logDebug "Creating logical device" *> | ||||||
|  |     Vulkan.Utils.createDeviceFromRequirements swapchainDeviceRequirements [] physicalDevice deviceCreateInfo | ||||||
|  |   queue  <- Vulkan.getDeviceQueue device ( fromIntegral queueFamily ) 0 | ||||||
|  |    | ||||||
|  |   pure ( VulkanContext { .. } ) | ||||||
|  |    | ||||||
|  |  | ||||||
|  |  | ||||||
|  | vulkanInstanceInfo | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => ByteString | ||||||
|  |   -> m ( Vulkan.InstanceCreateInfo '[] ) | ||||||
|  | vulkanInstanceInfo appName = do | ||||||
|  |  | ||||||
|  |   ( availableLayers :: Boxed.Vector Vulkan.LayerProperties ) <- snd <$> Vulkan.enumerateInstanceLayerProperties | ||||||
|  |  | ||||||
|  |   let | ||||||
|  |     validationLayer :: Maybe ValidationLayerName | ||||||
|  |     validationLayer | ||||||
|  |       = coerce  | ||||||
|  |       . foldMap | ||||||
|  |         (  (  Vulkan.layerName :: Vulkan.LayerProperties -> ByteString ) | ||||||
|  |         >>> \case | ||||||
|  |               "VK_LAYER_LUNARG_standard_validation" -> Just ( First LunarG  ) | ||||||
|  |               "VK_LAYER_KHRONOS_validation"         -> Just ( First Khronos ) | ||||||
|  |               _                                     -> Nothing | ||||||
|  |         ) | ||||||
|  |       $ availableLayers | ||||||
|  |  | ||||||
|  |     enabledLayers :: [ ByteString ] | ||||||
|  |     enabledLayers = case validationLayer of | ||||||
|  |       Nothing      -> [] | ||||||
|  |       Just LunarG  -> [ "VK_LAYER_LUNARG_standard_validation" ] | ||||||
|  |       Just Khronos -> [ "VK_LAYER_KHRONOS_validation" ] | ||||||
|  |  | ||||||
|  |     appInfo :: Vulkan.ApplicationInfo | ||||||
|  |     appInfo = | ||||||
|  |       Vulkan.ApplicationInfo | ||||||
|  |         { Vulkan.applicationName    = Just appName | ||||||
|  |         , Vulkan.applicationVersion = 0 | ||||||
|  |         , Vulkan.engineName         = Nothing | ||||||
|  |         , Vulkan.engineVersion      = 0 | ||||||
|  |         , Vulkan.apiVersion         = Vulkan.API_VERSION_1_2 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     createInfo :: Vulkan.InstanceCreateInfo '[] | ||||||
|  |     createInfo = | ||||||
|  |       Vulkan.InstanceCreateInfo | ||||||
|  |         { Vulkan.next                  = () | ||||||
|  |         , Vulkan.flags                 = Vulkan.zero | ||||||
|  |         , Vulkan.applicationInfo       = Just appInfo | ||||||
|  |         , Vulkan.enabledLayerNames     = Boxed.Vector.fromList enabledLayers | ||||||
|  |         , Vulkan.enabledExtensionNames = mempty | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |   case validationLayer of | ||||||
|  |     Nothing -> logInfo "Validation layer unavailable. Is the Vulkan SDK installed?" | ||||||
|  |     Just _  -> logInfo ( "Enabled validation layers " <> ShortText.pack ( show enabledLayers ) ) | ||||||
|  |  | ||||||
|  |   pure createInfo | ||||||
|  |  | ||||||
|  | createPhysicalDevice :: MonadVulkan m => Vulkan.Instance -> m Vulkan.PhysicalDevice | ||||||
|  | createPhysicalDevice vk = do | ||||||
|  |   physicalDevices <- snd <$> Vulkan.enumeratePhysicalDevices vk | ||||||
|  |  | ||||||
|  |   typedDevices <- | ||||||
|  |     for physicalDevices \ physicalDevice -> do | ||||||
|  |       properties <- Vulkan.getPhysicalDeviceProperties physicalDevice | ||||||
|  |       pure ( physicalDevice, Vulkan.deviceType properties ) | ||||||
|  |  | ||||||
|  |   case Boxed.Vector.find ( isSuitableDeviceType . snd ) typedDevices of | ||||||
|  |     Nothing       -> error "Could not find a suitable physical device" | ||||||
|  |     Just ( d, _ ) -> pure d | ||||||
|  |  | ||||||
|  |   where | ||||||
|  |     isSuitableDeviceType :: Vulkan.PhysicalDeviceType -> Bool | ||||||
|  |     isSuitableDeviceType | ||||||
|  |       = flip elem | ||||||
|  |           [ Vulkan.PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU | ||||||
|  |           , Vulkan.PHYSICAL_DEVICE_TYPE_DISCRETE_GPU | ||||||
|  |           ] | ||||||
|  |  | ||||||
|  | findQueueFamilyIndex | ||||||
|  |   :: MonadIO m | ||||||
|  |   => Vulkan.PhysicalDevice | ||||||
|  |   -> Vulkan.QueueFlags | ||||||
|  |   -> m Word32 | ||||||
|  | findQueueFamilyIndex physicalDevice requiredFlags = do | ||||||
|  |   queueFamilies <- Vulkan.getPhysicalDeviceQueueFamilyProperties physicalDevice | ||||||
|  |   let | ||||||
|  |     capableFamilyIndices :: Boxed.Vector Int | ||||||
|  |     capableFamilyIndices = ( `Boxed.Vector.imapMaybe` queueFamilies ) \ i queueFamily -> do | ||||||
|  |       guard ( Vulkan.queueFlags queueFamily .&. requiredFlags > Vulkan.zero ) | ||||||
|  |       pure i | ||||||
|  |   case capableFamilyIndices Boxed.Vector.!? 0 of | ||||||
|  |     Nothing -> error "No queue family has sufficient capabilities" | ||||||
|  |     Just i  -> pure ( fromIntegral i ) | ||||||
|  |  | ||||||
|  | instanceExtensions :: [ ByteString ] -> [ Vulkan.InstanceRequirement ] | ||||||
|  | instanceExtensions = map mkExtensionRequirement | ||||||
|  |   where | ||||||
|  |     mkExtensionRequirement :: ByteString -> Vulkan.InstanceRequirement | ||||||
|  |     mkExtensionRequirement extName = | ||||||
|  |       Vulkan.RequireInstanceExtension | ||||||
|  |         { Vulkan.instanceExtensionLayerName  = Nothing | ||||||
|  |         , Vulkan.instanceExtensionName       = extName | ||||||
|  |         , Vulkan.instanceExtensionMinVersion = 0 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | initialiseWindow :: MonadVulkan m => WindowInfo -> m ( SDL.Window, [ ByteString ] ) | ||||||
|  | initialiseWindow ( WindowInfo { height, width, windowName, mouseMode } ) = do | ||||||
|  |   logDebug "Initializing SDL" | ||||||
|  |   SDL.Raw.logSetAllPriority SDL.Raw.SDL_LOG_PRIORITY_VERBOSE | ||||||
|  |   SDL.initialize [ SDL.InitVideo ] | ||||||
|  |   void ( SDL.setMouseLocationMode mouseMode ) | ||||||
|  |   window           <- logDebug "Creating SDL window"           *> createWindow width height windowName | ||||||
|  |   neededExtensions <- logDebug "Loading needed extensions"     *> SDL.Video.Vulkan.vkGetInstanceExtensions window | ||||||
|  |   extensionNames   <- traverse ( liftIO . peekCString ) neededExtensions | ||||||
|  |   logInfo $ "Needed instance extensions are: " <> ShortText.intercalate ", " extensionNames | ||||||
|  |   pure ( window, map ShortText.toByteString extensionNames ) | ||||||
|  |  | ||||||
|  | peekCString :: CString -> IO ShortText | ||||||
|  | peekCString = fmap ( fromMaybe "???" . ShortText.fromShortByteString ) . ShortByteString.packCString | ||||||
|  |  | ||||||
|  | data WindowInfo | ||||||
|  |   = WindowInfo | ||||||
|  |   { width      :: CInt | ||||||
|  |   , height     :: CInt | ||||||
|  |   , windowName :: ShortText | ||||||
|  |   , mouseMode  :: SDL.LocationMode | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | createWindow :: MonadVulkan m => CInt -> CInt -> ShortText -> m SDL.Window | ||||||
|  | createWindow x y title = | ||||||
|  |   snd <$> ResourceT.allocate | ||||||
|  |     ( SDL.createWindow | ||||||
|  |               ( fromString ( ShortText.unpack title ) ) | ||||||
|  |               SDL.defaultWindow | ||||||
|  |                 { SDL.windowGraphicsContext = SDL.VulkanContext | ||||||
|  |                 , SDL.windowInitialSize     = SDL.V2 x y | ||||||
|  |                 , SDL.windowResizable       = True | ||||||
|  |                 } | ||||||
|  |     ) | ||||||
|  |     SDL.destroyWindow | ||||||
|  |  | ||||||
|  | createSurface | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => SDL.Window | ||||||
|  |   -> Vulkan.Instance | ||||||
|  |   -> m SDL.Video.Vulkan.VkSurfaceKHR | ||||||
|  | createSurface window vulkanInstance = | ||||||
|  |   snd <$> ResourceT.allocate | ||||||
|  |     ( SDL.Video.Vulkan.vkCreateSurface window ( castPtr $ Vulkan.instanceHandle vulkanInstance ) ) | ||||||
|  |     ( \ surf -> Vulkan.destroySurfaceKHR vulkanInstance ( Vulkan.SurfaceKHR surf ) Nothing ) | ||||||
|  |  | ||||||
|  | assertSurfacePresentable | ||||||
|  |   :: MonadIO m | ||||||
|  |   => Vulkan.PhysicalDevice | ||||||
|  |   -> Word32 | ||||||
|  |   -> SDL.Video.Vulkan.VkSurfaceKHR | ||||||
|  |   -> m () | ||||||
|  | assertSurfacePresentable physicalDevice queueFamilyIndex surface = do | ||||||
|  |   isPresentable <- | ||||||
|  |     Vulkan.getPhysicalDeviceSurfaceSupportKHR | ||||||
|  |       physicalDevice | ||||||
|  |       queueFamilyIndex | ||||||
|  |       ( Vulkan.SurfaceKHR surface ) | ||||||
|  |  | ||||||
|  |   unless isPresentable ( error "Surface is not presentable" ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | chooseSwapchainFormat | ||||||
|  |   :: MonadIO m | ||||||
|  |   => Vulkan.SurfaceFormatKHR | ||||||
|  |   -> Vulkan.PhysicalDevice | ||||||
|  |   -> SDL.Video.Vulkan.VkSurfaceKHR | ||||||
|  |   -> m Vulkan.SurfaceFormatKHR | ||||||
|  | chooseSwapchainFormat | ||||||
|  |   preferredFormat@( Vulkan.SurfaceFormatKHR fmt_p spc_p ) | ||||||
|  |   physicalDevice | ||||||
|  |   surface | ||||||
|  |   = do | ||||||
|  |       surfaceFormats <- snd <$> Vulkan.getPhysicalDeviceSurfaceFormatsKHR physicalDevice ( Vulkan.SurfaceKHR surface ) | ||||||
|  |  | ||||||
|  |       case sortOn ( Down . score ) ( Boxed.Vector.toList surfaceFormats ) of | ||||||
|  |         [] -> error "No formats found." | ||||||
|  |         ( best : _ ) | ||||||
|  |           | Vulkan.FORMAT_UNDEFINED <- ( Vulkan.format :: Vulkan.SurfaceFormatKHR -> Vulkan.Format ) best | ||||||
|  |             -> pure preferredFormat | ||||||
|  |           | otherwise | ||||||
|  |             -> pure best | ||||||
|  |  | ||||||
|  |     where | ||||||
|  |       match :: Eq a => a -> a -> Int | ||||||
|  |       match a b | ||||||
|  |         | a == b    = 1 | ||||||
|  |         | otherwise = 0 | ||||||
|  |  | ||||||
|  |       score :: Vulkan.SurfaceFormatKHR -> Int | ||||||
|  |       score ( Vulkan.SurfaceFormatKHR fmt spc ) | ||||||
|  |         = match fmt fmt_p | ||||||
|  |         + match spc spc_p | ||||||
|  |  | ||||||
|  | createSwapchain | ||||||
|  |   :: ( MonadIO m, MonadVulkan m ) | ||||||
|  |   => Vulkan.PhysicalDevice | ||||||
|  |   -> Vulkan.Device | ||||||
|  |   -> SDL.Video.Vulkan.VkSurfaceKHR | ||||||
|  |   -> Vulkan.SurfaceFormatKHR | ||||||
|  |   -> Vulkan.ImageUsageFlags | ||||||
|  |   -> Word32 | ||||||
|  |   -> Maybe Vulkan.SwapchainKHR | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Vulkan.SwapchainKHR, Vulkan.Extent2D ) | ||||||
|  | createSwapchain physicalDevice device surface surfaceFormat imageUsage imageCount oldSwapchain = do | ||||||
|  |  | ||||||
|  |   surfaceCapabilities <- Vulkan.getPhysicalDeviceSurfaceCapabilitiesKHR physicalDevice ( Vulkan.SurfaceKHR surface ) | ||||||
|  |  | ||||||
|  |   ( _, presentModes ) <- Vulkan.getPhysicalDeviceSurfacePresentModesKHR physicalDevice ( Vulkan.SurfaceKHR surface ) | ||||||
|  |    | ||||||
|  |   let | ||||||
|  |     presentMode :: Vulkan.PresentModeKHR | ||||||
|  |     presentMode  | ||||||
|  |       | Vulkan.PRESENT_MODE_MAILBOX_KHR `elem` presentModes | ||||||
|  |       = Vulkan.PRESENT_MODE_MAILBOX_KHR | ||||||
|  |       | otherwise | ||||||
|  |       = Vulkan.PRESENT_MODE_FIFO_KHR | ||||||
|  |  | ||||||
|  |     currentExtent :: Vulkan.Extent2D | ||||||
|  |     currentExtent = ( Vulkan.currentExtent :: Vulkan.SurfaceCapabilitiesKHR -> Vulkan.Extent2D ) surfaceCapabilities | ||||||
|  |  | ||||||
|  |     currentTransform :: Vulkan.SurfaceTransformFlagBitsKHR | ||||||
|  |     currentTransform = ( Vulkan.currentTransform :: Vulkan.SurfaceCapabilitiesKHR -> Vulkan.SurfaceTransformFlagBitsKHR ) surfaceCapabilities | ||||||
|  |  | ||||||
|  |     swapchainCreateInfo :: Vulkan.SwapchainCreateInfoKHR '[] | ||||||
|  |     swapchainCreateInfo = | ||||||
|  |       Vulkan.SwapchainCreateInfoKHR | ||||||
|  |         { Vulkan.next                  = () | ||||||
|  |         , Vulkan.flags                 = Vulkan.zero | ||||||
|  |         , Vulkan.surface               = Vulkan.SurfaceKHR surface | ||||||
|  |         , Vulkan.minImageCount         = imageCount | ||||||
|  |         , Vulkan.imageFormat           = ( Vulkan.format     :: Vulkan.SurfaceFormatKHR -> Vulkan.Format        ) surfaceFormat | ||||||
|  |         , Vulkan.imageColorSpace       = ( Vulkan.colorSpace :: Vulkan.SurfaceFormatKHR -> Vulkan.ColorSpaceKHR ) surfaceFormat | ||||||
|  |         , Vulkan.imageExtent           = currentExtent | ||||||
|  |         , Vulkan.imageArrayLayers      = 1 | ||||||
|  |         , Vulkan.imageUsage            = imageUsage | ||||||
|  |         , Vulkan.imageSharingMode      = Vulkan.SHARING_MODE_EXCLUSIVE | ||||||
|  |         , Vulkan.queueFamilyIndices    = Boxed.Vector.empty | ||||||
|  |         , Vulkan.preTransform          = currentTransform | ||||||
|  |         , Vulkan.compositeAlpha        = Vulkan.COMPOSITE_ALPHA_OPAQUE_BIT_KHR | ||||||
|  |         , Vulkan.presentMode           = presentMode | ||||||
|  |         , Vulkan.clipped               = True | ||||||
|  |         , Vulkan.oldSwapchain          = fromMaybe Vulkan.NULL_HANDLE oldSwapchain | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |   ( key, swapchain ) <- Vulkan.withSwapchainKHR device swapchainCreateInfo Nothing ResourceT.allocate | ||||||
|  |   pure ( key, swapchain, currentExtent ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | simpleRenderPass | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> SubpassAttachments ( Vulkan.AttachmentDescription, AttachmentType ) | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Vulkan.RenderPass ) | ||||||
|  | simpleRenderPass dev attachments = Vulkan.withRenderPass dev createInfo Nothing ResourceT.allocate | ||||||
|  |   where | ||||||
|  |  | ||||||
|  |     attachmentReferences   :: SubpassAttachmentReferences | ||||||
|  |     attachmentDescriptions :: [ Vulkan.AttachmentDescription ] | ||||||
|  |     ( attachmentReferences, attachmentDescriptions ) | ||||||
|  |       = attachmentReferencesAndDescriptions attachments | ||||||
|  |  | ||||||
|  |     subpass :: Vulkan.SubpassDescription | ||||||
|  |     subpass = createSubpass attachmentReferences | ||||||
|  |  | ||||||
|  |     dependency1 :: Vulkan.SubpassDependency | ||||||
|  |     dependency1 = | ||||||
|  |       Vulkan.SubpassDependency | ||||||
|  |         { Vulkan.srcSubpass      = Vulkan.SUBPASS_EXTERNAL | ||||||
|  |         , Vulkan.dstSubpass      = Vulkan.zero | ||||||
|  |         , Vulkan.srcStageMask    = Vulkan.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | ||||||
|  |         , Vulkan.srcAccessMask   = Vulkan.zero | ||||||
|  |         , Vulkan.dstStageMask    = Vulkan.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | ||||||
|  |         , Vulkan.dstAccessMask   = Vulkan.ACCESS_COLOR_ATTACHMENT_READ_BIT | ||||||
|  |                                .|. Vulkan.ACCESS_COLOR_ATTACHMENT_WRITE_BIT | ||||||
|  |         , Vulkan.dependencyFlags = Vulkan.zero | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     dependency2 :: Vulkan.SubpassDependency | ||||||
|  |     dependency2 = | ||||||
|  |       Vulkan.SubpassDependency | ||||||
|  |         { Vulkan.srcSubpass      = Vulkan.zero | ||||||
|  |         , Vulkan.dstSubpass      = Vulkan.SUBPASS_EXTERNAL | ||||||
|  |         , Vulkan.srcStageMask    = Vulkan.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | ||||||
|  |         , Vulkan.srcAccessMask   = Vulkan.ACCESS_COLOR_ATTACHMENT_READ_BIT | ||||||
|  |                                .|. Vulkan.ACCESS_COLOR_ATTACHMENT_WRITE_BIT | ||||||
|  |         , Vulkan.dstStageMask    = Vulkan.PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT | ||||||
|  |         , Vulkan.dstAccessMask   = Vulkan.zero | ||||||
|  |         , Vulkan.dependencyFlags = Vulkan.zero | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     createInfo :: Vulkan.RenderPassCreateInfo '[] | ||||||
|  |     createInfo = | ||||||
|  |       Vulkan.RenderPassCreateInfo | ||||||
|  |         { Vulkan.next         = () | ||||||
|  |         , Vulkan.flags        = Vulkan.zero | ||||||
|  |         , Vulkan.attachments  = Boxed.Vector.fromList attachmentDescriptions | ||||||
|  |         , Vulkan.subpasses    = Boxed.Vector.singleton subpass  | ||||||
|  |         , Vulkan.dependencies = Boxed.Vector.fromList [ dependency1, dependency2 ] | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | cmdBeginRenderPass | ||||||
|  |   :: MonadIO m | ||||||
|  |   => Vulkan.CommandBuffer | ||||||
|  |   -> Vulkan.RenderPass | ||||||
|  |   -> Vulkan.Framebuffer | ||||||
|  |   -> [ Vulkan.ClearValue ] | ||||||
|  |   -> Vulkan.Extent2D | ||||||
|  |   -> m () | ||||||
|  | cmdBeginRenderPass commandBuffer renderPass framebuffer clearValues extent = | ||||||
|  |   let | ||||||
|  |     zeroZero :: Vulkan.Offset2D | ||||||
|  |     zeroZero = | ||||||
|  |       Vulkan.Offset2D | ||||||
|  |         { Vulkan.x = 0 | ||||||
|  |         , Vulkan.y = 0 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     renderArea :: Vulkan.Rect2D | ||||||
|  |     renderArea = | ||||||
|  |       Vulkan.Rect2D | ||||||
|  |         { Vulkan.offset = zeroZero | ||||||
|  |         , Vulkan.extent = extent | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     beginInfo :: Vulkan.RenderPassBeginInfo '[] | ||||||
|  |     beginInfo = | ||||||
|  |       Vulkan.RenderPassBeginInfo | ||||||
|  |         { Vulkan.next        = () | ||||||
|  |         , Vulkan.renderPass  = renderPass | ||||||
|  |         , Vulkan.framebuffer = framebuffer | ||||||
|  |         , Vulkan.renderArea  = renderArea | ||||||
|  |         , Vulkan.clearValues = Boxed.Vector.fromList clearValues | ||||||
|  |         } | ||||||
|  |   in | ||||||
|  |     Vulkan.cmdBeginRenderPass | ||||||
|  |       commandBuffer | ||||||
|  |       beginInfo | ||||||
|  |       Vulkan.SUBPASS_CONTENTS_INLINE | ||||||
|  |  | ||||||
|  | cmdNextSubpass :: MonadIO m => Vulkan.CommandBuffer -> m () | ||||||
|  | cmdNextSubpass commandBuffer = Vulkan.cmdNextSubpass commandBuffer Vulkan.SUBPASS_CONTENTS_INLINE | ||||||
|  |  | ||||||
|  | cmdEndRenderPass :: MonadIO m => Vulkan.CommandBuffer -> m () | ||||||
|  | cmdEndRenderPass = Vulkan.cmdEndRenderPass | ||||||
|  |  | ||||||
|  | createImageView | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> Vulkan.Image | ||||||
|  |   -> Vulkan.ImageViewType | ||||||
|  |   -> Vulkan.Format | ||||||
|  |   -> Vulkan.ImageAspectFlags | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Vulkan.ImageView ) | ||||||
|  | createImageView dev image viewType fmt aspect = Vulkan.withImageView dev createInfo Nothing ResourceT.allocate | ||||||
|  |   where | ||||||
|  |     components :: Vulkan.ComponentMapping | ||||||
|  |     components = | ||||||
|  |       Vulkan.ComponentMapping | ||||||
|  |         { Vulkan.r = Vulkan.COMPONENT_SWIZZLE_IDENTITY | ||||||
|  |         , Vulkan.g = Vulkan.COMPONENT_SWIZZLE_IDENTITY | ||||||
|  |         , Vulkan.b = Vulkan.COMPONENT_SWIZZLE_IDENTITY | ||||||
|  |         , Vulkan.a = Vulkan.COMPONENT_SWIZZLE_IDENTITY | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     subResourceRange :: Vulkan.ImageSubresourceRange | ||||||
|  |     subResourceRange = | ||||||
|  |       Vulkan.ImageSubresourceRange | ||||||
|  |         { Vulkan.aspectMask     = aspect | ||||||
|  |         , Vulkan.baseMipLevel   = 0 | ||||||
|  |         , Vulkan.levelCount     = 1 | ||||||
|  |         , Vulkan.baseArrayLayer = 0 | ||||||
|  |         , Vulkan.layerCount     = 1 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     createInfo :: Vulkan.ImageViewCreateInfo '[] | ||||||
|  |     createInfo = | ||||||
|  |       Vulkan.ImageViewCreateInfo | ||||||
|  |         { Vulkan.next             = () | ||||||
|  |         , Vulkan.flags            = Vulkan.zero | ||||||
|  |         , Vulkan.image            = image | ||||||
|  |         , Vulkan.viewType         = viewType | ||||||
|  |         , Vulkan.format           = fmt | ||||||
|  |         , Vulkan.components       = components | ||||||
|  |         , Vulkan.subresourceRange = subResourceRange | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | createFramebuffer | ||||||
|  |   :: ( MonadVulkan m, Foldable f ) | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> Vulkan.RenderPass | ||||||
|  |   -> Vulkan.Extent2D | ||||||
|  |   -> f Vulkan.ImageView | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Vulkan.Framebuffer ) | ||||||
|  | createFramebuffer dev renderPass extent attachments = Vulkan.withFramebuffer dev createInfo Nothing ResourceT.allocate | ||||||
|  |   where | ||||||
|  |     createInfo :: Vulkan.FramebufferCreateInfo '[] | ||||||
|  |     createInfo = | ||||||
|  |       Vulkan.FramebufferCreateInfo | ||||||
|  |         { Vulkan.next        = () | ||||||
|  |         , Vulkan.flags       = Vulkan.zero | ||||||
|  |         , Vulkan.renderPass  = renderPass | ||||||
|  |         , Vulkan.attachments = Boxed.Vector.fromList . toList $ attachments | ||||||
|  |         , Vulkan.width       = ( Vulkan.width  :: Vulkan.Extent2D -> Word32 ) extent | ||||||
|  |         , Vulkan.height      = ( Vulkan.height :: Vulkan.Extent2D -> Word32 ) extent | ||||||
|  |         , Vulkan.layers      = 1 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | createDescriptorPool | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> Int | ||||||
|  |   -> [ ( Vulkan.DescriptorType, Int ) ] | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Vulkan.DescriptorPool ) | ||||||
|  | createDescriptorPool device maxSets descTypes = Vulkan.withDescriptorPool device createInfo Nothing ResourceT.allocate | ||||||
|  |  | ||||||
|  |     where | ||||||
|  |       poolSizes :: [ Vulkan.DescriptorPoolSize ] | ||||||
|  |       poolSizes = | ||||||
|  |         counts descTypes <&> \ ( descType, descCount ) -> | ||||||
|  |           Vulkan.DescriptorPoolSize | ||||||
|  |           { Vulkan.type'           = descType | ||||||
|  |           , Vulkan.descriptorCount = fromIntegral $ maxSets * descCount | ||||||
|  |           } | ||||||
|  |       createInfo :: Vulkan.DescriptorPoolCreateInfo '[] | ||||||
|  |       createInfo = | ||||||
|  |         Vulkan.DescriptorPoolCreateInfo | ||||||
|  |           { Vulkan.next      = () | ||||||
|  |           , Vulkan.flags     = Vulkan.DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT | ||||||
|  |           , Vulkan.poolSizes = Boxed.Vector.fromList poolSizes | ||||||
|  |           , Vulkan.maxSets   = fromIntegral maxSets | ||||||
|  |           } | ||||||
|  |  | ||||||
|  | counts :: ( Ord a, Num i ) => [ ( a, i ) ] -> [ ( a, i ) ] | ||||||
|  | counts = Map.toList . foldr ( uncurry $ Map.insertWith (+) ) Map.empty | ||||||
|  |  | ||||||
|  | createDescriptorSetLayout | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> [ ( Vulkan.DescriptorType, Vulkan.ShaderStageFlags ) ] | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Vulkan.DescriptorSetLayout ) | ||||||
|  | createDescriptorSetLayout device descriptorTypes = Vulkan.withDescriptorSetLayout device createInfo Nothing ResourceT.allocate | ||||||
|  |  | ||||||
|  |       where | ||||||
|  |         bindings :: Boxed.Vector Vulkan.DescriptorSetLayoutBinding | ||||||
|  |         bindings = ( `Boxed.Vector.imap` Boxed.Vector.fromList descriptorTypes ) \ i ( descType, descStageFlags ) -> | ||||||
|  |           Vulkan.DescriptorSetLayoutBinding | ||||||
|  |             { Vulkan.binding           = fromIntegral i | ||||||
|  |             , Vulkan.descriptorType    = descType | ||||||
|  |             , Vulkan.descriptorCount   = 1 | ||||||
|  |             , Vulkan.stageFlags        = descStageFlags | ||||||
|  |             , Vulkan.immutableSamplers = Boxed.Vector.empty | ||||||
|  |             } | ||||||
|  |         createInfo :: Vulkan.DescriptorSetLayoutCreateInfo '[] | ||||||
|  |         createInfo = | ||||||
|  |           Vulkan.DescriptorSetLayoutCreateInfo | ||||||
|  |             { Vulkan.next     = () | ||||||
|  |             , Vulkan.flags    = Vulkan.zero | ||||||
|  |             , Vulkan.bindings = bindings | ||||||
|  |             } | ||||||
|  |  | ||||||
|  | createCommandPool | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> Vulkan.CommandPoolCreateFlagBits | ||||||
|  |   -> Word32 | ||||||
|  |   -> m Vulkan.CommandPool | ||||||
|  | createCommandPool dev flags queueFamilyIndex = snd <$> Vulkan.withCommandPool dev createInfo Nothing ResourceT.allocate | ||||||
|  |   where | ||||||
|  |     createInfo :: Vulkan.CommandPoolCreateInfo | ||||||
|  |     createInfo = | ||||||
|  |       Vulkan.CommandPoolCreateInfo | ||||||
|  |         { Vulkan.flags            = flags | ||||||
|  |         , Vulkan.queueFamilyIndex = queueFamilyIndex | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | allocatePrimaryCommandBuffers | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> Vulkan.CommandPool | ||||||
|  |   -> Word32 | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Boxed.Vector Vulkan.CommandBuffer ) | ||||||
|  | allocatePrimaryCommandBuffers dev commandPool count = Vulkan.withCommandBuffers dev allocInfo ResourceT.allocate | ||||||
|  |     where | ||||||
|  |       allocInfo :: Vulkan.CommandBufferAllocateInfo | ||||||
|  |       allocInfo = | ||||||
|  |         Vulkan.CommandBufferAllocateInfo | ||||||
|  |           { Vulkan.commandPool        = commandPool | ||||||
|  |           , Vulkan.level              = Vulkan.COMMAND_BUFFER_LEVEL_PRIMARY | ||||||
|  |           , Vulkan.commandBufferCount = count | ||||||
|  |           } | ||||||
|  |  | ||||||
|  | submitCommandBuffer | ||||||
|  |   :: MonadIO m | ||||||
|  |   => Vulkan.Queue | ||||||
|  |   -> Vulkan.CommandBuffer | ||||||
|  |   -> [ ( Vulkan.Semaphore, Vulkan.PipelineStageFlags ) ] | ||||||
|  |   -> [ Vulkan.Semaphore ] | ||||||
|  |   -> Maybe Vulkan.Fence | ||||||
|  |   -> m () | ||||||
|  | submitCommandBuffer queue commandBuffer wait signal mbFence = | ||||||
|  |   Vulkan.queueSubmit queue ( Boxed.Vector.singleton $ Vulkan.SomeStruct submitInfo ) ( fromMaybe Vulkan.NULL_HANDLE mbFence ) | ||||||
|  |     where | ||||||
|  |       submitInfo :: Vulkan.SubmitInfo '[] | ||||||
|  |       submitInfo = | ||||||
|  |         Vulkan.SubmitInfo | ||||||
|  |           { Vulkan.next             = () | ||||||
|  |           , Vulkan.waitSemaphores   = Boxed.Vector.fromList $ map fst wait | ||||||
|  |           , Vulkan.waitDstStageMask = Boxed.Vector.fromList $ map snd wait | ||||||
|  |           , Vulkan.commandBuffers   = Boxed.Vector.singleton ( Vulkan.commandBufferHandle commandBuffer ) | ||||||
|  |           , Vulkan.signalSemaphores = Boxed.Vector.fromList signal | ||||||
|  |           } | ||||||
|  |  | ||||||
|  | beginCommandBuffer :: MonadIO m => Vulkan.CommandBuffer -> m () | ||||||
|  | beginCommandBuffer commandBuffer = Vulkan.beginCommandBuffer commandBuffer commandBufferBeginInfo | ||||||
|  |   where | ||||||
|  |     commandBufferBeginInfo :: Vulkan.CommandBufferBeginInfo '[] | ||||||
|  |     commandBufferBeginInfo = | ||||||
|  |       Vulkan.CommandBufferBeginInfo | ||||||
|  |         { Vulkan.next            = () | ||||||
|  |         , Vulkan.flags           = Vulkan.zero | ||||||
|  |         , Vulkan.inheritanceInfo = Nothing | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | endCommandBuffer :: MonadIO m => Vulkan.CommandBuffer -> m () | ||||||
|  | endCommandBuffer = Vulkan.endCommandBuffer | ||||||
|  |  | ||||||
|  | createFence :: MonadVulkan m => Vulkan.Device -> m ( ResourceT.ReleaseKey, Vulkan.Fence ) | ||||||
|  | createFence device = Vulkan.withFence device fenceCreateInfo Nothing ResourceT.allocate | ||||||
|  |   where | ||||||
|  |     fenceCreateInfo :: Vulkan.FenceCreateInfo '[] | ||||||
|  |     fenceCreateInfo = | ||||||
|  |       Vulkan.FenceCreateInfo | ||||||
|  |         { Vulkan.next  = () | ||||||
|  |         , Vulkan.flags = Vulkan.zero | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | data Wait a = WaitAll [a] | WaitAny [a] | ||||||
|  |  | ||||||
|  | waitForFences :: MonadIO m => Vulkan.Device -> Wait Vulkan.Fence -> m () | ||||||
|  | waitForFences device fences = void $ Vulkan.waitForFences device ( Boxed.Vector.fromList fenceList ) waitAll maxBound | ||||||
|  |   where | ||||||
|  |     waitAll   :: Bool | ||||||
|  |     fenceList :: [Vulkan.Fence] | ||||||
|  |     (waitAll, fenceList) = | ||||||
|  |       case fences of | ||||||
|  |         WaitAll l -> ( True , l ) | ||||||
|  |         WaitAny l -> ( False, l ) | ||||||
|  |  | ||||||
|  | createPipelineLayout | ||||||
|  |   :: MonadVulkan m | ||||||
|  |   => Vulkan.Device | ||||||
|  |   -> [ Vulkan.DescriptorSetLayout ] | ||||||
|  |   -> [ Vulkan.PushConstantRange ] | ||||||
|  |   -> m ( ResourceT.ReleaseKey, Vulkan.PipelineLayout ) | ||||||
|  | createPipelineLayout device layouts ranges = | ||||||
|  |   Vulkan.withPipelineLayout device pipelineLayoutCreateInfo Nothing ResourceT.allocate | ||||||
|  |     where | ||||||
|  |       pipelineLayoutCreateInfo :: Vulkan.PipelineLayoutCreateInfo | ||||||
|  |       pipelineLayoutCreateInfo = | ||||||
|  |         Vulkan.PipelineLayoutCreateInfo | ||||||
|  |           { Vulkan.flags              = Vulkan.zero | ||||||
|  |           , Vulkan.setLayouts         = Boxed.Vector.fromList layouts | ||||||
|  |           , Vulkan.pushConstantRanges = Boxed.Vector.fromList ranges | ||||||
|  |           } | ||||||
|  |  | ||||||
|  | present | ||||||
|  |   :: MonadIO m | ||||||
|  |   => Vulkan.Queue | ||||||
|  |   -> Vulkan.SwapchainKHR | ||||||
|  |   -> Word32 | ||||||
|  |   -> [Vulkan.Semaphore] | ||||||
|  |   -> m Vulkan.Result | ||||||
|  | present queue swapchain imageIndex wait = Vulkan.queuePresentKHR queue presentInfo | ||||||
|  |   where | ||||||
|  |     presentInfo :: Vulkan.PresentInfoKHR '[] | ||||||
|  |     presentInfo = | ||||||
|  |       Vulkan.PresentInfoKHR | ||||||
|  |         { Vulkan.next           = () | ||||||
|  |         , Vulkan.waitSemaphores = Boxed.Vector.fromList wait | ||||||
|  |         , Vulkan.swapchains     = Boxed.Vector.singleton swapchain | ||||||
|  |         , Vulkan.imageIndices   = Boxed.Vector.singleton imageIndex | ||||||
|  |         , Vulkan.results        = Vulkan.zero | ||||||
|  |         } | ||||||
							
								
								
									
										61
									
								
								examples/vulkan/Input.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								examples/vulkan/Input.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | module Input where | ||||||
|  |  | ||||||
|  | -- base | ||||||
|  | import Data.Int | ||||||
|  |   ( Int32 ) | ||||||
|  |  | ||||||
|  | -- sdl2 | ||||||
|  | import qualified SDL | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | data Input = Input | ||||||
|  |   { keysDown    :: [ SDL.Scancode ] | ||||||
|  |   , keysPressed :: [ SDL.Scancode ] | ||||||
|  |   , mousePos    :: ( Int32, Int32 ) | ||||||
|  |   , mouseRel    :: ( Int32, Int32 ) | ||||||
|  |   , quitAction  :: Bool | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | nullInput :: Input | ||||||
|  | nullInput | ||||||
|  |   = Input | ||||||
|  |     { keysDown    = [] | ||||||
|  |     , keysPressed = [] | ||||||
|  |     , mousePos    = ( 0, 0 ) | ||||||
|  |     , mouseRel    = ( 0, 0 ) | ||||||
|  |     , quitAction  = False | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | onSDLInput :: Input -> SDL.EventPayload -> Input | ||||||
|  | onSDLInput input SDL.QuitEvent | ||||||
|  |   = input { quitAction = True } | ||||||
|  | onSDLInput input (SDL.WindowClosedEvent _) | ||||||
|  |   = input { quitAction = True } | ||||||
|  | onSDLInput input ( SDL.KeyboardEvent ev ) | ||||||
|  |   = let keyCode = SDL.keysymScancode ( SDL.keyboardEventKeysym ev ) | ||||||
|  |     in case SDL.keyboardEventKeyMotion ev of | ||||||
|  |          SDL.Pressed  -> input { keysDown    = keyCode : filter ( /= keyCode ) ( keysDown    input ) | ||||||
|  |                                , keysPressed = keyCode : filter ( /= keyCode ) ( keysPressed input ) | ||||||
|  |                                } | ||||||
|  |          SDL.Released -> input { keysDown    =           filter ( /= keyCode ) ( keysDown    input ) } | ||||||
|  | onSDLInput input ( SDL.MouseMotionEvent ev ) | ||||||
|  |   = input { mousePos = (px, py) | ||||||
|  |           , mouseRel = (rx, ry) | ||||||
|  |           } | ||||||
|  |     where | ||||||
|  |       SDL.P ( SDL.V2 px py ) = SDL.mouseMotionEventPos       ev | ||||||
|  |       SDL.V2         rx ry   = SDL.mouseMotionEventRelMotion ev | ||||||
|  | onSDLInput input _ = input | ||||||
|  |  | ||||||
|  | onSDLInputs :: Input -> [ SDL.EventPayload ] -> Input | ||||||
|  | onSDLInputs prevInput events = escapeQuits $ foldl onSDLInput zeroedInput events | ||||||
|  |   where | ||||||
|  |     zeroedInput :: Input | ||||||
|  |     zeroedInput = prevInput { keysPressed = [], mouseRel = ( 0, 0 ) } | ||||||
|  |     escapeQuits :: Input -> Input | ||||||
|  |     escapeQuits input | ||||||
|  |       | SDL.ScancodeEscape `elem` keysPressed input | ||||||
|  |       = input { quitAction = True } | ||||||
|  |       | otherwise | ||||||
|  |       = input | ||||||
							
								
								
									
										425
									
								
								examples/vulkan/Main.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								examples/vulkan/Main.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,425 @@ | |||||||
|  | {-# LANGUAGE BlockArguments #-} | ||||||
|  | {-# LANGUAGE DataKinds #-} | ||||||
|  | {-# LANGUAGE DerivingVia #-} | ||||||
|  | {-# LANGUAGE DuplicateRecordFields #-} | ||||||
|  | {-# LANGUAGE FlexibleInstances #-} | ||||||
|  | {-# LANGUAGE LambdaCase #-} | ||||||
|  | {-# LANGUAGE MonoLocalBinds #-} | ||||||
|  | {-# LANGUAGE NamedFieldPuns #-} | ||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | {-# LANGUAGE RecordWildCards #-} | ||||||
|  | {-# LANGUAGE ScopedTypeVariables #-} | ||||||
|  | {-# LANGUAGE StandaloneDeriving #-} | ||||||
|  | {-# LANGUAGE TupleSections #-} | ||||||
|  | {-# LANGUAGE TypeApplications #-} | ||||||
|  |  | ||||||
|  | {-# OPTIONS_GHC -fno-warn-name-shadowing #-} | ||||||
|  | {-# OPTIONS_GHC -fno-warn-orphans        #-} | ||||||
|  |  | ||||||
|  | module Main where | ||||||
|  |  | ||||||
|  | -- base | ||||||
|  | import Control.Arrow | ||||||
|  |   ( second ) | ||||||
|  | import Control.Exception | ||||||
|  |   ( throw ) | ||||||
|  | import Control.Monad | ||||||
|  |   ( unless, void ) | ||||||
|  | import Data.Foldable | ||||||
|  |   ( traverse_ ) | ||||||
|  | import Data.String | ||||||
|  |   ( IsString ) | ||||||
|  | import Data.Traversable | ||||||
|  |   ( for ) | ||||||
|  | import Data.Word | ||||||
|  |   ( Word32 ) | ||||||
|  |  | ||||||
|  | -- logging-effect | ||||||
|  | import Control.Monad.Log | ||||||
|  |   ( LoggingT(..), logDebug, runLoggingT ) | ||||||
|  |  | ||||||
|  | -- resource-t | ||||||
|  | import Control.Monad.Trans.Resource | ||||||
|  |   ( ResourceT, MonadResource, runResourceT ) | ||||||
|  | import qualified Control.Monad.Trans.Resource as ResourceT | ||||||
|  |   ( allocate, release ) | ||||||
|  |  | ||||||
|  | -- sdl | ||||||
|  | import qualified SDL | ||||||
|  |  | ||||||
|  | -- transformers | ||||||
|  | import Control.Monad.Trans.Reader | ||||||
|  |   ( ReaderT(..) ) | ||||||
|  | import Control.Monad.IO.Class | ||||||
|  |   ( MonadIO(..) ) | ||||||
|  |  | ||||||
|  | -- unliftio | ||||||
|  | import UnliftIO.Exception | ||||||
|  |   ( handleJust ) | ||||||
|  |  | ||||||
|  | -- vector | ||||||
|  | import qualified Data.Vector as Boxed | ||||||
|  |   ( Vector ) | ||||||
|  | import qualified Data.Vector as Boxed.Vector | ||||||
|  |   ( (!), head, singleton, unzip ) | ||||||
|  |  | ||||||
|  | -- vulkan | ||||||
|  | import qualified Vulkan | ||||||
|  | import qualified Vulkan.Exception as Vulkan | ||||||
|  | import qualified Vulkan.Zero      as Vulkan | ||||||
|  |  | ||||||
|  | -- dear-imgui | ||||||
|  | import Attachments | ||||||
|  | import Backend | ||||||
|  | import Input | ||||||
|  | import qualified DearImGui            as ImGui | ||||||
|  | import qualified DearImGui.Vulkan     as ImGui.Vulkan | ||||||
|  | import qualified DearImGui.SDL        as ImGui.SDL | ||||||
|  | import qualified DearImGui.SDL.Vulkan as ImGui.SDL.Vulkan | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Handler    = LogMessage -> ResourceT IO () | ||||||
|  | deriving via ( ReaderT Handler (ResourceT IO) ) | ||||||
|  |   instance MonadResource ( LoggingT LogMessage (ResourceT IO) ) | ||||||
|  |  | ||||||
|  | main :: IO () | ||||||
|  | main = runResourceT . ( `runLoggingT` logHandler ) $ app @( LoggingT LogMessage ( ResourceT IO ) ) | ||||||
|  |  | ||||||
|  | appName :: IsString a => a | ||||||
|  | appName = "DearImGui - Vulkan" | ||||||
|  |  | ||||||
|  | app :: forall m. MonadVulkan m => m () | ||||||
|  | app = do | ||||||
|  |  | ||||||
|  |   ------------------------------------------- | ||||||
|  |   -- Initialise window, Vulkan and Dear ImGui contexts. | ||||||
|  |  | ||||||
|  |   ( window, windowExtensions ) <- | ||||||
|  |     initialiseWindow | ||||||
|  |       WindowInfo | ||||||
|  |         { width      = 1280 | ||||||
|  |         , height     = 720 | ||||||
|  |         , windowName = appName | ||||||
|  |         , mouseMode  = SDL.AbsoluteLocation | ||||||
|  |         } | ||||||
|  |   let | ||||||
|  |     vulkanReqs :: VulkanRequirements | ||||||
|  |     vulkanReqs = | ||||||
|  |       VulkanRequirements | ||||||
|  |         { instanceRequirements = instanceExtensions windowExtensions | ||||||
|  |         , deviceRequirements   = [] | ||||||
|  |         , queueFlags           = Vulkan.QUEUE_GRAPHICS_BIT | ||||||
|  |         } | ||||||
|  |   VulkanContext {..} <- initialiseVulkanContext NormalInstance appName vulkanReqs | ||||||
|  |  | ||||||
|  |   surface <- logDebug "Creating SDL surface" *> createSurface window instance' | ||||||
|  |   assertSurfacePresentable physicalDevice queueFamily surface | ||||||
|  |  | ||||||
|  |   void $ ResourceT.allocate | ||||||
|  |     ImGui.createContext | ||||||
|  |     ImGui.destroyContext | ||||||
|  |  | ||||||
|  |   let | ||||||
|  |     preferredFormat :: Vulkan.SurfaceFormatKHR | ||||||
|  |     preferredFormat = | ||||||
|  |       Vulkan.SurfaceFormatKHR | ||||||
|  |         Vulkan.FORMAT_B8G8R8A8_UNORM | ||||||
|  |         Vulkan.COLOR_SPACE_SRGB_NONLINEAR_KHR | ||||||
|  |     surfaceUsage :: Vulkan.ImageUsageFlagBits | ||||||
|  |     surfaceUsage = Vulkan.IMAGE_USAGE_COLOR_ATTACHMENT_BIT | ||||||
|  |  | ||||||
|  |   commandPool  <- createCommandPool device Vulkan.COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT queueFamily | ||||||
|  |   nextImageSem <- snd <$> Vulkan.withSemaphore device Vulkan.zero Nothing ResourceT.allocate | ||||||
|  |   submitted    <- snd <$> Vulkan.withSemaphore device Vulkan.zero Nothing ResourceT.allocate | ||||||
|  |  | ||||||
|  |   let | ||||||
|  |     imGuiDescriptorTypes :: [ ( Vulkan.DescriptorType, Int ) ] | ||||||
|  |     imGuiDescriptorTypes = map (, 1000) | ||||||
|  |       [ Vulkan.DESCRIPTOR_TYPE_SAMPLER | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_SAMPLED_IMAGE | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_STORAGE_IMAGE | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_UNIFORM_BUFFER | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_STORAGE_BUFFER | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC | ||||||
|  |       , Vulkan.DESCRIPTOR_TYPE_INPUT_ATTACHMENT | ||||||
|  |       ] | ||||||
|  |  | ||||||
|  |   ( _imGuiPoolKey, imGuiDescriptorPool ) <- createDescriptorPool device 1000 imGuiDescriptorTypes | ||||||
|  |  | ||||||
|  |   --------------------------------------------------------------------------- | ||||||
|  |   -- Handle swapchain creation (and resources that depend on the swapchain). | ||||||
|  |  | ||||||
|  |   surfaceCapabilities <- Vulkan.getPhysicalDeviceSurfaceCapabilitiesKHR physicalDevice ( Vulkan.SurfaceKHR surface ) | ||||||
|  |  | ||||||
|  |   let | ||||||
|  |     minImageCount, maxImageCount, imageCount :: Word32 | ||||||
|  |     minImageCount = ( Vulkan.minImageCount :: Vulkan.SurfaceCapabilitiesKHR -> Word32 ) surfaceCapabilities | ||||||
|  |     maxImageCount = ( Vulkan.maxImageCount :: Vulkan.SurfaceCapabilitiesKHR -> Word32 ) surfaceCapabilities | ||||||
|  |     imageCount | ||||||
|  |       | maxImageCount == 0 =   minImageCount + 1 | ||||||
|  |       | otherwise          = ( minImageCount + 1 ) `min` maxImageCount | ||||||
|  |  | ||||||
|  |     clearValues :: [ Vulkan.ClearValue ] | ||||||
|  |     clearValues = [ Vulkan.Color $ Vulkan.Float32 0.5 0.2 0 1.0 ] | ||||||
|  |  | ||||||
|  |     swapchainResources :: Maybe SwapchainResources -> m ( m (), SwapchainResources ) | ||||||
|  |     swapchainResources mbOldResources = do | ||||||
|  |       ( surfaceFormat, imGuiRenderPass ) <- case mbOldResources of | ||||||
|  |         Nothing -> do | ||||||
|  |           logDebug "Choosing swapchain format & color space" | ||||||
|  |           surfaceFormat <- chooseSwapchainFormat preferredFormat physicalDevice surface | ||||||
|  |           let | ||||||
|  |             colFmt :: Vulkan.Format | ||||||
|  |             colFmt = ( Vulkan.format :: Vulkan.SurfaceFormatKHR -> Vulkan.Format ) surfaceFormat | ||||||
|  |           logDebug "Creating Dear ImGui render pass" | ||||||
|  |           ( _, imGuiRenderPass ) <- | ||||||
|  |             simpleRenderPass device | ||||||
|  |               ( noAttachments | ||||||
|  |                 { colorAttachments = Boxed.Vector.singleton $ presentableColorAttachmentDescription colFmt } | ||||||
|  |               ) | ||||||
|  |           pure ( surfaceFormat, imGuiRenderPass ) | ||||||
|  |         Just oldResources -> pure ( surfaceFormat oldResources, imGuiRenderPass oldResources ) | ||||||
|  |  | ||||||
|  |       let | ||||||
|  |         colFmt :: Vulkan.Format | ||||||
|  |         colFmt = ( Vulkan.format :: Vulkan.SurfaceFormatKHR -> Vulkan.Format ) surfaceFormat | ||||||
|  |  | ||||||
|  |       logDebug "Creating swapchain" | ||||||
|  |       ( swapchainKey, swapchain, swapchainExtent ) <- | ||||||
|  |         createSwapchain | ||||||
|  |           physicalDevice device | ||||||
|  |           surface surfaceFormat | ||||||
|  |           surfaceUsage | ||||||
|  |           imageCount | ||||||
|  |           ( swapchain <$> mbOldResources ) | ||||||
|  |  | ||||||
|  |       logDebug "Getting swapchain images" | ||||||
|  |       swapchainImages <- snd <$> Vulkan.getSwapchainImagesKHR device swapchain | ||||||
|  |  | ||||||
|  |       ------------------------------------------- | ||||||
|  |       -- Create framebuffer attachments. | ||||||
|  |  | ||||||
|  | {- | ||||||
|  |       let | ||||||
|  |         width, height :: Num a => a | ||||||
|  |         width  = fromIntegral $ ( Vulkan.width  :: Vulkan.Extent2D -> Word32 ) swapchainExtent | ||||||
|  |         height = fromIntegral $ ( Vulkan.height :: Vulkan.Extent2D -> Word32 ) swapchainExtent | ||||||
|  |  | ||||||
|  |         extent3D :: Vulkan.Extent3D | ||||||
|  |         extent3D | ||||||
|  |           = Vulkan.Extent3D | ||||||
|  |               { Vulkan.width  = width | ||||||
|  |               , Vulkan.height = height | ||||||
|  |               , Vulkan.depth  = 1 | ||||||
|  |               } | ||||||
|  | -} | ||||||
|  |  | ||||||
|  |       logDebug "Creating framebuffers" | ||||||
|  |       ( fbKeys, framebuffersWithAttachments ) <- | ||||||
|  |           fmap Boxed.Vector.unzip . for swapchainImages $ \ swapchainImage -> do | ||||||
|  |               ( imageViewKey, colorImageView ) | ||||||
|  |                 <- createImageView | ||||||
|  |                       device swapchainImage | ||||||
|  |                       Vulkan.IMAGE_VIEW_TYPE_2D | ||||||
|  |                       colFmt | ||||||
|  |                       Vulkan.IMAGE_ASPECT_COLOR_BIT | ||||||
|  |               let attachment = (swapchainImage, colorImageView) | ||||||
|  |               ( framebufferKey, framebuffer ) <- createFramebuffer device imGuiRenderPass swapchainExtent [colorImageView] | ||||||
|  |               pure ( [ imageViewKey, framebufferKey ], ( framebuffer, attachment ) ) | ||||||
|  |  | ||||||
|  |       ------------------------------------------- | ||||||
|  |       -- Create descriptor sets. | ||||||
|  |  | ||||||
|  |       -- Application doesn't have any descriptor sets of its own yet. | ||||||
|  |  | ||||||
|  |       ------------------------------------------- | ||||||
|  |       -- Create pipelines. | ||||||
|  |  | ||||||
|  |       -- Application doesn't have any pipelines of its own yet. | ||||||
|  |  | ||||||
|  |       ------------------------------------------- | ||||||
|  |       -- Return the resources and free method. | ||||||
|  |  | ||||||
|  |       pure | ||||||
|  |         ( do | ||||||
|  |             traverse_ ( traverse_ ResourceT.release ) fbKeys | ||||||
|  |             traverse_ ResourceT.release | ||||||
|  |               [ swapchainKey ] | ||||||
|  |         , SwapchainResources {..} | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |   ( freeResources, resources@( SwapchainResources {..} ) ) <- swapchainResources Nothing | ||||||
|  |   let | ||||||
|  |     imageCount :: Word32 | ||||||
|  |     imageCount = fromIntegral $ length swapchainImages | ||||||
|  |  | ||||||
|  |   logDebug "Allocating command buffers" | ||||||
|  |   commandBuffers <- snd <$> allocatePrimaryCommandBuffers device commandPool imageCount | ||||||
|  |  | ||||||
|  |   ------------------------------------------- | ||||||
|  |   -- Initialise Dear ImGui. | ||||||
|  |  | ||||||
|  |   let | ||||||
|  |     initInfo :: ImGui.Vulkan.InitInfo | ||||||
|  |     initInfo = ImGui.Vulkan.InitInfo | ||||||
|  |       { instance' | ||||||
|  |       , physicalDevice | ||||||
|  |       , device | ||||||
|  |       , queueFamily | ||||||
|  |       , queue | ||||||
|  |       , pipelineCache  = Vulkan.NULL_HANDLE | ||||||
|  |       , descriptorPool = imGuiDescriptorPool | ||||||
|  |       , subpass        = 0 | ||||||
|  |       , minImageCount | ||||||
|  |       , imageCount | ||||||
|  |       , msaaSamples    = Vulkan.SAMPLE_COUNT_1_BIT | ||||||
|  |       , mbAllocator    = Nothing | ||||||
|  |       , checkResult    = \case { Vulkan.SUCCESS -> pure (); e -> throw $ Vulkan.VulkanException e } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |   logDebug "Initialising ImGui SDL2 for Vulkan" | ||||||
|  |   void $ ResourceT.allocate | ||||||
|  |     ( ImGui.SDL.Vulkan.sdl2InitForVulkan window ) | ||||||
|  |     ( const ImGui.SDL.sdl2Shutdown ) | ||||||
|  |  | ||||||
|  |   logDebug "Initialising ImGui for Vulkan" | ||||||
|  |   ImGui.Vulkan.withVulkan initInfo imGuiRenderPass \ _ -> do | ||||||
|  |  | ||||||
|  |     logDebug "Running one-shot commands to upload ImGui textures" | ||||||
|  |     logDebug "Creating fence" | ||||||
|  |     ( fenceKey, fence ) <- createFence device | ||||||
|  |     logDebug "Allocating one-shot command buffer" | ||||||
|  |     ( fontUploadCommandBufferKey, fontUploadCommandBuffer ) <- | ||||||
|  |       second Boxed.Vector.head <$> | ||||||
|  |         allocatePrimaryCommandBuffers device commandPool 1 | ||||||
|  |  | ||||||
|  |     logDebug "Recording one-shot commands" | ||||||
|  |     beginCommandBuffer fontUploadCommandBuffer | ||||||
|  |     _ <- ImGui.Vulkan.vulkanCreateFontsTexture fontUploadCommandBuffer | ||||||
|  |     endCommandBuffer fontUploadCommandBuffer | ||||||
|  |  | ||||||
|  |     logDebug "Submitting one-shot commands" | ||||||
|  |     submitCommandBuffer queue fontUploadCommandBuffer [] [] ( Just fence ) | ||||||
|  |     waitForFences device ( WaitAll [ fence ] ) | ||||||
|  |  | ||||||
|  |     logDebug "Finished uploading font objects" | ||||||
|  |     logDebug "Cleaning up one-shot commands" | ||||||
|  |     ImGui.Vulkan.vulkanDestroyFontUploadObjects | ||||||
|  |     traverse_ ResourceT.release [ fenceKey, fontUploadCommandBufferKey ] | ||||||
|  |  | ||||||
|  |     let | ||||||
|  |       mainLoop :: AppState m -> m () | ||||||
|  |       mainLoop ( AppState {..} ) = do | ||||||
|  |  | ||||||
|  |         ( freeResources, resources@( SwapchainResources {..} ), freeOldResources ) <- | ||||||
|  |           if reloadSwapchain | ||||||
|  |           then do | ||||||
|  |             logDebug "Reloading swapchain and associated resources" | ||||||
|  |             ( freeNewResources, newResources ) <- swapchainResources ( Just resources ) | ||||||
|  |             pure ( freeNewResources, newResources, freeOldResources *> freeResources ) | ||||||
|  |           else pure ( freeResources, resources, freeOldResources ) | ||||||
|  |  | ||||||
|  |         inputEvents <- map SDL.eventPayload <$> pollEventsWithImGui | ||||||
|  |         inputState  <- pure $ onSDLInputs inputState inputEvents | ||||||
|  |  | ||||||
|  |         unless ( quitAction inputState ) do | ||||||
|  |           ( acquireResult, nextImageIndex ) <- | ||||||
|  |             handleJust vulkanException ( \ e -> pure ( e, 0 ) ) | ||||||
|  |               ( Vulkan.acquireNextImageKHR device swapchain maxBound nextImageSem Vulkan.NULL_HANDLE ) | ||||||
|  |           let | ||||||
|  |             reloadSwapchain, quit :: Bool | ||||||
|  |             ( reloadSwapchain, quit ) = reloadQuit acquireResult | ||||||
|  |           unless quit do | ||||||
|  |             ( reloadSwapchain, quit ) <- | ||||||
|  |               if reloadSwapchain | ||||||
|  |               then do | ||||||
|  |                 pure ( True, False ) | ||||||
|  |               else | ||||||
|  |                 handleJust vulkanException ( pure . reloadQuit ) do | ||||||
|  |                   ImGui.Vulkan.vulkanNewFrame | ||||||
|  |                   ImGui.SDL.sdl2NewFrame window | ||||||
|  |                   ImGui.newFrame | ||||||
|  |                   ImGui.showDemoWindow | ||||||
|  |                   ImGui.render | ||||||
|  |                   drawData <- ImGui.getDrawData | ||||||
|  |                   let | ||||||
|  |                     commandBuffer :: Vulkan.CommandBuffer | ||||||
|  |                     commandBuffer = commandBuffers Boxed.Vector.! fromIntegral nextImageIndex | ||||||
|  |                     framebuffer :: Vulkan.Framebuffer | ||||||
|  |                     framebuffer = fst $ framebuffersWithAttachments Boxed.Vector.! fromIntegral nextImageIndex | ||||||
|  |                   Vulkan.resetCommandBuffer commandBuffer Vulkan.zero | ||||||
|  |                   beginCommandBuffer commandBuffer | ||||||
|  |                   cmdBeginRenderPass commandBuffer imGuiRenderPass framebuffer clearValues swapchainExtent | ||||||
|  |                   ImGui.Vulkan.vulkanRenderDrawData drawData commandBuffer Nothing | ||||||
|  |                   cmdEndRenderPass commandBuffer | ||||||
|  |                   endCommandBuffer commandBuffer | ||||||
|  |                   submitCommandBuffer | ||||||
|  |                     queue | ||||||
|  |                     commandBuffer | ||||||
|  |                     [ ( nextImageSem, Vulkan.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ) ] | ||||||
|  |                     [ submitted ] | ||||||
|  |                     Nothing | ||||||
|  |                   presentResult <- present queue swapchain nextImageIndex [submitted] | ||||||
|  |                   Vulkan.queueWaitIdle queue | ||||||
|  |                   pure ( reloadQuit presentResult ) | ||||||
|  |             freeOldResources | ||||||
|  |             let | ||||||
|  |               freeOldResources :: m () | ||||||
|  |               freeOldResources = pure ()     | ||||||
|  |             unless quit $ mainLoop ( AppState {..} ) | ||||||
|  |  | ||||||
|  |     let | ||||||
|  |       reloadSwapchain :: Bool | ||||||
|  |       reloadSwapchain = False | ||||||
|  |       freeOldResources :: m () | ||||||
|  |       freeOldResources = pure () | ||||||
|  |       inputState :: Input | ||||||
|  |       inputState = nullInput | ||||||
|  |  | ||||||
|  |     logDebug "Starting main loop." | ||||||
|  |     mainLoop ( AppState {..} ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | data SwapchainResources = SwapchainResources | ||||||
|  |   { swapchain       :: !Vulkan.SwapchainKHR | ||||||
|  |   , swapchainExtent :: !Vulkan.Extent2D | ||||||
|  |   , swapchainImages :: !( Boxed.Vector Vulkan.Image ) | ||||||
|  |   , surfaceFormat   :: !Vulkan.SurfaceFormatKHR | ||||||
|  |   , imGuiRenderPass :: !Vulkan.RenderPass | ||||||
|  |   , framebuffersWithAttachments :: !( Boxed.Vector ( Vulkan.Framebuffer, ( Vulkan.Image, Vulkan.ImageView ) ) ) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | data AppState m | ||||||
|  |   = AppState | ||||||
|  |     { reloadSwapchain  :: !Bool | ||||||
|  |     , freeResources    :: !( m () ) | ||||||
|  |     , resources        :: !SwapchainResources | ||||||
|  |     , freeOldResources :: !( m () ) | ||||||
|  |     , inputState       :: !Input | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | pollEventsWithImGui :: MonadIO m => m [ SDL.Event ] | ||||||
|  | pollEventsWithImGui = do | ||||||
|  |   e <- ImGui.SDL.pollEventWithImGui | ||||||
|  |   case e of | ||||||
|  |     Nothing -> pure [] | ||||||
|  |     Just e' -> ( e' : ) <$> pollEventsWithImGui | ||||||
|  |  | ||||||
|  | vulkanException :: Vulkan.VulkanException -> Maybe Vulkan.Result | ||||||
|  | vulkanException ( Vulkan.VulkanException e ) | ||||||
|  |   | e >= Vulkan.SUCCESS | ||||||
|  |   = Nothing | ||||||
|  |   | otherwise | ||||||
|  |   = Just e | ||||||
|  |  | ||||||
|  | reloadQuit :: Vulkan.Result -> ( Bool, Bool ) | ||||||
|  | reloadQuit = \ case | ||||||
|  |   Vulkan.ERROR_OUT_OF_DATE_KHR -> ( True , False ) | ||||||
|  |   Vulkan.SUBOPTIMAL_KHR        -> ( True , False ) | ||||||
|  |   e | e >= Vulkan.SUCCESS      -> ( False, False ) | ||||||
|  |   _                            -> ( False, True  ) | ||||||
							
								
								
									
										40
									
								
								examples/vulkan/Util.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								examples/vulkan/Util.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | {-# LANGUAGE BlockArguments      #-} | ||||||
|  | {-# LANGUAGE ScopedTypeVariables #-} | ||||||
|  |  | ||||||
|  | module Util where | ||||||
|  |  | ||||||
|  | -- base | ||||||
|  | import Data.Coerce | ||||||
|  |   ( coerce ) | ||||||
|  | import Data.Functor.Compose | ||||||
|  |   ( Compose(..) ) | ||||||
|  | import Data.Functor.Identity | ||||||
|  |   ( Identity(..) ) | ||||||
|  | import Data.Traversable | ||||||
|  |   ( for ) | ||||||
|  |  | ||||||
|  | -- transformers | ||||||
|  | import Control.Monad.Trans.State.Strict | ||||||
|  |   ( StateT(..), State, evalState ) | ||||||
|  | import Control.Monad.Trans.Writer.Strict | ||||||
|  |   ( runWriter, tell ) | ||||||
|  |  | ||||||
|  | --------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | iunzipWith | ||||||
|  |   :: (Traversable t, Num i, Enum i) | ||||||
|  |   => (i -> a -> b) -> (i -> a -> c) -> t a -> ( t b, [c] ) | ||||||
|  | iunzipWith f g ta | ||||||
|  |   = runWriter | ||||||
|  |   $ ifor 0 succ ta \ i a -> do | ||||||
|  |        tell [g i a] | ||||||
|  |        pure ( f i a ) | ||||||
|  |  | ||||||
|  | ifor | ||||||
|  |   :: forall t f i a b | ||||||
|  |   .  ( Applicative f, Traversable t ) | ||||||
|  |   => i -> ( i -> i ) -> t a -> ( i -> a -> f b ) -> f (t b) | ||||||
|  | ifor i0 upd ta f = (`evalState` i0) . getCompose $ result | ||||||
|  |   where | ||||||
|  |     result :: Compose (State i) f (t b) | ||||||
|  |     result = for ta \ a -> ( coerce ( \ i -> ( f i a, upd i ) ) ) | ||||||
							
								
								
									
										45
									
								
								src/DearImGui/SDL/Vulkan.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/DearImGui/SDL/Vulkan.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | {-# LANGUAGE BlockArguments #-} | ||||||
|  | {-# LANGUAGE FlexibleContexts #-} | ||||||
|  | {-# LANGUAGE NamedFieldPuns #-} | ||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | {-# LANGUAGE PatternSynonyms #-} | ||||||
|  | {-# LANGUAGE QuasiQuotes #-} | ||||||
|  | {-# LANGUAGE TemplateHaskell #-} | ||||||
|  |  | ||||||
|  | {-| | ||||||
|  | Module: DearImGui.SDL.Vulkan | ||||||
|  |  | ||||||
|  | Initialising the Vulkan backend for Dear ImGui using SDL2. | ||||||
|  | -} | ||||||
|  |  | ||||||
|  | module DearImGui.SDL.Vulkan | ||||||
|  |   ( sdl2InitForVulkan ) | ||||||
|  |   where | ||||||
|  |  | ||||||
|  | -- inline-c | ||||||
|  | import qualified Language.C.Inline as C | ||||||
|  |  | ||||||
|  | -- inline-c-cpp | ||||||
|  | import qualified Language.C.Inline.Cpp as Cpp | ||||||
|  |  | ||||||
|  | -- sdl2 | ||||||
|  | import SDL.Internal.Types | ||||||
|  |   ( Window(..) ) | ||||||
|  |  | ||||||
|  | -- transformers | ||||||
|  | import Control.Monad.IO.Class ( MonadIO, liftIO ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | C.context Cpp.cppCtx | ||||||
|  | C.include "imgui.h" | ||||||
|  | C.include "backends/imgui_impl_vulkan.h" | ||||||
|  | C.include "backends/imgui_impl_sdl.h" | ||||||
|  | C.include "SDL.h" | ||||||
|  | C.include "SDL_vulkan.h" | ||||||
|  | Cpp.using "namespace ImGui" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | -- | Wraps @ImGui_ImplSDL2_InitForVulkan@. | ||||||
|  | sdl2InitForVulkan :: MonadIO m => Window -> m Bool | ||||||
|  | sdl2InitForVulkan (Window windowPtr) = liftIO do | ||||||
|  |   ( 0 /= ) <$> [C.exp| bool { ImGui_ImplSDL2_InitForVulkan((SDL_Window*)$(void* windowPtr)) } |] | ||||||
							
								
								
									
										172
									
								
								src/DearImGui/Vulkan.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								src/DearImGui/Vulkan.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | |||||||
|  | {-# LANGUAGE BlockArguments #-} | ||||||
|  | {-# LANGUAGE QuasiQuotes #-} | ||||||
|  | {-# LANGUAGE RecordWildCards #-} | ||||||
|  | {-# LANGUAGE TemplateHaskell #-} | ||||||
|  |  | ||||||
|  | {-| | ||||||
|  | Module: DearImGui.Vulkan | ||||||
|  |  | ||||||
|  | Vulkan backend for Dear ImGui. | ||||||
|  | -} | ||||||
|  |  | ||||||
|  | module DearImGui.Vulkan | ||||||
|  |   ( InitInfo(..) | ||||||
|  |   , withVulkan | ||||||
|  |   , vulkanNewFrame | ||||||
|  |   , vulkanRenderDrawData | ||||||
|  |   , vulkanCreateFontsTexture | ||||||
|  |   , vulkanDestroyFontUploadObjects | ||||||
|  |   , vulkanSetMinImageCount | ||||||
|  |   ) | ||||||
|  |   where | ||||||
|  |  | ||||||
|  | -- base | ||||||
|  | import Data.Maybe | ||||||
|  |   ( fromMaybe ) | ||||||
|  | import Data.Word | ||||||
|  |   ( Word32 ) | ||||||
|  | import Foreign.Marshal.Alloc | ||||||
|  |   ( alloca ) | ||||||
|  | import Foreign.Ptr | ||||||
|  |   ( Ptr, freeHaskellFunPtr, nullPtr ) | ||||||
|  | import Foreign.Storable | ||||||
|  |   ( Storable(poke) ) | ||||||
|  |  | ||||||
|  | -- inline-c | ||||||
|  | import qualified Language.C.Inline as C | ||||||
|  |  | ||||||
|  | -- inline-c-cpp | ||||||
|  | import qualified Language.C.Inline.Cpp as Cpp | ||||||
|  |  | ||||||
|  | -- transformers | ||||||
|  | import Control.Monad.IO.Class | ||||||
|  |   ( MonadIO, liftIO ) | ||||||
|  |  | ||||||
|  | -- unliftio | ||||||
|  | import UnliftIO | ||||||
|  |   ( MonadUnliftIO ) | ||||||
|  | import UnliftIO.Exception | ||||||
|  |   ( bracket ) | ||||||
|  |  | ||||||
|  | -- vulkan | ||||||
|  | import qualified Vulkan | ||||||
|  |  | ||||||
|  | -- DearImGui | ||||||
|  | import DearImGui | ||||||
|  |   ( DrawData(..) ) | ||||||
|  | import DearImGui.Vulkan.Types | ||||||
|  |   ( vulkanCtx ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | C.context ( Cpp.cppCtx <> C.funCtx <> vulkanCtx ) | ||||||
|  | C.include "imgui.h" | ||||||
|  | C.include "backends/imgui_impl_vulkan.h" | ||||||
|  | Cpp.using "namespace ImGui" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | data InitInfo = | ||||||
|  |   InitInfo | ||||||
|  |   { instance'      :: !Vulkan.Instance | ||||||
|  |   , physicalDevice :: !Vulkan.PhysicalDevice | ||||||
|  |   , device         :: !Vulkan.Device | ||||||
|  |   , queueFamily    :: !Word32 | ||||||
|  |   , queue          :: !Vulkan.Queue | ||||||
|  |   , pipelineCache  :: !Vulkan.PipelineCache | ||||||
|  |   , descriptorPool :: !Vulkan.DescriptorPool | ||||||
|  |   , subpass        :: !Word32 | ||||||
|  |   , minImageCount  :: !Word32 | ||||||
|  |   , imageCount     :: !Word32 | ||||||
|  |   , msaaSamples    :: !Vulkan.SampleCountFlagBits | ||||||
|  |   , mbAllocator    :: Maybe Vulkan.AllocationCallbacks | ||||||
|  |   , checkResult    :: Vulkan.Result -> IO () | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | -- | Wraps @ImGui_ImplVulkan_Init@ and @ImGui_ImplVulkan_Shutdown@. | ||||||
|  | withVulkan :: MonadUnliftIO m => InitInfo -> Vulkan.RenderPass -> ( Bool -> m a ) -> m a | ||||||
|  | withVulkan ( InitInfo {..} ) renderPass action = do | ||||||
|  |   let | ||||||
|  |     instancePtr :: Ptr Vulkan.Instance_T | ||||||
|  |     instancePtr = Vulkan.instanceHandle instance' | ||||||
|  |     physicalDevicePtr :: Ptr Vulkan.PhysicalDevice_T | ||||||
|  |     physicalDevicePtr = Vulkan.physicalDeviceHandle physicalDevice | ||||||
|  |     devicePtr :: Ptr Vulkan.Device_T | ||||||
|  |     devicePtr = Vulkan.deviceHandle device | ||||||
|  |     queuePtr :: Ptr Vulkan.Queue_T | ||||||
|  |     queuePtr = Vulkan.queueHandle queue | ||||||
|  |     withCallbacks :: ( Ptr Vulkan.AllocationCallbacks -> IO a ) -> IO a | ||||||
|  |     withCallbacks f = case mbAllocator of | ||||||
|  |       Nothing        -> f nullPtr | ||||||
|  |       Just callbacks -> alloca ( \ ptr -> poke ptr callbacks *> f ptr ) | ||||||
|  |   bracket | ||||||
|  |     ( liftIO do | ||||||
|  |         checkResultFunPtr <- $( C.mkFunPtr [t| Vulkan.Result -> IO () |] ) checkResult | ||||||
|  |         initResult <- withCallbacks \ callbacksPtr -> | ||||||
|  |             [C.block| bool { | ||||||
|  |               ImGui_ImplVulkan_InitInfo initInfo; | ||||||
|  |               VkInstance instance = { $( VkInstance_T* instancePtr ) }; | ||||||
|  |               initInfo.Instance = instance; | ||||||
|  |               VkPhysicalDevice physicalDevice = { $( VkPhysicalDevice_T* physicalDevicePtr ) }; | ||||||
|  |               initInfo.PhysicalDevice = physicalDevice; | ||||||
|  |               VkDevice device = { $( VkDevice_T* devicePtr ) }; | ||||||
|  |               initInfo.Device = device; | ||||||
|  |               initInfo.QueueFamily = $(uint32_t queueFamily); | ||||||
|  |               VkQueue queue = { $( VkQueue_T* queuePtr ) }; | ||||||
|  |               initInfo.Queue = queue; | ||||||
|  |               initInfo.PipelineCache = $(VkPipelineCache pipelineCache); | ||||||
|  |               initInfo.DescriptorPool = $(VkDescriptorPool descriptorPool); | ||||||
|  |               initInfo.Subpass = $(uint32_t subpass); | ||||||
|  |               initInfo.MinImageCount = $(uint32_t minImageCount); | ||||||
|  |               initInfo.ImageCount = $(uint32_t imageCount); | ||||||
|  |               initInfo.MSAASamples = $(VkSampleCountFlagBits msaaSamples); | ||||||
|  |               initInfo.Allocator = $(VkAllocationCallbacks* callbacksPtr); | ||||||
|  |               initInfo.CheckVkResultFn = $( void (*checkResultFunPtr)(VkResult) ); | ||||||
|  |               return ImGui_ImplVulkan_Init(&initInfo, $(VkRenderPass renderPass) ); | ||||||
|  |             }|] | ||||||
|  |         pure ( checkResultFunPtr, initResult /= 0 ) | ||||||
|  |     ) | ||||||
|  |     ( \ ( checkResultFunPtr, _ ) -> liftIO do | ||||||
|  |       [C.exp| void { ImGui_ImplVulkan_Shutdown(); } |] | ||||||
|  |       freeHaskellFunPtr checkResultFunPtr | ||||||
|  |     ) | ||||||
|  |     ( \ ( _, initResult ) -> action initResult ) | ||||||
|  |  | ||||||
|  | -- | Wraps @ImGui_ImplVulkan_NewFrame@. | ||||||
|  | vulkanNewFrame :: MonadIO m => m () | ||||||
|  | vulkanNewFrame = liftIO do | ||||||
|  |   [C.exp| void { ImGui_ImplVulkan_NewFrame(); } |] | ||||||
|  |  | ||||||
|  | -- | Wraps @ImGui_ImplVulkan_RenderDrawData@. | ||||||
|  | vulkanRenderDrawData :: MonadIO m => DrawData -> Vulkan.CommandBuffer -> Maybe Vulkan.Pipeline -> m () | ||||||
|  | vulkanRenderDrawData (DrawData dataPtr) commandBuffer mbPipeline = liftIO do | ||||||
|  |   let | ||||||
|  |     commandBufferPtr :: Ptr Vulkan.CommandBuffer_T | ||||||
|  |     commandBufferPtr = Vulkan.commandBufferHandle commandBuffer | ||||||
|  |     pipeline :: Vulkan.Pipeline | ||||||
|  |     pipeline = fromMaybe Vulkan.NULL_HANDLE mbPipeline | ||||||
|  |   [C.block| void { | ||||||
|  |     VkCommandBuffer commandBuffer = { $( VkCommandBuffer_T* commandBufferPtr ) }; | ||||||
|  |     ImGui_ImplVulkan_RenderDrawData((ImDrawData*) $(void* dataPtr), commandBuffer, $(VkPipeline pipeline)); | ||||||
|  |   }|] | ||||||
|  |  | ||||||
|  | -- | Wraps @ImGui_ImplVulkan_CreateFontsTexture@. | ||||||
|  | vulkanCreateFontsTexture :: MonadIO m => Vulkan.CommandBuffer -> m Bool | ||||||
|  | vulkanCreateFontsTexture commandBuffer = liftIO do | ||||||
|  |   let | ||||||
|  |     commandBufferPtr :: Ptr Vulkan.CommandBuffer_T | ||||||
|  |     commandBufferPtr = Vulkan.commandBufferHandle commandBuffer | ||||||
|  |   res <- | ||||||
|  |     [C.block| bool { | ||||||
|  |       VkCommandBuffer commandBuffer = { $( VkCommandBuffer_T* commandBufferPtr ) }; | ||||||
|  |       return ImGui_ImplVulkan_CreateFontsTexture(commandBuffer); | ||||||
|  |     }|] | ||||||
|  |   pure ( res /= 0 ) | ||||||
|  |  | ||||||
|  | -- | Wraps @ImGui_ImplVulkan_DestroyFontUploadObjects@. | ||||||
|  | vulkanDestroyFontUploadObjects :: MonadIO m => m () | ||||||
|  | vulkanDestroyFontUploadObjects = liftIO do | ||||||
|  |   [C.exp| void { ImGui_ImplVulkan_DestroyFontUploadObjects(); } |] | ||||||
|  |  | ||||||
|  | -- | Wraps @ImGui_ImplVulkan_SetMinImageCount@. | ||||||
|  | vulkanSetMinImageCount :: MonadIO m => Word32 -> m () | ||||||
|  | vulkanSetMinImageCount minImageCount = liftIO do | ||||||
|  |   [C.exp| void { ImGui_ImplVulkan_SetMinImageCount($(uint32_t minImageCount)); } |] | ||||||
							
								
								
									
										37
									
								
								src/DearImGui/Vulkan/Types.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/DearImGui/Vulkan/Types.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | {-# LANGUAGE QuasiQuotes #-} | ||||||
|  | {-# LANGUAGE TemplateHaskell #-} | ||||||
|  |  | ||||||
|  | module DearImGui.Vulkan.Types | ||||||
|  |   ( vulkanCtx ) | ||||||
|  |   where | ||||||
|  |  | ||||||
|  | -- containers | ||||||
|  | import qualified Data.Map.Strict as Map | ||||||
|  |   ( fromList ) | ||||||
|  |  | ||||||
|  | -- inline-c | ||||||
|  | import qualified Language.C.Inline.Context as C | ||||||
|  | import qualified Language.C.Types          as C | ||||||
|  |  | ||||||
|  | -- vulkan | ||||||
|  | import qualified Vulkan | ||||||
|  |  | ||||||
|  | vulkanTypesTable :: C.TypesTable | ||||||
|  | vulkanTypesTable = Map.fromList | ||||||
|  |   [ ( C.TypeName "VkAllocationCallbacks", [t| Vulkan.AllocationCallbacks |] ) | ||||||
|  |   , ( C.TypeName "VkCommandBuffer_T"    , [t| Vulkan.CommandBuffer_T     |] ) | ||||||
|  |   , ( C.TypeName "VkDescriptorPool"     , [t| Vulkan.DescriptorPool      |] ) | ||||||
|  |   , ( C.TypeName "VkDevice_T"           , [t| Vulkan.Device_T            |] ) | ||||||
|  |   , ( C.TypeName "VkInstance_T"         , [t| Vulkan.Instance_T          |] ) | ||||||
|  |   , ( C.TypeName "VkPhysicalDevice_T"   , [t| Vulkan.PhysicalDevice_T    |] ) | ||||||
|  |   , ( C.TypeName "VkPipeline"           , [t| Vulkan.Pipeline            |] ) | ||||||
|  |   , ( C.TypeName "VkPipelineCache"      , [t| Vulkan.PipelineCache       |] ) | ||||||
|  |   , ( C.TypeName "VkQueue_T"            , [t| Vulkan.Queue_T             |] ) | ||||||
|  |   , ( C.TypeName "VkRenderPass"         , [t| Vulkan.RenderPass          |] ) | ||||||
|  |   , ( C.TypeName "VkResult"             , [t| Vulkan.Result              |] ) | ||||||
|  |   , ( C.TypeName "VkSampleCountFlagBits", [t| Vulkan.SampleCountFlagBits |] ) | ||||||
|  |   ] | ||||||
|  |  | ||||||
|  | vulkanCtx :: C.Context | ||||||
|  | vulkanCtx = mempty { C.ctxTypesTable = vulkanTypesTable } | ||||||
		Reference in New Issue
	
	Block a user