almost drawing, just fix shaders
This commit is contained in:
parent
bfb3b1a814
commit
83950010ac
7 changed files with 174 additions and 38 deletions
|
@ -19,7 +19,7 @@ createCommandPool
|
||||||
:: (MonadResource m)
|
:: (MonadResource m)
|
||||||
=> Vk.PhysicalDevice
|
=> Vk.PhysicalDevice
|
||||||
-> Vk.Device
|
-> Vk.Device
|
||||||
-> m Vk.CommandPool
|
-> m (Vk.CommandPool, Vk.Queue)
|
||||||
createCommandPool physicalDevice logicalDevice = do
|
createCommandPool physicalDevice logicalDevice = do
|
||||||
|
|
||||||
queueFamilyIndex <- getQueueFamily physicalDevice Vk.QUEUE_GRAPHICS_BIT
|
queueFamilyIndex <- getQueueFamily physicalDevice Vk.QUEUE_GRAPHICS_BIT
|
||||||
|
@ -29,19 +29,23 @@ createCommandPool physicalDevice logicalDevice = do
|
||||||
, Vk.queueFamilyIndex = queueFamilyIndex
|
, Vk.queueFamilyIndex = queueFamilyIndex
|
||||||
} :: Vk.CommandPoolCreateInfo
|
} :: Vk.CommandPoolCreateInfo
|
||||||
|
|
||||||
snd <$> allocate
|
graphicsQueue <- Vk.getDeviceQueue logicalDevice queueFamilyIndex 0
|
||||||
|
|
||||||
|
commandPool <- snd <$> allocate
|
||||||
(Vk.createCommandPool logicalDevice poolCreateInfo Nothing)
|
(Vk.createCommandPool logicalDevice poolCreateInfo Nothing)
|
||||||
(\commandPool -> do
|
(\commandPool -> do
|
||||||
putStrLn "destroying comman pool"
|
putStrLn "destroying comman pool"
|
||||||
Vk.destroyCommandPool logicalDevice commandPool Nothing
|
Vk.destroyCommandPool logicalDevice commandPool Nothing
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return (commandPool, graphicsQueue)
|
||||||
|
|
||||||
createCommandBuffer
|
createCommandBuffer
|
||||||
:: (MonadResource m)
|
:: (MonadResource m)
|
||||||
=> Vk.CommandPool
|
=> Vk.Device
|
||||||
-> Vk.Device
|
-> Vk.CommandPool
|
||||||
-> m (V.Vector Vk.CommandBuffer)
|
-> m (V.Vector Vk.CommandBuffer)
|
||||||
createCommandBuffer commandPool logicalDevice = do
|
createCommandBuffer logicalDevice commandPool = do
|
||||||
|
|
||||||
let commandBufferAllocationInfo = Vk.zero
|
let commandBufferAllocationInfo = Vk.zero
|
||||||
{ Vk.commandPool = commandPool
|
{ Vk.commandPool = commandPool
|
||||||
|
@ -123,6 +127,8 @@ createSyncObjects logicalDevice = do
|
||||||
|
|
||||||
let semaphoreCreateInfo = Vk.zero
|
let semaphoreCreateInfo = Vk.zero
|
||||||
fenceCreateInfo = Vk.zero
|
fenceCreateInfo = Vk.zero
|
||||||
|
{ Vk.flags = Vk.FENCE_CREATE_SIGNALED_BIT
|
||||||
|
} :: Vk.FenceCreateInfo '[]
|
||||||
|
|
||||||
[imageAvailableSemaphore, renderFinishedSemaphore] <- replicateM 2
|
[imageAvailableSemaphore, renderFinishedSemaphore] <- replicateM 2
|
||||||
(snd <$> allocate
|
(snd <$> allocate
|
||||||
|
@ -134,4 +140,4 @@ createSyncObjects logicalDevice = do
|
||||||
)
|
)
|
||||||
inFlightFence <- Vk.createFence logicalDevice fenceCreateInfo Nothing
|
inFlightFence <- Vk.createFence logicalDevice fenceCreateInfo Nothing
|
||||||
|
|
||||||
return (imageAvailableSemaphore, renderFinishedSemaphore,inFlightFence)
|
return (imageAvailableSemaphore, renderFinishedSemaphore, inFlightFence)
|
||||||
|
|
65
src/Draw.hs
Normal file
65
src/Draw.hs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
module Draw where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.Trans.Resource
|
||||||
|
import qualified Data.Vector as V
|
||||||
|
import qualified Vulkan.Core10 as Vk
|
||||||
|
import qualified Vulkan.Extensions.VK_KHR_swapchain as Khr
|
||||||
|
import qualified Vulkan.CStruct.Extends as Vk
|
||||||
|
import qualified Vulkan.Zero as Vk
|
||||||
|
|
||||||
|
|
||||||
|
drawFrame
|
||||||
|
:: (MonadResource m)
|
||||||
|
=> Vk.Device
|
||||||
|
-> Khr.SwapchainKHR
|
||||||
|
-> Vk.Queue
|
||||||
|
-> Vk.CommandBuffer
|
||||||
|
-> Vk.Fence
|
||||||
|
-> Vk.Semaphore
|
||||||
|
-> Vk.Semaphore
|
||||||
|
-> m ()
|
||||||
|
drawFrame
|
||||||
|
logicalDevice
|
||||||
|
swapchain
|
||||||
|
graphicsQueue
|
||||||
|
commandBuffer
|
||||||
|
inFlightFence
|
||||||
|
imageAvailableSemaphore
|
||||||
|
renderFinishedSemaphore
|
||||||
|
= do
|
||||||
|
|
||||||
|
let fences = V.singleton inFlightFence
|
||||||
|
|
||||||
|
void $ Vk.waitForFences logicalDevice fences True maxBound
|
||||||
|
Vk.resetFences logicalDevice fences
|
||||||
|
|
||||||
|
(imageResult, index) <-
|
||||||
|
Khr.acquireNextImageKHR logicalDevice swapchain maxBound imageAvailableSemaphore inFlightFence
|
||||||
|
|
||||||
|
unless (imageResult == Vk.SUCCESS) $
|
||||||
|
error "drawFrame: Failed acquiring next image from swapchain"
|
||||||
|
|
||||||
|
Vk.resetCommandBuffer commandBuffer (Vk.CommandBufferResetFlagBits 0)
|
||||||
|
|
||||||
|
let submitInfo = Vk.zero
|
||||||
|
{ Vk.waitSemaphores = V.singleton imageAvailableSemaphore
|
||||||
|
, Vk.waitDstStageMask = V.singleton Vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
|
||||||
|
, Vk.commandBuffers = V.singleton (Vk.commandBufferHandle commandBuffer)
|
||||||
|
, Vk.signalSemaphores = V.singleton renderFinishedSemaphore
|
||||||
|
}
|
||||||
|
|
||||||
|
Vk.queueSubmit graphicsQueue (V.singleton (Vk.SomeStruct submitInfo)) inFlightFence
|
||||||
|
|
||||||
|
let presentInfo = Vk.zero
|
||||||
|
{ Khr.waitSemaphores = V.singleton renderFinishedSemaphore
|
||||||
|
, Khr.swapchains = V.singleton swapchain
|
||||||
|
, Khr.imageIndices = V.singleton index
|
||||||
|
}
|
||||||
|
|
||||||
|
presentResult <- Khr.queuePresentKHR graphicsQueue presentInfo
|
||||||
|
|
||||||
|
unless (presentResult == Vk.SUCCESS) $
|
||||||
|
error "drawFrame: presentation failed"
|
|
@ -4,26 +4,21 @@
|
||||||
module Framebuffers where
|
module Framebuffers where
|
||||||
|
|
||||||
import Linear
|
import Linear
|
||||||
import Control.Monad (unless)
|
|
||||||
import Control.Monad.IO.Class (liftIO)
|
|
||||||
import Control.Monad.Trans.Resource
|
import Control.Monad.Trans.Resource
|
||||||
import Data.Bits
|
|
||||||
import qualified Data.ByteString as BS
|
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import Foreign.C.Types (CInt)
|
import Foreign.C.Types (CInt)
|
||||||
import qualified Vulkan as Vk
|
import qualified Vulkan as Vk
|
||||||
import qualified Vulkan.Zero as Vk
|
import qualified Vulkan.Zero as Vk
|
||||||
import qualified Vulkan.CStruct.Extends as Vk
|
|
||||||
|
|
||||||
|
|
||||||
createFramebuffer
|
createFramebuffer
|
||||||
:: (MonadResource m)
|
:: (MonadResource m)
|
||||||
=> Vk.RenderPass
|
=> Vk.Device
|
||||||
|
-> Vk.RenderPass
|
||||||
-> V.Vector Vk.ImageView
|
-> V.Vector Vk.ImageView
|
||||||
-> V2 CInt
|
-> V2 CInt
|
||||||
-> Vk.Device
|
|
||||||
-> m Vk.Framebuffer
|
-> m Vk.Framebuffer
|
||||||
createFramebuffer renderPass swapchainImageViews (V2 swapchainWidth swapcheinHeight) logicalDevice
|
createFramebuffer logicalDevice renderPass swapchainImageViews (V2 swapchainWidth swapcheinHeight)
|
||||||
= do
|
= do
|
||||||
|
|
||||||
let framebufferCreateInfo = Vk.zero
|
let framebufferCreateInfo = Vk.zero
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Data.Bits
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import Foreign.C.Types (CInt)
|
import Foreign.C.Types (CInt)
|
||||||
import qualified Vulkan as Vk
|
import qualified Vulkan.Core10 as Vk
|
||||||
import qualified Vulkan.Zero as Vk
|
import qualified Vulkan.Zero as Vk
|
||||||
import qualified Vulkan.CStruct.Extends as Vk
|
import qualified Vulkan.CStruct.Extends as Vk
|
||||||
|
|
||||||
|
@ -36,20 +36,20 @@ loadShader logicalDevice shaderPath = do
|
||||||
|
|
||||||
createGraphicsPipelines
|
createGraphicsPipelines
|
||||||
:: (MonadResource m)
|
:: (MonadResource m)
|
||||||
=> Vk.ShaderModule
|
=> Vk.Device
|
||||||
|
-> Vk.ShaderModule
|
||||||
-> Vk.ShaderModule
|
-> Vk.ShaderModule
|
||||||
-> BS.ByteString
|
-> BS.ByteString
|
||||||
-> V2 CInt
|
-> V2 CInt
|
||||||
-> Vk.Format
|
-> Vk.Format
|
||||||
-> Vk.Device
|
-> m (V.Vector Vk.Pipeline, Vk.RenderPass)
|
||||||
-> m (V.Vector Vk.Pipeline)
|
|
||||||
createGraphicsPipelines
|
createGraphicsPipelines
|
||||||
|
logicalDevice
|
||||||
vertexShaderModule
|
vertexShaderModule
|
||||||
fragmentShaderModule
|
fragmentShaderModule
|
||||||
stageName
|
stageName
|
||||||
(V2 width height)
|
(V2 width height)
|
||||||
swapchainImageFormat
|
swapchainImageFormat
|
||||||
logicalDevice
|
|
||||||
= do
|
= do
|
||||||
|
|
||||||
let vertexShaderStageCreateInfo = Vk.zero
|
let vertexShaderStageCreateInfo = Vk.zero
|
||||||
|
@ -160,10 +160,16 @@ createGraphicsPipelines
|
||||||
{ Vk.pipelineBindPoint = Vk.PIPELINE_BIND_POINT_GRAPHICS
|
{ Vk.pipelineBindPoint = Vk.PIPELINE_BIND_POINT_GRAPHICS
|
||||||
, Vk.colorAttachments = V.singleton colorAttachmentReference
|
, Vk.colorAttachments = V.singleton colorAttachmentReference
|
||||||
}
|
}
|
||||||
|
subpassDependency = Vk.zero
|
||||||
|
{ Vk.srcSubpass = Vk.SUBPASS_EXTERNAL
|
||||||
|
, Vk.dstSubpass = 0
|
||||||
|
, Vk.srcStageMask = Vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
|
||||||
|
, Vk.srcAccessMask = Vk.ACCESS_COLOR_ATTACHMENT_WRITE_BIT
|
||||||
|
}
|
||||||
renderPassCreateInfo = Vk.zero
|
renderPassCreateInfo = Vk.zero
|
||||||
{ Vk.attachments = V.singleton colorAttachmentDescription
|
{ Vk.attachments = V.singleton colorAttachmentDescription
|
||||||
, Vk.subpasses = V.singleton subpassDescriptor
|
, Vk.subpasses = V.singleton subpassDescriptor
|
||||||
, Vk.dependencies = V.empty
|
, Vk.dependencies = V.singleton subpassDependency
|
||||||
} :: Vk.RenderPassCreateInfo '[]
|
} :: Vk.RenderPassCreateInfo '[]
|
||||||
|
|
||||||
pipelineRenderPass <- snd <$> allocate
|
pipelineRenderPass <- snd <$> allocate
|
||||||
|
@ -190,7 +196,7 @@ createGraphicsPipelines
|
||||||
, Vk.basePipelineIndex = -1
|
, Vk.basePipelineIndex = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
snd <$> allocate
|
pipelines <- snd <$> allocate
|
||||||
(do
|
(do
|
||||||
(result, pipelines) <- Vk.createGraphicsPipelines
|
(result, pipelines) <- Vk.createGraphicsPipelines
|
||||||
logicalDevice
|
logicalDevice
|
||||||
|
@ -209,3 +215,5 @@ createGraphicsPipelines
|
||||||
)
|
)
|
||||||
pipelines
|
pipelines
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return (pipelines, pipelineRenderPass)
|
||||||
|
|
74
src/Init.hs
74
src/Init.hs
|
@ -10,16 +10,30 @@ import qualified Data.Vector as V
|
||||||
import qualified SDL hiding (V2)
|
import qualified SDL hiding (V2)
|
||||||
import qualified SDL.Video.Vulkan as SDL
|
import qualified SDL.Video.Vulkan as SDL
|
||||||
import Foreign.Ptr
|
import Foreign.Ptr
|
||||||
import qualified Vulkan as Vk
|
import qualified Vulkan.Core10 as Vk
|
||||||
|
import qualified Vulkan.Extensions.VK_KHR_swapchain as Khr
|
||||||
|
import qualified Vulkan.Extensions.VK_KHR_surface as Khr
|
||||||
|
|
||||||
-- internal imports
|
-- internal imports
|
||||||
|
|
||||||
import Instance
|
import Instance
|
||||||
import Devices
|
import Devices
|
||||||
|
import GraphicsPipeline
|
||||||
|
import Framebuffers
|
||||||
|
import CommandBuffer
|
||||||
|
|
||||||
initEngine
|
initEngine
|
||||||
:: MonadResource m
|
:: (MonadResource m, MonadFail m)
|
||||||
=> m (SDL.Window, Vk.Instance, Vk.SurfaceKHR, Vk.SwapchainKHR, V.Vector Vk.ImageView)
|
=> m
|
||||||
|
( SDL.Window
|
||||||
|
, Vk.Device
|
||||||
|
, Khr.SwapchainKHR
|
||||||
|
, Vk.Queue
|
||||||
|
, V.Vector Vk.CommandBuffer
|
||||||
|
, Vk.Fence
|
||||||
|
, Vk.Semaphore
|
||||||
|
, Vk.Semaphore
|
||||||
|
)
|
||||||
initEngine = do
|
initEngine = do
|
||||||
-- initialize SDL2 with all subsystems
|
-- initialize SDL2 with all subsystems
|
||||||
void $ allocate_
|
void $ allocate_
|
||||||
|
@ -46,32 +60,68 @@ initEngine = do
|
||||||
SDL.destroyWindow window
|
SDL.destroyWindow window
|
||||||
)
|
)
|
||||||
|
|
||||||
-- create vulkan surface and vulkan instance from window
|
-- initialize viúlkan data structures
|
||||||
(surface, inst, swapchain, images) <- initVulkan window
|
initVulkan window
|
||||||
return (window, inst, surface, swapchain, images)
|
|
||||||
|
|
||||||
initVulkan
|
initVulkan
|
||||||
:: MonadResource m
|
:: (MonadResource m, MonadFail m)
|
||||||
=> SDL.Window
|
=> SDL.Window
|
||||||
-> m (Vk.SurfaceKHR, Vk.Instance, Vk.SwapchainKHR, V.Vector Vk.ImageView)
|
-> m
|
||||||
|
( SDL.Window
|
||||||
|
, Vk.Device
|
||||||
|
, Khr.SwapchainKHR
|
||||||
|
, Vk.Queue
|
||||||
|
, V.Vector Vk.CommandBuffer
|
||||||
|
, Vk.Fence
|
||||||
|
, Vk.Semaphore
|
||||||
|
, Vk.Semaphore
|
||||||
|
)
|
||||||
initVulkan window = do
|
initVulkan window = do
|
||||||
|
|
||||||
vulkanInstance <- createInstance window
|
vulkanInstance <- createInstance window
|
||||||
|
|
||||||
(_, vulkanSurface) <- allocate
|
(_, vulkanSurface) <- allocate
|
||||||
(Vk.SurfaceKHR <$>
|
(Khr.SurfaceKHR <$>
|
||||||
SDL.vkCreateSurface window (castPtr (Vk.instanceHandle vulkanInstance))
|
SDL.vkCreateSurface window (castPtr (Vk.instanceHandle vulkanInstance))
|
||||||
)
|
)
|
||||||
(\vulkanSurface -> do
|
(\vulkanSurface -> do
|
||||||
putStrLn "destroying surface"
|
putStrLn "destroying surface"
|
||||||
Vk.destroySurfaceKHR vulkanInstance vulkanSurface Nothing
|
Khr.destroySurfaceKHR vulkanInstance vulkanSurface Nothing
|
||||||
)
|
)
|
||||||
|
|
||||||
vulkanPhysicalDevice <- pickPhysicalDevice vulkanInstance
|
vulkanPhysicalDevice <- pickPhysicalDevice vulkanInstance
|
||||||
(vulkanLogicalDevice, surfaceFormats) <- createLogicalDevice vulkanPhysicalDevice vulkanSurface
|
(vulkanLogicalDevice, surfaceFormats) <- createLogicalDevice vulkanPhysicalDevice vulkanSurface
|
||||||
|
|
||||||
dimensions <- liftIO $ SDL.windowInitialSize <$> SDL.getWindowConfig window
|
dimensions <- liftIO $ SDL.windowInitialSize <$> SDL.getWindowConfig window
|
||||||
(swapchain, surfaceFormat) <- createSwapchain vulkanSurface surfaceFormats dimensions vulkanLogicalDevice
|
(swapchain, surfaceFormat) <-
|
||||||
|
createSwapchain vulkanSurface surfaceFormats dimensions vulkanLogicalDevice
|
||||||
imageViews <- getImageViewHandles swapchain surfaceFormat vulkanLogicalDevice
|
imageViews <- getImageViewHandles swapchain surfaceFormat vulkanLogicalDevice
|
||||||
|
vertexShader <- loadShader vulkanLogicalDevice "shadersrc/shader.vert"
|
||||||
|
fragmentShader <- loadShader vulkanLogicalDevice "shadersrc/shader.frag"
|
||||||
|
(pipelines, renderPass) <-
|
||||||
|
createGraphicsPipelines
|
||||||
|
vulkanLogicalDevice
|
||||||
|
vertexShader
|
||||||
|
fragmentShader
|
||||||
|
"main"
|
||||||
|
dimensions
|
||||||
|
(Khr.format surfaceFormat)
|
||||||
|
|
||||||
return (vulkanSurface, vulkanInstance, swapchain, imageViews)
|
frameBuffer <- createFramebuffer vulkanLogicalDevice renderPass imageViews dimensions
|
||||||
|
|
||||||
|
(commandPool, graphicsQueue) <- createCommandPool vulkanPhysicalDevice vulkanLogicalDevice
|
||||||
|
commandBuffer <- createCommandBuffer vulkanLogicalDevice commandPool
|
||||||
|
|
||||||
|
(imageAvailableSemaphore, renderFinishedSemaphore, inFlightFence) <-
|
||||||
|
createSyncObjects vulkanLogicalDevice
|
||||||
|
|
||||||
|
return
|
||||||
|
( window
|
||||||
|
, vulkanLogicalDevice
|
||||||
|
, swapchain
|
||||||
|
, graphicsQueue
|
||||||
|
, commandBuffer
|
||||||
|
, inFlightFence
|
||||||
|
, imageAvailableSemaphore
|
||||||
|
, renderFinishedSemaphore
|
||||||
|
)
|
||||||
|
|
21
src/Main.hs
21
src/Main.hs
|
@ -8,6 +8,7 @@ import Control.Monad.Loops
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
import Control.Monad.Trans.Resource
|
import Control.Monad.Trans.Resource
|
||||||
import Data.Bits
|
import Data.Bits
|
||||||
|
import qualified Data.Vector as V
|
||||||
import Data.Word
|
import Data.Word
|
||||||
import qualified SDL hiding (V2)
|
import qualified SDL hiding (V2)
|
||||||
import qualified Control.Concurrent.STM as STM
|
import qualified Control.Concurrent.STM as STM
|
||||||
|
@ -17,10 +18,11 @@ import qualified Vulkan.Zero as Vk
|
||||||
-- internal imports
|
-- internal imports
|
||||||
|
|
||||||
import Init
|
import Init
|
||||||
|
import Draw (drawFrame)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = runResourceT $ do
|
main = runResourceT $ do
|
||||||
(window, inst, surface, swapchain, images) <- initEngine
|
(window, logicalDevice, swapchain, graphicsQueue, commandBuffer, inFlightFence, imageAvailableSemaphore, renderFinishedSemaphore) <- initEngine
|
||||||
|
|
||||||
SDL.showWindow window
|
SDL.showWindow window
|
||||||
|
|
||||||
|
@ -28,17 +30,26 @@ main = runResourceT $ do
|
||||||
quit <- liftIO $ STM.newTMVarIO True
|
quit <- liftIO $ STM.newTMVarIO True
|
||||||
|
|
||||||
-- main loop
|
-- main loop
|
||||||
liftIO $ whileM_
|
whileM_
|
||||||
(STM.atomically $ STM.readTMVar quit
|
(liftIO $ STM.atomically $ STM.readTMVar quit
|
||||||
)
|
)
|
||||||
( do
|
( do
|
||||||
|
-- draw
|
||||||
|
drawFrame
|
||||||
|
logicalDevice
|
||||||
|
swapchain
|
||||||
|
graphicsQueue
|
||||||
|
(V.head commandBuffer)
|
||||||
|
inFlightFence
|
||||||
|
imageAvailableSemaphore
|
||||||
|
renderFinishedSemaphore
|
||||||
-- poll events
|
-- poll events
|
||||||
evs <- SDL.pollEvents
|
evs <- liftIO SDL.pollEvents
|
||||||
-- flip abort condition on window close
|
-- flip abort condition on window close
|
||||||
mapM_
|
mapM_
|
||||||
(\e -> case SDL.eventPayload e of
|
(\e -> case SDL.eventPayload e of
|
||||||
SDL.WindowClosedEvent _ ->
|
SDL.WindowClosedEvent _ ->
|
||||||
void $ STM.atomically $ STM.swapTMVar quit False
|
void $ liftIO $ STM.atomically $ STM.swapTMVar quit False
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
)
|
)
|
||||||
evs
|
evs
|
||||||
|
|
|
@ -30,6 +30,7 @@ executable vulkan-tutorial
|
||||||
GraphicsPipeline
|
GraphicsPipeline
|
||||||
Framebuffers
|
Framebuffers
|
||||||
CommandBuffer
|
CommandBuffer
|
||||||
|
Draw
|
||||||
|
|
||||||
-- LANGUAGE extensions used by modules in this package.
|
-- LANGUAGE extensions used by modules in this package.
|
||||||
-- other-extensions:
|
-- other-extensions:
|
||||||
|
|
Loading…
Reference in a new issue