diff options
| author | andromeda <andromeda@lenovo> | 2026-08-20 23:30:29 +0200 |
|---|---|---|
| committer | andromeda <andromeda@lenovo> | 2026-08-21 03:36:29 +0200 |
| commit | a91ac16c11f4a2fe7fef6abfe5d29436280d2252 (patch) | |
| tree | f88f574184f064a9d2cced0279d879b2e553ba7d /VkTest | |
| parent | eee13566610e714f3e00982c6793a867c87be4f5 (diff) | |
it draws!
Diffstat (limited to 'VkTest')
| -rw-r--r-- | VkTest/Config.hs | 248 | ||||
| -rw-r--r-- | VkTest/Shaders.hs | 43 | ||||
| -rw-r--r-- | VkTest/Util.hs | 162 |
3 files changed, 453 insertions, 0 deletions
diff --git a/VkTest/Config.hs b/VkTest/Config.hs new file mode 100644 index 0000000..8f18aa2 --- /dev/null +++ b/VkTest/Config.hs @@ -0,0 +1,248 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE NondecreasingIndentation #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} + +module VkTest.Config where + +import Data.Bits ((.|.)) +import Data.ByteString (ByteString) +import Data.Int (Int32) +import Data.Vector (Vector) +import Data.Word (Word32) +import FIR (compileTo, runCompilationsTH, ModuleRequirements) +import Vulkan.CStruct.Extends (SomeStruct(..)) +import Vulkan.Zero (zero) + +import qualified Data.ByteString.Char8 as BSC +import qualified Data.Vector as V +import qualified VkTest.Shaders as Shaders +import qualified Vulkan.Core10 as Vk +import qualified Vulkan.Core13 as Vk +import qualified Vulkan.Extensions.VK_KHR_surface as Vk +import qualified Vulkan.Extensions.VK_KHR_swapchain as Vk + +height :: Int32 +height = 400 +width :: Int32 +width = 800 + +layers :: Vector ByteString +layers = V.fromList $ map BSC.pack ["VK_LAYER_KHRONOS_validation"] + +extensions :: Vector ByteString +extensions = V.fromList $ map BSC.pack ["VK_KHR_swapchain"] + +vert :: Either a ModuleRequirements +vert = $( runCompilationsTH [("Fragment Shader", compileTo Shaders.vertPath [] Shaders.vertex)] ) + +frag :: Either a ModuleRequirements +frag = $( runCompilationsTH [("Fragment Shader", compileTo Shaders.fragPath [] Shaders.fragment)] ) + +data Consts = Consts + { title :: String + , vkApiVersion :: Word32 + , framesInFlight :: Word32 + } + deriving Show + +consts :: Consts +consts = + Consts + { title = "game title" + , vkApiVersion = Vk.API_VERSION_1_3 + , framesInFlight = 2 + } + +-- for a given physical device and surface +data QueriedData = QueriedData + { surfaceCapabilities :: Vk.SurfaceCapabilitiesKHR + , surfaceSupport :: [Int] + , surfaceFormats :: Vector Vk.SurfaceFormatKHR + , queueFamilyProperties :: Vector Vk.QueueFamilyProperties + , extensionProperties :: Vector Vk.ExtensionProperties + , physicalDeviceProperties :: Vk.PhysicalDeviceProperties + } + deriving Show + +instance' :: Vector ByteString -> Vk.InstanceCreateInfo '[] +instance' exts = + zero { Vk.applicationInfo = Just (zero :: Vk.ApplicationInfo) { Vk.apiVersion = consts.vkApiVersion } + , Vk.enabledExtensionNames = exts + } + +device :: Word32 -> Vk.DeviceCreateInfo '[Vk.PhysicalDeviceVulkan13Features] +device gqueueIndex = + zero { Vk.next = (vkFeatures13, ()) + , Vk.queueCreateInfos = V.singleton $ SomeStruct zero { Vk.queueFamilyIndex = gqueueIndex + , Vk.queuePriorities = V.singleton 1 + } + , Vk.enabledExtensionNames = extensions + } + +vkFeatures13 :: Vk.PhysicalDeviceVulkan13Features +vkFeatures13 = + zero { Vk.synchronization2 = True + , Vk.dynamicRendering = True + } + +commandPool :: Word32 -> Vk.CommandPoolCreateInfo '[] +commandPool gqueueIndex = + zero { Vk.queueFamilyIndex = gqueueIndex + , Vk.flags = Vk.COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT + } + +commandBuffer :: Vk.CommandPool -> Vk.CommandBufferAllocateInfo +commandBuffer gpool = + zero { Vk.commandPool = gpool + , Vk.level = Vk.COMMAND_BUFFER_LEVEL_PRIMARY + , Vk.commandBufferCount = consts.framesInFlight + } + +swapchain :: QueriedData -> Vk.SurfaceKHR -> Vk.SwapchainCreateInfoKHR '[] +swapchain q surface = + zero { Vk.compositeAlpha = Vk.COMPOSITE_ALPHA_OPAQUE_BIT_KHR + , Vk.imageArrayLayers = 1 + , Vk.imageColorSpace = Vk.COLORSPACE_SRGB_NONLINEAR_KHR + , Vk.imageExtent = q.surfaceCapabilities.currentExtent + , Vk.imageFormat = (V.head q.surfaceFormats).format + , Vk.imageUsage = Vk.IMAGE_USAGE_COLOR_ATTACHMENT_BIT + , Vk.minImageCount = q.surfaceCapabilities.minImageCount + , Vk.presentMode = Vk.PRESENT_MODE_FIFO_KHR + , Vk.preTransform = Vk.SURFACE_TRANSFORM_IDENTITY_BIT_KHR + , Vk.surface = surface + } + +imageViews :: Vector Vk.Image -> QueriedData -> Vector (Vk.ImageViewCreateInfo '[]) +imageViews images q = + V.map (\image -> (zero :: Vk.ImageViewCreateInfo '[]) { Vk.image = image + , Vk.viewType = Vk.IMAGE_VIEW_TYPE_2D + , Vk.subresourceRange = zero { Vk.aspectMask = Vk.IMAGE_ASPECT_COLOR_BIT + , Vk.levelCount = Vk.REMAINING_MIP_LEVELS + , Vk.layerCount = Vk.REMAINING_ARRAY_LAYERS + } + , Vk.format = (V.head q.surfaceFormats).format + }) images + +pipelineLayout :: Vk.PipelineLayoutCreateInfo +pipelineLayout = + zero + +graphicsPipeline :: QueriedData -> Vk.ShaderModule -> Vk.ShaderModule -> Vk.PipelineLayout -> Vk.GraphicsPipelineCreateInfo '[Vk.PipelineRenderingCreateInfo] +graphicsPipeline q vertMod fragMod pipelineLayout' = + zero { Vk.next = (pipelineRendering q, ()) + , Vk.stages = V.map (SomeStruct) $ V.fromList [ zero { Vk.stage = Vk.SHADER_STAGE_VERTEX_BIT + , Vk.module' = vertMod + , Vk.name = "main" + } + , zero { Vk.stage = Vk.SHADER_STAGE_FRAGMENT_BIT + , Vk.module' = fragMod + , Vk.name = "main" + }] + , Vk.vertexInputState = Just zero + , Vk.inputAssemblyState = Just zero { Vk.topology = Vk.PRIMITIVE_TOPOLOGY_TRIANGLE_LIST } + , Vk.tessellationState = Nothing + , Vk.viewportState = Just $ SomeStruct zero { Vk.viewports = V.fromList [zero { Vk.x = 0 + , Vk.y = 0 + , Vk.width = fromIntegral q.surfaceCapabilities.currentExtent.width + , Vk.height = fromIntegral q.surfaceCapabilities.currentExtent.height + , Vk.minDepth = 0 + , Vk.maxDepth = 1 + }] + , Vk.scissors = V.fromList [(zero :: Vk.Rect2D) { Vk.offset = zero + , Vk.extent = q.surfaceCapabilities.currentExtent + }] + } + + , Vk.rasterizationState = Just $ SomeStruct zero { Vk.lineWidth = 1 } + , Vk.multisampleState = Just $ SomeStruct zero { Vk.rasterizationSamples = Vk.SAMPLE_COUNT_1_BIT } + , Vk.depthStencilState = Just zero + , Vk.colorBlendState = Just $ SomeStruct zero { Vk.attachments = V.singleton zero { Vk.colorWriteMask = Vk.COLOR_COMPONENT_R_BIT .|. Vk.COLOR_COMPONENT_G_BIT .|. Vk.COLOR_COMPONENT_B_BIT .|. Vk.COLOR_COMPONENT_A_BIT }} + , Vk.dynamicState = Just zero { Vk.dynamicStates = V.fromList [Vk.DYNAMIC_STATE_VIEWPORT, Vk.DYNAMIC_STATE_SCISSOR] } + , Vk.layout = pipelineLayout' + } + +pipelineRendering :: QueriedData -> Vk.PipelineRenderingCreateInfo +pipelineRendering q = + zero { Vk.colorAttachmentFormats = V.singleton (V.head q.surfaceFormats).format } + +preRenderPipelineBarrier :: Vk.Image -> Vk.DependencyInfo '[] +preRenderPipelineBarrier image = + zero { Vk.imageMemoryBarriers = V.singleton $ SomeStruct zero { Vk.srcStageMask = Vk.PIPELINE_STAGE_2_NONE + , Vk.srcAccessMask = Vk.ACCESS_2_NONE + , Vk.dstStageMask = Vk.PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT + , Vk.dstAccessMask = Vk.ACCESS_2_COLOR_ATTACHMENT_READ_BIT .|. Vk.ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT + , Vk.oldLayout = Vk.IMAGE_LAYOUT_UNDEFINED + , Vk.newLayout = Vk.IMAGE_LAYOUT_ATTACHMENT_OPTIMAL + , Vk.image = image + , Vk.subresourceRange = zero { Vk.aspectMask = Vk.IMAGE_ASPECT_COLOR_BIT + , Vk.levelCount = 1 + , Vk.layerCount = 1 + } + } + } + +postRenderPipelineBarrier :: Vk.Image -> Vk.DependencyInfo '[] +postRenderPipelineBarrier image = + zero { Vk.imageMemoryBarriers = V.singleton $ SomeStruct zero { Vk.srcStageMask = Vk.PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT + , Vk.srcAccessMask = Vk.ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT + , Vk.dstStageMask = Vk.PIPELINE_STAGE_2_NONE + , Vk.dstAccessMask = Vk.ACCESS_2_NONE + , Vk.oldLayout = Vk.IMAGE_LAYOUT_ATTACHMENT_OPTIMAL + , Vk.newLayout = Vk.IMAGE_LAYOUT_PRESENT_SRC_KHR + , Vk.image = image + , Vk.subresourceRange = zero { Vk.aspectMask = Vk.IMAGE_ASPECT_COLOR_BIT + , Vk.levelCount = 1 + , Vk.layerCount = 1 + } + } + } + +rendering :: QueriedData -> Vk.ImageView -> Vk.RenderingInfo '[] +rendering q imageView = + zero { Vk.renderArea = zero { Vk.extent = q.surfaceCapabilities.currentExtent } + , Vk.layerCount = 1 + , Vk.colorAttachments = V.singleton $ (SomeStruct) zero { Vk.imageView = imageView + , Vk.imageLayout = Vk.IMAGE_LAYOUT_ATTACHMENT_OPTIMAL + , Vk.loadOp = Vk.ATTACHMENT_LOAD_OP_CLEAR + , Vk.storeOp = Vk.ATTACHMENT_STORE_OP_STORE + , Vk.clearValue = Vk.Color $ Vk.Float32 0 0 1 1 + } + } + +viewport :: QueriedData -> Vector Vk.Viewport +viewport q = + V.singleton zero { Vk.x = 0 + , Vk.y = 0 + , Vk.width = fromIntegral q.surfaceCapabilities.currentExtent.width + , Vk.height = fromIntegral q.surfaceCapabilities.currentExtent.height + , Vk.minDepth = 0 + , Vk.maxDepth = 1 + } + +scissor :: QueriedData -> Vector Vk.Rect2D +scissor q = + V.singleton (zero :: Vk.Rect2D) { Vk.offset = zero + , Vk.extent = q.surfaceCapabilities.currentExtent + } + +submitQueue :: Vk.CommandBuffer -> Vk.Semaphore -> Vk.Semaphore -> Vector (SomeStruct Vk.SubmitInfo2) +submitQueue cbuffer sImageAcquired sRenderFinished = + V.singleton $ SomeStruct zero { Vk.waitSemaphoreInfos = V.singleton $ zero { Vk.semaphore = sImageAcquired + , Vk.stageMask = Vk.PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT + } + , Vk.commandBufferInfos = V.singleton $ SomeStruct zero { Vk.commandBuffer = Vk.commandBufferHandle cbuffer } + , Vk.signalSemaphoreInfos = V.singleton $ zero { Vk.semaphore = sRenderFinished + , Vk.stageMask = Vk.PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT + } + } + +present :: Vk.SwapchainKHR -> Word32 -> Vk.Semaphore -> Vk.PresentInfoKHR '[] +present swapchain' imageIndex sRenderFinished = + zero { Vk.waitSemaphores = V.singleton sRenderFinished + , Vk.swapchains = V.singleton swapchain' + , Vk.imageIndices = V.singleton imageIndex + }
\ No newline at end of file diff --git a/VkTest/Shaders.hs b/VkTest/Shaders.hs new file mode 100644 index 0000000..0d88325 --- /dev/null +++ b/VkTest/Shaders.hs @@ -0,0 +1,43 @@ +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE RebindableSyntax #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} + +module VkTest.Shaders where + +import FIR +import Math.Linear + +vertices = Vec3 (Vec2 0 (-0.5)) + (Vec2 0.5 0.5) + (Vec2 (-0.5) 0.5) + +vertPath = "assets/shaders/vert.spv" +fragPath = "assets/shaders/frag.spv" + +type VertexDefs = + '[ "main" ':-> EntryPoint '[] Vertex ] + +vertex :: ShaderModule "main" VertexShader VertexDefs _ +vertex = shader do + i <- get @"gl_VertexIndex" + (Vec2 x y) <- let' $ atv3v2f vertices i (Vec2 0 0) + put @"gl_Position" (Vec4 x y 0 1) + +type FragmentDefs = + '[ "main" ':-> EntryPoint '[ OriginUpperLeft ] Fragment + , "out_color" ':-> Output '[ Location 0 ] (V 4 Float) + ] + +fragment :: ShaderModule "main" FragmentShader FragmentDefs _ +fragment = shader do + put @"out_color" (Vec4 1.0 0.0 1.0 1.0) + +atv3v2f :: Code (V 3 (V 2 Float)) -> Code Word32 -> Code (V 2 Float) -> Code (V 2 Float) +atv3v2f (Vec3 x y z) i d = + if i == 0 then x + else if i == 1 then y + else if i == 2 then z + else d
\ No newline at end of file diff --git a/VkTest/Util.hs b/VkTest/Util.hs new file mode 100644 index 0000000..d92c2dd --- /dev/null +++ b/VkTest/Util.hs @@ -0,0 +1,162 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE NondecreasingIndentation #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} + +module VkTest.Util ( withRGFW + , withWindow + , withShaderModules + , withImageViews + , withSemaphores + , withFences + , pickPhysicalDevice + , processExtensions + , getGraphicsQueues + ) where + +import Data.Bits ((.&.)) +import Data.ByteString (ByteString) +import Data.Coerce (coerce) +import Data.Int (Int32) +import Data.List ((\\)) +import Data.Vector (Vector) +import Foreign.C +import Foreign.C.ConstPtr (ConstPtr(..)) +import Foreign.Marshal.Array (advancePtr) +import Foreign.Ptr (Ptr) +import Foreign.Storable (peek) +import Vulkan.Zero (zero) + +import VkTest.Config (QueriedData(..)) + +import qualified VkTest.Config as Config + +import qualified Data.ByteString as BS +import qualified Data.Vector as V +import qualified RGFW as RGFW +import qualified Vulkan.Core10 as Vk +import qualified Vulkan.Extensions.VK_KHR_surface as Vk + +pickPhysicalDevice :: Vector Vk.PhysicalDevice -> Vk.SurfaceKHR -> IO (Vk.PhysicalDevice, QueriedData) +pickPhysicalDevice pdevs surface = do + queriedDevs <- do + qs <- V.mapM (\pdev -> (queryPhysicalDevice pdev surface)) pdevs + return $ V.zip pdevs qs + let validpdevs = V.filter isValidPhysicalDevice queriedDevs + pickPhysicalDevice' validpdevs V.empty + +pickPhysicalDevice' :: Vector (Vk.PhysicalDevice, QueriedData) -> Vector (Vk.PhysicalDevice, QueriedData) -> IO (Vk.PhysicalDevice, QueriedData) +pickPhysicalDevice' pdevs opts = + if V.null pdevs + then return $ (V.head opts) + else do + let pdev = V.head pdevs + case (snd pdev).physicalDeviceProperties.deviceType of + Vk.PHYSICAL_DEVICE_TYPE_DISCRETE_GPU -> pickPhysicalDevice' (V.tail pdevs) (V.cons pdev opts) + Vk.PHYSICAL_DEVICE_TYPE_CPU -> pickPhysicalDevice' (V.tail pdevs) opts + _ -> pickPhysicalDevice' (V.tail pdevs) (V.snoc opts pdev) + +-- returns list of queueFamilyIndex whick support a graphics pipeline +getGraphicsQueues :: QueriedData -> [Int] +getGraphicsQueues q = getGraphicsQueues' q.queueFamilyProperties 0 [] +getGraphicsQueues' :: Vector Vk.QueueFamilyProperties -> Int -> [Int] -> [Int] +getGraphicsQueues' qfprops i is = + if V.null qfprops then is else + if zero /= (Vk.QUEUE_GRAPHICS_BIT .&. (V.head qfprops).queueFlags) + then getGraphicsQueues' (V.tail qfprops) (i + 1) (i:is) + else getGraphicsQueues' (V.tail qfprops) (i + 1) is + +-- returns list of queueFamilyIndex which support presentation +getSurfaceSupport :: Vk.PhysicalDevice -> Vk.SurfaceKHR -> IO [Int] +getSurfaceSupport pdev surface = do + qfprops <- Vk.getPhysicalDeviceQueueFamilyProperties pdev + getSurfaceSupport' pdev surface (V.length qfprops - 1) [] + +getSurfaceSupport' :: Vk.PhysicalDevice -> Vk.SurfaceKHR -> Int -> [Int] -> IO [Int] +getSurfaceSupport' pdev surface i is = if i < 0 then return is else do + support <- Vk.getPhysicalDeviceSurfaceSupportKHR pdev (fromIntegral i) surface + if support + then getSurfaceSupport' pdev surface (i - 1) (i:is) + else getSurfaceSupport' pdev surface (i - 1) is + +queryPhysicalDevice :: Vk.PhysicalDevice -> Vk.SurfaceKHR -> IO QueriedData +queryPhysicalDevice pdev surface = do + qfprops <- Vk.getPhysicalDeviceQueueFamilyProperties pdev + sSupport <- getSurfaceSupport pdev surface + (_, extps) <- Vk.enumerateDeviceExtensionProperties pdev Nothing + caps <- Vk.getPhysicalDeviceSurfaceCapabilitiesKHR pdev surface + (_, forms) <- Vk.getPhysicalDeviceSurfaceFormatsKHR pdev surface + props <- Vk.getPhysicalDeviceProperties pdev + return QueriedData { surfaceCapabilities = caps + , surfaceSupport = sSupport + , surfaceFormats = forms + , queueFamilyProperties = qfprops + , extensionProperties = extps + , physicalDeviceProperties = props + } + +-- checks that a device has the requisite capabilities +isValidPhysicalDevice :: (Vk.PhysicalDevice, QueriedData) -> Bool +isValidPhysicalDevice (_, q) = + if null $ getGraphicsQueues q + then False + else if null q.surfaceSupport + then False + else if [] /= ((V.toList Config.extensions) \\ (V.toList $ V.map (\p -> p.extensionName) q.extensionProperties)) + then False + else True + +-- type conversion +processExtensions :: CSize -> Ptr (ConstPtr CChar) -> Vector ByteString -> IO (Vector ByteString) +processExtensions 0 _ extNames = return extNames +processExtensions count strs extNames = do + str <- peek strs + extName <- BS.packCString (coerce str) + processExtensions (count - 1) (advancePtr strs 1) $ V.snoc extNames extName + +withFences :: Vk.Device -> Vector (Vk.FenceCreateInfo '[]) -> Maybe Vk.AllocationCallbacks -> (Vector Vk.Fence -> IO r) -> IO r +withFences dev infos alloc io = do + fs <- mapM (\info -> Vk.createFence dev info alloc) infos + o0 <- io fs + _ <- mapM (\f -> Vk.destroyFence dev f alloc) fs + return o0 + +withSemaphores :: Vk.Device -> Vector (Vk.SemaphoreCreateInfo '[]) -> Maybe Vk.AllocationCallbacks -> (Vector Vk.Semaphore -> IO r) -> IO r +withSemaphores dev infos alloc io = do + ss <- mapM (\info -> Vk.createSemaphore dev info alloc) infos + o0 <- io ss + _ <- mapM (\s -> Vk.destroySemaphore dev s alloc) ss + return o0 + +withImageViews :: Vk.Device -> Vector (Vk.ImageViewCreateInfo '[]) -> Maybe Vk.AllocationCallbacks -> (Vector Vk.ImageView -> IO r) -> IO r +withImageViews dev infos alloc io = do + imageViews <- mapM (\info -> Vk.createImageView dev info alloc) infos + o0 <- io imageViews + _ <- mapM (\imageView -> Vk.destroyImageView dev imageView alloc) imageViews + return o0 + +withShaderModules :: Vk.Device -> Vector (Vk.ShaderModuleCreateInfo '[]) -> Maybe Vk.AllocationCallbacks -> (Vector Vk.ShaderModule -> IO r) -> IO r +withShaderModules dev infos alloc io = do + mods <- mapM (\info -> Vk.createShaderModule dev info alloc) infos + o0 <- io mods + _ <- mapM (\mod' -> Vk.destroyShaderModule dev mod' alloc) mods + return o0 + +withWindow :: String -> Int32 -> Int32 -> Int32 -> Int32 -> RGFW.RGFW_windowFlags -> (Ptr RGFW.RGFW_window -> IO r) -> IO r +withWindow name x y w h flags io = + withCString name $ \str -> do + window <- RGFW.rGFW_createWindow (ConstPtr str) (RGFW.I32 x) (RGFW.I32 y) (RGFW.I32 w) (RGFW.I32 h) flags + o0 <- io window + RGFW.rGFW_window_close window + return o0 + +withRGFW :: String -> RGFW.RGFW_initFlags -> (Int32 -> IO r) -> IO r +withRGFW title flags io = + withCString title $ \str -> do + ret_code <- RGFW.rGFW_init (ConstPtr str) flags + o0 <- io $ fromIntegral ret_code + RGFW.rGFW_deinit + return o0
\ No newline at end of file |
