module Resource.Image ( AllocatedImage(..) , create , destroy , subresource , transitionLayout , copyBufferToImage ) where import RIO import Data.Bits (shiftR, (.|.)) import RIO.Vector qualified as Vector import Vulkan.Core10 qualified as Vk import Vulkan.CStruct.Extends (SomeStruct(..)) import Vulkan.NamedType ((:::)) import Vulkan.Utils.Debug qualified as Debug import Vulkan.Zero (zero) import VulkanMemoryAllocator qualified as VMA import Engine.Vulkan.Types (HasVulkan(..), Queues(..)) import Resource.CommandBuffer (oneshot_) data AllocatedImage = AllocatedImage { aiAllocation :: VMA.Allocation , aiExtent :: Vk.Extent2D , aiFormat :: Vk.Format , aiImage :: Vk.Image , aiImageView :: Vk.ImageView , aiImageRange :: Vk.ImageSubresourceRange } deriving (Show) create :: ( MonadIO io , HasVulkan ctx ) => ctx -> Maybe Text -> Vk.ImageAspectFlags -> "image dimensions" ::: Vk.Extent2D -> "mip levels" ::: Word32 -> "stored layers" ::: Word32 -> Vk.SampleCountFlagBits -> Vk.Format -> Vk.ImageUsageFlags -> io AllocatedImage create context mlabel aspect extent mipLevels numLayers samples format usage = do let device = getDevice context allocator = getAllocator context (image, allocation, _info) <- VMA.createImage allocator imageCI imageAllocationCI for_ mlabel \label -> Debug.nameObject device image $ encodeUtf8 label <> ".label" imageView <- Vk.createImageView device (imageViewCI image) Nothing for_ mlabel \label -> Debug.nameObject device image $ encodeUtf8 label <> ".view" pure AllocatedImage { aiAllocation = allocation , aiExtent = extent , aiFormat = format , aiImage = image , aiImageView = imageView , aiImageRange = subr } where Vk.Extent2D{width, height} = extent imageCI = zero { Vk.imageType = Vk.IMAGE_TYPE_2D , Vk.flags = createFlags , Vk.format = format , Vk.extent = Vk.Extent3D width height 1 , Vk.mipLevels = mipLevels , Vk.arrayLayers = numLayers , Vk.tiling = Vk.IMAGE_TILING_OPTIMAL , Vk.initialLayout = Vk.IMAGE_LAYOUT_UNDEFINED , Vk.usage = usage , Vk.sharingMode = Vk.SHARING_MODE_EXCLUSIVE , Vk.samples = samples } imageAllocationCI = zero { VMA.usage = VMA.MEMORY_USAGE_GPU_ONLY , VMA.requiredFlags = Vk.MEMORY_PROPERTY_DEVICE_LOCAL_BIT } imageViewCI image = zero { Vk.image = image , Vk.viewType = viewType , Vk.format = format , Vk.components = zero , Vk.subresourceRange = subr } (createFlags, viewType) = case numLayers of 1 -> (zero, Vk.IMAGE_VIEW_TYPE_2D) 6 -> (Vk.IMAGE_CREATE_CUBE_COMPATIBLE_BIT, Vk.IMAGE_VIEW_TYPE_CUBE) _ -> (zero, Vk.IMAGE_VIEW_TYPE_2D_ARRAY) subr = subresource aspect mipLevels numLayers destroy :: ( MonadIO io , HasVulkan context ) => context -> AllocatedImage -> io () destroy context AllocatedImage{..} = do -- traceM "destroyAllocatedImage" Vk.destroyImageView (getDevice context) aiImageView Nothing VMA.destroyImage (getAllocator context) aiImage aiAllocation -------------------------------------------- transitionLayout :: ( HasVulkan context , MonadUnliftIO m ) => context -> Queues Vk.CommandPool -> Vk.Image -> "mip levels" ::: Word32 -> "layer count" ::: Word32 -> Vk.Format -> "old" ::: Vk.ImageLayout -> "new" ::: Vk.ImageLayout -> m () transitionLayout ctx pool image mipLevels layerCount format old new = case (old, new) of (Vk.IMAGE_LAYOUT_UNDEFINED, Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) -> oneshot_ ctx pool qTransfer \buf -> Vk.cmdPipelineBarrier buf Vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT Vk.PIPELINE_STAGE_TRANSFER_BIT zero mempty mempty ( Vector.singleton $ barrier Vk.IMAGE_ASPECT_COLOR_BIT zero Vk.ACCESS_TRANSFER_WRITE_BIT ) (Vk.IMAGE_LAYOUT_UNDEFINED, Vk.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) -> oneshot_ ctx pool qTransfer \buf -> Vk.cmdPipelineBarrier buf Vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT Vk.PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT zero mempty mempty ( Vector.singleton $ barrier aspectMask zero $ Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT .|. Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT ) where aspectMask = if hasStencilComponent then Vk.IMAGE_ASPECT_DEPTH_BIT .|. Vk.IMAGE_ASPECT_STENCIL_BIT else Vk.IMAGE_ASPECT_DEPTH_BIT hasStencilComponent = format == Vk.FORMAT_D32_SFLOAT_S8_UINT || format == Vk.FORMAT_D24_UNORM_S8_UINT (Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, Vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) -> oneshot_ ctx pool qGraphics \buf -> Vk.cmdPipelineBarrier buf Vk.PIPELINE_STAGE_TRANSFER_BIT Vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT zero mempty mempty ( Vector.singleton $ barrier Vk.IMAGE_ASPECT_COLOR_BIT Vk.ACCESS_TRANSFER_WRITE_BIT Vk.ACCESS_SHADER_READ_BIT ) _ -> error $ "Unsupported image layout transfer: " <> show (old, new) where barrier aspectMask srcMask dstMask = SomeStruct zero { Vk.srcAccessMask = srcMask , Vk.dstAccessMask = dstMask , Vk.oldLayout = old , Vk.newLayout = new , Vk.srcQueueFamilyIndex = Vk.QUEUE_FAMILY_IGNORED , Vk.dstQueueFamilyIndex = Vk.QUEUE_FAMILY_IGNORED , Vk.image = image , Vk.subresourceRange = subresource aspectMask mipLevels layerCount } -- imageBarrier cb oldLayout newLayout aspectMask srcStage srcMask dstStage dstMask image mipLevels layerCount = -- Vk.cmdPipelineBarrier -- cb -- srcStage -- dstStage -- zero -- mempty -- mempty -- (Vector.singleton barrier) -- where -- barrier = SomeStruct zero -- { Vk.srcAccessMask = srcMask -- , Vk.dstAccessMask = dstMask -- , Vk.oldLayout = oldLayout -- , Vk.newLayout = newLayout -- , Vk.srcQueueFamilyIndex = Vk.QUEUE_FAMILY_IGNORED -- , Vk.dstQueueFamilyIndex = Vk.QUEUE_FAMILY_IGNORED -- , Vk.image = image -- , Vk.subresourceRange = subresource aspectMask mipLevels layerCount -- } subresource :: Vk.ImageAspectFlags -> "mip levels" ::: Word32 -> "layer count" ::: Word32 -> Vk.ImageSubresourceRange subresource aspectMask mipLevels layerCount = Vk.ImageSubresourceRange { aspectMask = aspectMask , baseMipLevel = 0 , levelCount = mipLevels -- XXX: including base , baseArrayLayer = 0 , layerCount = layerCount } copyBufferToImage :: ( HasVulkan context , Foldable t , Integral deviceSize , MonadUnliftIO m ) => context -> Queues Vk.CommandPool -> Vk.Buffer -> Vk.Image -> "base extent" ::: Vk.Extent3D -> "mip offsets" ::: t deviceSize -> "layer count" ::: Word32 -> m () copyBufferToImage ctx pool src dst Vk.Extent3D{..} mipOffsets layerCount = oneshot_ ctx pool qTransfer \cmd -> Vk.cmdCopyBufferToImage cmd src dst Vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL $ Vector.fromList copyRegions where copyRegions = do (offset, mipLevel) <- zip (toList mipOffsets) [0..] pure Vk.BufferImageCopy { Vk.bufferOffset = fromIntegral offset , Vk.bufferRowLength = zero -- XXX: "use extent width" , Vk.bufferImageHeight = zero -- XXX: "use extent height" , Vk.imageSubresource = Vk.ImageSubresourceLayers { aspectMask = Vk.IMAGE_ASPECT_COLOR_BIT , mipLevel = fromIntegral mipLevel , baseArrayLayer = 0 , layerCount = layerCount } , Vk.imageOffset = zero , Vk.imageExtent = Vk.Extent3D { width = max 1 $ width `shiftR` mipLevel , height = max 1 $ height `shiftR` mipLevel , depth = depth } }