dear-imgui-3.0.0: examples/vulkan/Main.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
module Main where
import Control.Exception (throwIO)
import Control.Monad (unless, void, when)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Resource
( ReleaseKey, ResourceT, allocate, allocate_, register, release, runResourceT )
import Data.Word (Word32)
import Data.Bits ((.|.))
import Data.Complex (Complex (..), imagPart, realPart)
import Data.Foldable (traverse_)
import qualified Data.Vector as V
import qualified Data.Vector.Storable as SV
import Foreign (castPtr, copyBytes, withForeignPtr)
import qualified Graphics.UI.GLFW as GLFW
import qualified Codec.Picture as Picture
import Vulkan (pattern API_VERSION_1_3)
import qualified Vulkan as Vk
import Vulkan.CStruct.Extends (SomeStruct (..))
import qualified Vulkan.Extensions.VK_KHR_surface as SurfaceFormatKHR (SurfaceFormatKHR (..))
import Vulkan.Zero (zero)
import Vulkan.Utils.Barrier (imageBarrier)
import Vulkan.Utils.Frame
import Vulkan.Utils.Framebuffer (allocateFramebuffer)
import qualified Vulkan.Utils.Init.GLFW as Init
import Vulkan.Utils.Init.GLFW.Window (createWindow, drawableSize, showWindow, withGLFW)
import Vulkan.Utils.QueueAssignment (QueueFamilyIndex (..))
import Vulkan.Utils.Queues (Queues (..), allocateDevice)
import Vulkan.Utils.RenderPass (allocateColorRenderPass)
import Vulkan.Utils.Swapchain
import Vulkan.Utils.VulkanContext (VulkanContext (..), mkVulkanContext)
import Vulkan.Utils.WindowLoop
import qualified VulkanMemoryAllocator as VMA
import VulkanMemoryAllocator.Utils (allocatorCreateInfo)
import DearImGui.Impl.Init (InitFailed (..))
import qualified DearImGui as ImGui
import qualified DearImGui.Impl.GLFW as ImplGlfw
import qualified DearImGui.Impl.Vulkan as ImplVulkan
main :: IO ()
main = runResourceT do
withGLFW
window <- createWindow "DearImGui - Vulkan" 1280 720
inst <- Init.allocateInstance window (Just appInfo) frameInstanceRequirements []
surface <- Init.allocateSurface inst window
(phys, dev, queues) <- allocateDevice inst (Just surface) frameDeviceRequirements
vc <- liftIO $ mkVulkanContext inst phys dev queues
windowSize <- drawableSize window
let swapchainConfig = defaultSwapchainConfig { scSurfaceFormatPreferences = [ImplVulkan.surfaceFormatPreference] }
swapchain <- allocateSwapchain phys dev swapchainConfig Vk.NULL_HANDLE windowSize surface
(_, renderPass) <- allocateColorRenderPass dev (SurfaceFormatKHR.format (sFormat swapchain)) Vk.IMAGE_LAYOUT_PRESENT_SRC_KHR
(_, vma) <- VMA.withAllocator (allocatorCreateInfo zero apiVersion inst phys dev) allocate
(textureSize, textureView) <- uploadTexture vc vma (juliaSet 512 384)
descriptorPool <- ImplVulkan.mkDescriptorPool dev 1
void $ allocate ImGui.createContext ImGui.destroyContext
void $ allocate_
( ImplGlfw.initForVulkan window True >>= \initialized ->
unless initialized $ throwIO (InitFailed "ImGui_ImplGlfw_InitForVulkan")
)
ImplGlfw.shutdown
let (QueueFamilyIndex graphicsFamily, graphicsQueue) = qGraphics queues
void $ allocate_
( ImplVulkan.init descriptorPool ImplVulkan.InitArgs
{ apiVersion
, inst
, physicalDevice = phys
, device = dev
, queueFamily = graphicsFamily
, queue = graphicsQueue
, imageCount = swapchainImageCount swapchain
, target = ImplVulkan.RenderPassTarget renderPass 0
, msaaSamples = Vk.SAMPLE_COUNT_1_BIT
}
)
ImplVulkan.shutdown
Vk.DescriptorSet textureSet <- liftIO $ ImplVulkan.addTexture textureView Vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
showWindow window
runWindowLoop vc swapchain (drawableSize window) (windowClosed window) WindowLoop
{ wlMkState = allocateFramebuffers dev renderPass
, wlMkRecycled = noRecycledResources
, wlRender = renderFrame vc renderPass textureSize (fromIntegral textureSet)
, wlOnFrame = noOnFrame
, wlOnExit = noOnExit
}
where
apiVersion = API_VERSION_1_3
appInfo = zero { Vk.applicationName = Just "DearImGui - Vulkan", Vk.apiVersion = apiVersion }
swapchainImageCount :: Swapchain -> Word32
swapchainImageCount = fromIntegral . length . sImages
windowClosed :: GLFW.Window -> IO Bool
windowClosed window = GLFW.pollEvents *> GLFW.windowShouldClose window
allocateFramebuffers :: Vk.Device -> Vk.RenderPass -> Swapchain -> ResourceT IO (V.Vector Vk.Framebuffer, ReleaseKey)
allocateFramebuffers dev renderPass swapchain@Swapchain{sImageViews, sExtent} = do
liftIO $ ImplVulkan.setMinImageCount (swapchainImageCount swapchain)
(keys, framebuffers) <- V.unzip <$> traverse (\view -> allocateFramebuffer dev renderPass view sExtent) sImageViews
key <- register (traverse_ release keys)
pure (framebuffers, key)
renderFrame :: VulkanContext () -> Vk.RenderPass -> ImGui.ImVec2 -> ImGui.ImTextureID -> V.Vector Vk.Framebuffer -> Frame () -> ResourceT IO ()
renderFrame vc renderPass textureSize textureId framebuffers frame = do
(acquireResult, imageIndex) <- acquireFrameImage vc frame
let
renderPassBegin = zero
{ Vk.renderPass = renderPass
, Vk.framebuffer = framebuffers V.! fromIntegral imageIndex
, Vk.renderArea = Vk.Rect2D zero (sExtent (fSwapchain frame))
, Vk.clearValues = [Vk.Color (Vk.Float32 0.5 0.2 0 1)]
}
commandBuffer <- recordCommands vc frame \cb ->
Vk.cmdUseRenderPass cb renderPassBegin Vk.SUBPASS_CONTENTS_INLINE do
drawData <- gui textureSize textureId
liftIO $ ImplVulkan.renderDrawData drawData (Vk.commandBufferHandle cb) Vk.NULL_HANDLE
queueSubmitFrame vc frame imageIndex [commandBuffer]
presentFrameImage vc frame acquireResult imageIndex
gui :: ImGui.ImVec2 -> ImGui.ImTextureID -> ResourceT IO ImGui.DrawData
gui textureSize textureId = do
liftIO ImplVulkan.newFrame
liftIO ImplGlfw.newFrame
ImGui.newFrame
ImGui.showDemoWindow
ImGui.withWindowOpen "Vulkan demo" do
clicked <-
ImGui.imageButton
"##btn"
(ImGui.textureRefFromID textureId)
textureSize
(ImGui.ImVec2 0 0)
(ImGui.ImVec2 1 1)
(ImGui.ImVec4 0 0 0 0)
(ImGui.ImVec4 1 1 1 1)
when clicked $
ImGui.text "clicky click!"
ImGui.render
ImGui.getDrawData
juliaSet :: Int -> Int -> Picture.Image Picture.PixelRGBA8
juliaSet width height = Picture.generateImage pixel width height
where
pixel px py = maybe transparent cosinePalette (escapeTime 0 (plane px width 1.6 :+ plane py height 1.2))
plane i n half = (fromIntegral i / fromIntegral n * 2 - 1) * half
transparent = Picture.PixelRGBA8 0 0 0 0
c = (-0.7269) :+ 0.1889
maxIter = 128 :: Int
escapeTime :: Int -> Complex Double -> Maybe Double
escapeTime n z
| n >= maxIter = Nothing
| r2 > 16 = Just ((fromIntegral n + 1 - logBase 2 (logBase 2 (sqrt r2))) / fromIntegral maxIter)
| otherwise = escapeTime (n + 1) (z * z + c)
where
r2 = realPart z * realPart z + imagPart z * imagPart z
cosinePalette :: Double -> Picture.PixelRGBA8
cosinePalette t = Picture.PixelRGBA8 (channel 0) (channel 0.1) (channel 0.2) 255
where
channel phase = round (255 * (0.5 + 0.5 * cos (2 * pi * (1.2 * sqrt t + phase))))
uploadTexture :: VulkanContext rr -> VMA.Allocator -> Picture.Image Picture.PixelRGBA8 -> ResourceT IO (ImGui.ImVec2, Vk.ImageView)
uploadTexture vc vma picture = do
let
width = fromIntegral (Picture.imageWidth picture)
height = fromIntegral (Picture.imageHeight picture)
format = Vk.FORMAT_R8G8B8A8_SRGB
extent = Vk.Extent3D width height 1
(pixels, pixelBytes) = SV.unsafeToForeignPtr0 (Picture.imageData picture)
dev = vcDevice vc
copyRegion = Vk.BufferImageCopy 0 0 0 (Vk.ImageSubresourceLayers Vk.IMAGE_ASPECT_COLOR_BIT 0 0 1) zero extent
(_, (image, _, _)) <- VMA.withImage vma
zero
{ Vk.imageType = Vk.IMAGE_TYPE_2D
, Vk.mipLevels = 1
, Vk.arrayLayers = 1
, Vk.format = format
, Vk.extent = extent
, Vk.tiling = Vk.IMAGE_TILING_OPTIMAL
, Vk.usage = Vk.IMAGE_USAGE_SAMPLED_BIT .|. Vk.IMAGE_USAGE_TRANSFER_DST_BIT
, Vk.samples = Vk.SAMPLE_COUNT_1_BIT
}
zero { VMA.usage = VMA.MEMORY_USAGE_GPU_ONLY, VMA.requiredFlags = Vk.MEMORY_PROPERTY_DEVICE_LOCAL_BIT }
allocate
liftIO $ runResourceT do
(_, (staging, stagingAllocation, stagingInfo)) <- VMA.withBuffer vma
zero { Vk.size = fromIntegral pixelBytes, Vk.usage = Vk.BUFFER_USAGE_TRANSFER_SRC_BIT }
zero
{ VMA.flags = VMA.ALLOCATION_CREATE_MAPPED_BIT
, VMA.usage = VMA.MEMORY_USAGE_CPU_TO_GPU
, VMA.requiredFlags = Vk.MEMORY_PROPERTY_HOST_VISIBLE_BIT
}
allocate
liftIO $ withForeignPtr pixels \src -> copyBytes (VMA.mappedData stagingInfo) (castPtr src) pixelBytes
VMA.flushAllocation vma stagingAllocation 0 Vk.WHOLE_SIZE
oneShot vc \cb -> do
Vk.cmdPipelineBarrier cb Vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT Vk.PIPELINE_STAGE_TRANSFER_BIT zero [] []
[ imageBarrier Vk.IMAGE_ASPECT_COLOR_BIT zero Vk.ACCESS_TRANSFER_WRITE_BIT
Vk.IMAGE_LAYOUT_UNDEFINED Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL image
]
Vk.cmdCopyBufferToImage cb staging image Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL (V.singleton copyRegion)
Vk.cmdPipelineBarrier cb Vk.PIPELINE_STAGE_TRANSFER_BIT Vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT zero [] []
[ imageBarrier Vk.IMAGE_ASPECT_COLOR_BIT Vk.ACCESS_TRANSFER_WRITE_BIT Vk.ACCESS_SHADER_READ_BIT
Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL Vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL image
]
(_, view) <- Vk.withImageView dev
zero
{ Vk.image = image
, Vk.viewType = Vk.IMAGE_VIEW_TYPE_2D
, Vk.format = format
, Vk.subresourceRange = zero { Vk.aspectMask = Vk.IMAGE_ASPECT_COLOR_BIT, Vk.levelCount = 1, Vk.layerCount = 1 }
}
Nothing
allocate
pure (ImGui.ImVec2 (fromIntegral width) (fromIntegral height), view)
oneShot :: VulkanContext rr -> (Vk.CommandBuffer -> IO ()) -> ResourceT IO ()
oneShot VulkanContext{vcDevice, vcQueues} record = do
let (QueueFamilyIndex family, queue) = qGraphics vcQueues
pool <- allocateCommandPool vcDevice family
cb <- allocatePrimary vcDevice pool
liftIO (record cb)
Vk.endCommandBuffer cb
(_, fence) <- Vk.withFence vcDevice zero Nothing allocate
Vk.queueSubmit queue [SomeStruct zero { Vk.commandBuffers = [Vk.commandBufferHandle cb] }] fence
void $ Vk.waitForFences vcDevice [fence] True maxBound