diff --git a/src/main/kotlin/vkn/direct fields.kt b/src/main/kotlin/vkn/direct fields.kt index ceb03ff..9dc3f90 100644 --- a/src/main/kotlin/vkn/direct fields.kt +++ b/src/main/kotlin/vkn/direct fields.kt @@ -1550,16 +1550,29 @@ inline var VkGraphicsPipelineCreateInfo.basePipelineIndex get() = VkGraphicsPipelineCreateInfo.nbasePipelineIndex(adr) set(value) = VkGraphicsPipelineCreateInfo.nbasePipelineIndex(adr, value) -// -//typedef struct VkComputePipelineCreateInfo { -// VkStructureType sType; -// const void* pNext; -// VkPipelineCreateFlags flags; -// VkPipelineShaderStageCreateInfo stage; -// VkPipelineLayout layout; -// VkPipeline basePipelineHandle; -// int32_t basePipelineIndex; -//} VkComputePipelineCreateInfo; + +inline var VkComputePipelineCreateInfo.type: VkStructureType + get() = VkStructureType of VkComputePipelineCreateInfo.nsType(adr) + set(value) = VkComputePipelineCreateInfo.nsType(adr, value.i) +inline var VkComputePipelineCreateInfo.next: Long + get() = VkComputePipelineCreateInfo.npNext(adr) + set(value) = VkComputePipelineCreateInfo.npNext(adr, value) +inline var VkComputePipelineCreateInfo.flags: VkPipelineCreateFlags + get() = VkComputePipelineCreateInfo.nflags(adr) + set(value) = VkComputePipelineCreateInfo.nflags(adr, value) +inline var VkComputePipelineCreateInfo.stage: VkPipelineShaderStageCreateInfo + get() = VkComputePipelineCreateInfo.nstage(adr) + set(value) = VkComputePipelineCreateInfo.nstage(adr, value) +inline var VkComputePipelineCreateInfo.layout: VkPipelineLayout + get() = VkComputePipelineCreateInfo.nlayout(adr) + set(value) = VkComputePipelineCreateInfo.nlayout(adr, value) +inline var VkComputePipelineCreateInfo.basePipelineHandle: VkPipeline + get() = VkComputePipelineCreateInfo.nbasePipelineHandle(adr) + set(value) = VkComputePipelineCreateInfo.nbasePipelineHandle(adr, value) +inline var VkComputePipelineCreateInfo.basePipelineIndex: Int + get() = VkComputePipelineCreateInfo.nbasePipelineIndex(adr) + set(value) = VkComputePipelineCreateInfo.nbasePipelineIndex(adr, value) + inline var VkPushConstantRange.stageFlags: VkShaderStageFlags get() = VkPushConstantRange.nstageFlags(adr) diff --git a/src/main/kotlin/vkn/extension functions.kt b/src/main/kotlin/vkn/extension functions.kt index ef30d93..e167588 100644 --- a/src/main/kotlin/vkn/extension functions.kt +++ b/src/main/kotlin/vkn/extension functions.kt @@ -137,9 +137,13 @@ inline infix fun VkCommandBuffer.setViewport(size: Vec2i) { } inline fun VkCommandBuffer.setViewport(size: Vec2i, minDepth: Float, maxDepth: Float) { + setViewport(size.x.f, size.y.f, minDepth, maxDepth) +} + +inline fun VkCommandBuffer.setViewport(width: Float, height: Float, minDepth: Float = 0f, maxDepth: Float = 1f) { setViewport(vk.Viewport { - width = size.x.f - height = size.y.f + this.width = width + this.height = height this.minDepth = minDepth this.maxDepth = maxDepth }) @@ -226,6 +230,12 @@ inline infix fun VkDevice.createCommandPool(createInfo: VkCommandPoolCreateInfo) return memGetLong(pCommandPool) } +inline fun VkDevice.createComputePipelines(pipelineCache: VkPipelineCache, createInfo: VkComputePipelineCreateInfo): VkPipeline { + val pPipeline = appBuffer.long + VK_CHECK_RESULT(VK10.nvkCreateComputePipelines(this, pipelineCache, 1, createInfo.adr, NULL, pPipeline)) + return memGetLong(pPipeline) +} + inline infix fun VkDevice.createDescriptorPool(createInfo: VkDescriptorPoolCreateInfo): VkDescriptorPool { val pDescriptorPool = appBuffer.long VK_CHECK_RESULT(VK10.nvkCreateDescriptorPool(this, createInfo.adr, NULL, pDescriptorPool)) diff --git a/src/main/kotlin/vkn/vk.kt b/src/main/kotlin/vkn/vk.kt index dc026f0..6959225 100644 --- a/src/main/kotlin/vkn/vk.kt +++ b/src/main/kotlin/vkn/vk.kt @@ -8,6 +8,7 @@ import glm_.vec2.Vec2i import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil.* import org.lwjgl.system.Pointer +import org.lwjgl.system.Struct import org.lwjgl.vulkan.* import org.lwjgl.vulkan.VK10.VK_QUEUE_FAMILY_IGNORED import vkn.VkPhysicalDeviceArrayList.resize @@ -69,6 +70,13 @@ object vk { return res } + inline fun ComputePipelineCreateInfo(block: VkComputePipelineCreateInfo.() -> Unit): VkComputePipelineCreateInfo { + val res = VkComputePipelineCreateInfo.create(ptr.advance(VkComputePipelineCreateInfo.SIZEOF)) + res.type = VkStructureType.COMPUTE_PIPELINE_CREATE_INFO + res.block() + return res + } + inline fun DebugReportCallbackCreateInfoEXT(block: VkDebugReportCallbackCreateInfoEXT.() -> Unit): VkDebugReportCallbackCreateInfoEXT { val res = VkDebugReportCallbackCreateInfoEXT.create(ptr.advance(VkDebugReportCallbackCreateInfoEXT.SIZEOF)) res.type = VkStructureType.DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT @@ -477,6 +485,13 @@ object vk { Function Constructors */ + inline fun ComputePipelineCreateInfo(layout: VkPipelineLayout, flags: VkPipelineCreateFlags = 0): VkComputePipelineCreateInfo { + return ComputePipelineCreateInfo { + this.layout = layout + this.flags = flags + } + } + inline fun DescriptorImageInfo(sampler: VkSampler, imageView: VkImageView, imageLayout: VkImageLayout): VkDescriptorImageInfo { return DescriptorImageInfo { this.sampler = sampler @@ -521,6 +536,26 @@ object vk { } } + inline fun DescriptorPoolSize( + type0: VkDescriptorType, descriptorCount0: Int, + type1: VkDescriptorType, descriptorCount1: Int, + type2: VkDescriptorType, descriptorCount2: Int): VkDescriptorPoolSize.Buffer { + return DescriptorPoolSize(3) { + this[0].apply { + type = type0 + descriptorCount = descriptorCount0 + } + this[1].apply { + type = type1 + descriptorCount = descriptorCount1 + } + this[2].apply { + type = type2 + descriptorCount = descriptorCount2 + } + } + } + inline fun DescriptorSetAllocateInfo(descriptorPool: VkDescriptorPool, setLayout: VkDescriptorSetLayout): VkDescriptorSetAllocateInfo { return DescriptorSetAllocateInfo { this.descriptorPool = descriptorPool @@ -577,6 +612,12 @@ object vk { } } + inline fun FenceCreateInfo( flags: VkFenceCreateFlags = 0): VkFenceCreateInfo { + return FenceCreateInfo { + this.flags = flags + } + } + inline fun GraphicsPipelineCreateInfo(layout: VkPipelineLayout, renderPass: VkRenderPass, flags: VkPipelineCreateFlags = 0): VkGraphicsPipelineCreateInfo { return GraphicsPipelineCreateInfo { this.layout = layout @@ -635,7 +676,7 @@ object vk { inline fun PipelineLayoutCreateInfo(setLayout: VkDescriptorSetLayout): VkPipelineLayoutCreateInfo { return PipelineLayoutCreateInfo { - setLayouts = appBuffer longBufferOf setLayout + this.setLayout = setLayout } } @@ -690,9 +731,13 @@ object vk { } inline fun Viewport(size: Vec2i, minDepth: Float = 0f, maxDepth: Float = 1f): VkViewport { + return Viewport(size.x.f, size.y.f, minDepth, maxDepth) + } + + inline fun Viewport(width: Float, height: Float, minDepth: Float = 0f, maxDepth: Float = 1f): VkViewport { return Viewport { - width = size.x.f - height = size.y.f + this.width = width + this.height = height this.minDepth = minDepth this.maxDepth = maxDepth } @@ -708,20 +753,26 @@ object vk { } inline fun WriteDescriptorSet( - dstSet0: VkDescriptorSet, type0: VkDescriptorType, binding0: Int, bufferInfo0: VkDescriptorBufferInfo, - dstSet1: VkDescriptorSet, type1: VkDescriptorType, binding1: Int, imageInfo1: VkDescriptorImageInfo): VkWriteDescriptorSet.Buffer { + dstSet0: VkDescriptorSet, type0: VkDescriptorType, binding0: Int, info0: Struct, + dstSet1: VkDescriptorSet, type1: VkDescriptorType, binding1: Int, info1: Struct): VkWriteDescriptorSet.Buffer { return WriteDescriptorSet(2) { this[0].apply { this.dstSet = dstSet0 descriptorType = type0 dstBinding = binding0 - bufferInfo_ = bufferInfo0 + if (info0 is VkDescriptorBufferInfo) + bufferInfo_ = info0 + else + imageInfo_ = info0 as VkDescriptorImageInfo } this[1].apply { this.dstSet = dstSet1 descriptorType = type1 dstBinding = binding1 - imageInfo_ = imageInfo1 + if (info1 is VkDescriptorBufferInfo) + bufferInfo_ = info1 + else + imageInfo_ = info1 as VkDescriptorImageInfo } } } diff --git a/src/main/kotlin/vulkan/base/VulkanDevice.kt b/src/main/kotlin/vulkan/base/VulkanDevice.kt index b64fa0a..77cc4e4 100644 --- a/src/main/kotlin/vulkan/base/VulkanDevice.kt +++ b/src/main/kotlin/vulkan/base/VulkanDevice.kt @@ -186,27 +186,29 @@ constructor( queueFamilyIndices.compute = if (requestedQueueTypes has VkQueueFlag.COMPUTE_BIT) { // Dedicated compute queue - if (queueFamilyIndices.compute != queueFamilyIndices.graphics) { + val compute = getQueueFamilyIndex(VkQueueFlag.COMPUTE_BIT) + if (compute != queueFamilyIndices.graphics) { // If compute family index differs, we need an additional queue create info for the compute queue queueCreateInfos += vk.DeviceQueueCreateInfo { - queueFamilyIndex = queueFamilyIndices.compute + queueFamilyIndex = compute queuePriorities = defaultQueuePriority } } - getQueueFamilyIndex(VkQueueFlag.COMPUTE_BIT) + compute } else queueFamilyIndices.graphics // Else we use the same queue queueFamilyIndices.transfer = if (requestedQueueTypes has VkQueueFlag.TRANSFER_BIT) { // Dedicated transfer queue - if (queueFamilyIndices.transfer != queueFamilyIndices.graphics && queueFamilyIndices.transfer != queueFamilyIndices.compute) { + val transfer = getQueueFamilyIndex(VkQueueFlag.TRANSFER_BIT) + if (transfer != queueFamilyIndices.graphics && transfer != queueFamilyIndices.compute) { // If compute family index differs, we need an additional queue create info for the compute queue queueCreateInfos += vk.DeviceQueueCreateInfo { - queueFamilyIndex = queueFamilyIndices.transfer + queueFamilyIndex = transfer queuePriorities = defaultQueuePriority } } - getQueueFamilyIndex(VkQueueFlag.TRANSFER_BIT) + transfer } else queueFamilyIndices.graphics // Else we use the same queue // Create the logical device representation diff --git a/src/main/kotlin/vulkan/base/VulkanExampleBase.kt b/src/main/kotlin/vulkan/base/VulkanExampleBase.kt index 4098c28..c7080a2 100644 --- a/src/main/kotlin/vulkan/base/VulkanExampleBase.kt +++ b/src/main/kotlin/vulkan/base/VulkanExampleBase.kt @@ -715,9 +715,10 @@ abstract class VulkanExampleBase { /** Create swap chain images */ fun setupSwapChain() = swapChain.create(size, settings.vsync) -// -// // Check if command buffers are valid (!= VK_NULL_HANDLE) -// bool checkCommandBuffers(); + + /** Check if command buffers are valid (!= NULL) */ + fun checkCommandBuffers() = drawCmdBuffers.all { it.adr != NULL } + /** Create command buffers for drawing commands */ fun createCommandBuffers() { // Create one command buffer for each swap chain image and reuse for rendering @@ -824,7 +825,7 @@ abstract class VulkanExampleBase { throw RuntimeException("glslang failed to initialize.") spirvCrossLoaded = true } - + type = VkStructureType.PIPELINE_SHADER_STAGE_CREATE_INFO this.stage = stage module = when { isSpirV -> device loadShader fileName diff --git a/src/main/kotlin/vulkan/basics/09 Texture Array.kt b/src/main/kotlin/vulkan/basics/09 Texture Array.kt index 422a74a..79f30e5 100644 --- a/src/main/kotlin/vulkan/basics/09 Texture Array.kt +++ b/src/main/kotlin/vulkan/basics/09 Texture Array.kt @@ -8,6 +8,7 @@ package vulkan.basics +import glfw_.appBuffer import gli_.Texture2dArray import gli_.gli import glm_.L @@ -60,9 +61,9 @@ class TextureArray : VulkanExampleBase() { val textureArray = Texture() private var vertices = object { - val inputState = cVkPipelineVertexInputStateCreateInfo() - val bindingDescription = VkVertexInputBindingDescription.calloc() - val attributeDescriptions = VkVertexInputAttributeDescription.calloc(2) + lateinit var inputState: VkPipelineVertexInputStateCreateInfo + lateinit var bindingDescription: VkVertexInputBindingDescription + lateinit var attributeDescriptions: VkVertexInputAttributeDescription.Buffer } val vertexBuffer = Buffer() @@ -365,14 +366,14 @@ class TextureArray : VulkanExampleBase() { fun generateQuad() { // Setup vertices for a single uv-mapped quad made from two triangles - val vertices = floatBufferOf( + val vertices = appBuffer.floatBufferOf( +2.5f, +2.5f, 0f, 1f, 1f, -2.5f, +2.5f, 0f, 0f, 1f, -2.5f, -2.5f, 0f, 0f, 0f, +2.5f, -2.5f, 0f, 1f, 0f) // Setup indices - val indices = intBufferOf(0, 1, 2, 2, 3, 0) + val indices = appBuffer.intBufferOf(0, 1, 2, 2, 3, 0) indexCount = indices.capacity // Create buffers @@ -393,16 +394,17 @@ class TextureArray : VulkanExampleBase() { fun setupVertexDescriptions() { // Binding description - vertices.bindingDescription(VERTEX_BUFFER_BIND_ID, Vertex.size, VkVertexInputRate.VERTEX) + vertices.bindingDescription = vk.VertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, Vertex.size, VkVertexInputRate.VERTEX) // Attribute descriptions // Describes memory layout and shader positions - // Location 0 : Position - vertices.attributeDescriptions[0](0, VERTEX_BUFFER_BIND_ID, VkFormat.R32G32B32_SFLOAT, 0) - // Location 1 : Texture coordinates - vertices.attributeDescriptions[1](1, VERTEX_BUFFER_BIND_ID, VkFormat.R32G32_SFLOAT, Vec3.size) + vertices.attributeDescriptions = vk.VertexInputAttributeDescription( + // Location 0 : Position + VERTEX_BUFFER_BIND_ID, 0, VkFormat.R32G32B32_SFLOAT, 0, + // Location 1 : Texture coordinates + VERTEX_BUFFER_BIND_ID, 1, VkFormat.R32G32_SFLOAT, Vec3.size) - vertices.inputState.apply { + vertices.inputState = vk.PipelineVertexInputStateCreateInfo { vertexBindingDescription = vertices.bindingDescription vertexAttributeDescriptions = vertices.attributeDescriptions } @@ -445,12 +447,11 @@ class TextureArray : VulkanExampleBase() { // Image descriptor for the texture array val textureDescriptor = vk.DescriptorImageInfo(textureArray.sampler, textureArray.view, textureArray.imageLayout) - val writeDescriptorSets = vk.WriteDescriptorSet(2).also { + val writeDescriptorSets = vk.WriteDescriptorSet( // Binding 0 : Vertex shader uniform buffer - it[0](descriptorSet, VkDescriptorType.UNIFORM_BUFFER, 0, uniformBufferVS.descriptor) + descriptorSet, VkDescriptorType.UNIFORM_BUFFER, 0, uniformBufferVS.descriptor, // Binding 1 : Fragment shader cubemap sampler - it[1](descriptorSet, VkDescriptorType.COMBINED_IMAGE_SAMPLER, 1, textureDescriptor) - } + descriptorSet, VkDescriptorType.COMBINED_IMAGE_SAMPLER, 1, textureDescriptor) device updateDescriptorSets writeDescriptorSets } @@ -551,17 +552,17 @@ class TextureArray : VulkanExampleBase() { memCopy(uboVS.address, uniformBufferVS.mapped[0], uboVS.matrices.size.L) } - fun draw() { + fun draw() { super.prepareFrame() - submitInfo.commandBuffer = drawCmdBuffers [currentBuffer] + submitInfo.commandBuffer = drawCmdBuffers[currentBuffer] queue submit submitInfo super.submitFrame() } - override fun prepare() { + override fun prepare() { super.prepare() loadTextures() @@ -577,8 +578,7 @@ class TextureArray : VulkanExampleBase() { window.show() } - override fun render() - { + override fun render() { if (!prepared) return draw() diff --git a/src/main/kotlin/vulkan/computeShader/01 Image Processing.kt b/src/main/kotlin/vulkan/computeShader/01 Image Processing.kt new file mode 100644 index 0000000..3d1a73c --- /dev/null +++ b/src/main/kotlin/vulkan/computeShader/01 Image Processing.kt @@ -0,0 +1,631 @@ +/* +* Vulkan Example - Compute shader image processing +* +* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +package vulkan.computeShader + +import glfw_.appBuffer +import glm_.L +import glm_.f +import glm_.func.rad +import glm_.glm +import glm_.mat4x4.Mat4 +import glm_.vec2.Vec2 +import glm_.vec2.Vec2i +import glm_.vec3.Vec3 +import org.lwjgl.system.MemoryUtil.* +import org.lwjgl.vulkan.* +import uno.buffer.bufferBig +import uno.kotlin.buffers.capacity +import vkn.* +import vulkan.VERTEX_BUFFER_BIND_ID +import vulkan.assetPath +import vulkan.base.* +import vulkan.base.tools.VK_FLAGS_NONE + + +fun main(args: Array) { + ImageProcessing().apply { + setupWindow() + initVulkan() + prepare() + renderLoop() + destroy() + } +} + +// Vertex layout for this example +private val Vertex = object { + // float pos[3]; +// float uv[2]; + val size = Vec3.size + Vec2.size + val offPos = 0 + val offUv = Vec3.size +} + +private class ImageProcessing : VulkanExampleBase() { + + val textureColorMap = Texture2D() + val textureComputeTarget = Texture2D() + + private val vertices = object { + lateinit var inputState: VkPipelineVertexInputStateCreateInfo + lateinit var bindingDescriptions: VkVertexInputBindingDescription + lateinit var attributeDescriptions: VkVertexInputAttributeDescription.Buffer + } + + /** Resources for the graphics part of the example */ + private val graphics = object { + var descriptorSetLayout: VkDescriptorSetLayout = NULL // Image display shader binding layout + var descriptorSetPreCompute: VkDescriptorSet = NULL // Image display shader bindings before compute shader image manipulation + var descriptorSetPostCompute: VkDescriptorSet = NULL // Image display shader bindings after compute shader image manipulation + var pipeline: VkPipeline = NULL // Image display pipeline + var pipelineLayout: VkPipelineLayout = NULL // Layout of the graphics pipeline + } + + /** Resources for the compute part of the example */ + private val compute = object { + lateinit var queue: VkQueue // Separate queue for compute commands (queue family may differ from the one used for graphics) + var commandPool: VkCommandPool = NULL // Use a separate command pool (queue family may differ from the one used for graphics) + lateinit var commandBuffer: VkCommandBuffer // Command buffer storing the dispatch commands and barriers + var fence: VkFence = NULL // Synchronization fence to avoid rewriting compute CB if still in use + var descriptorSetLayout: VkDescriptorSetLayout = NULL // Compute shader binding layout + var descriptorSet: VkDescriptorSet = NULL // Compute shader bindings + var pipelineLayout: VkPipelineLayout = NULL // Layout of the compute pipeline + val pipelines = ArrayList() // Compute pipelines for image filters + var pipelineIndex = 0 // Current image filtering compute pipeline index + var queueFamilyIndex = 0 // Family index of the graphics queue, used for barriers + } + + val vertexBuffer = Buffer() + var indexBuffer = Buffer() + var indexCount = 0 + + var uniformBufferVS = Buffer() + + private val uboVS = object { + var projection = Mat4() + var model = Mat4() + + fun pack() { + projection to buffer + model.to(buffer, Mat4.size) + } + + val size = Mat4.size * 2 + val buffer = bufferBig(size) + val address = memAddress(buffer) + } + + var vertexBufferSize = 0 + + val shaderNames = ArrayList() + + init { + zoom = -2.0f + title = "Compute shader image load/store" +// settings.overlay = true + } + + override fun destroy() { + device.apply { + // Graphics + destroyPipeline(graphics.pipeline) + destroyPipelineLayout(graphics.pipelineLayout) + destroyDescriptorSetLayout(graphics.descriptorSetLayout) + + // Compute + for (pipeline in compute.pipelines) + destroyPipeline(pipeline) + destroyPipelineLayout(compute.pipelineLayout) + destroyDescriptorSetLayout(compute.descriptorSetLayout) + destroyFence(compute.fence) + destroyCommandPool(compute.commandPool) + } + vertexBuffer.destroy() + indexBuffer.destroy() + uniformBufferVS.destroy() + + textureColorMap.destroy() + textureComputeTarget.destroy() + + super.destroy() + } + + /** Prepare a texture target that is used to store compute shader calculations */ + fun prepareTextureTarget(tex: Texture, size: Vec2i, format: VkFormat) { + + // Get device properties for the requested texture format + val formatProperties = physicalDevice getFormatProperties format + // Check if requested image format supports image storage operations + assert(formatProperties.optimalTilingFeatures has VkFormatFeature.STORAGE_IMAGE_BIT) + + // Prepare blit target texture + tex.size(size) + + val imageCreateInfo = vk.ImageCreateInfo { + imageType = VkImageType.`2D` + this.format = format + extent.set(size.x, size.y, 1) + mipLevels = 1 + arrayLayers = 1 + samples = VkSampleCount.`1_BIT` + tiling = VkImageTiling.OPTIMAL + // Image will be sampled in the fragment shader and used as storage target in the compute shader + usage = VkImageUsage.SAMPLED_BIT or VkImageUsage.STORAGE_BIT + flags = 0 + // Sharing mode exclusive means that ownership of the image does not need to be explicitly transferred between the compute and graphics queue + sharingMode = VkSharingMode.EXCLUSIVE + } + + tex.image = device createImage imageCreateInfo + + val memReqs = device getImageMemoryRequirements tex.image + val memAllocInfo = vk.MemoryAllocateInfo { + allocationSize = memReqs.size + memoryTypeIndex = vulkanDevice.getMemoryType(memReqs.memoryTypeBits, VkMemoryProperty.DEVICE_LOCAL_BIT) + } + tex.deviceMemory = device allocateMemory memAllocInfo + device.bindImageMemory(tex.image, tex.deviceMemory) + + val layoutCmd = super.createCommandBuffer(VkCommandBufferLevel.PRIMARY, true) + + tex.imageLayout = VkImageLayout.GENERAL + tools.setImageLayout( + layoutCmd, tex.image, + VkImageAspect.COLOR_BIT.i, + VkImageLayout.UNDEFINED, + tex.imageLayout) + + super.flushCommandBuffer(layoutCmd, queue, true) + + // Create sampler + val sampler = vk.SamplerCreateInfo { + magFilter = VkFilter.LINEAR + minFilter = VkFilter.LINEAR + mipmapMode = VkSamplerMipmapMode.LINEAR + addressModeU = VkSamplerAddressMode.CLAMP_TO_BORDER + addressModeV = addressModeU + addressModeW = addressModeU + mipLodBias = 0f + maxAnisotropy = 1f + compareOp = VkCompareOp.NEVER + minLod = 0f + maxLod = tex.mipLevels.f + borderColor = VkBorderColor.FLOAT_OPAQUE_WHITE + } + tex.sampler = device createSampler sampler + + // Create image view + val view = vk.ImageViewCreateInfo { + image = NULL + viewType = VkImageViewType.`2D` + this.format = format + components(VkComponentSwizzle.R, VkComponentSwizzle.G, VkComponentSwizzle.B, VkComponentSwizzle.A) + subresourceRange.set(VkImageAspect.COLOR_BIT.i, 0, 1, 0, 1) + image = tex.image + } + tex.view = device createImageView view + + // Initialize a descriptor for later use + tex.descriptor.imageLayout = tex.imageLayout + tex.descriptor.imageView = tex.view + tex.descriptor.sampler = tex.sampler + tex.device = vulkanDevice + } + + fun loadAssets() { + textureColorMap.loadFromFile( + "$assetPath/textures/vulkan_11_rgba.ktx", + VkFormat.R8G8B8A8_UNORM, + vulkanDevice, queue, + VkImageUsage.SAMPLED_BIT or VkImageUsage.STORAGE_BIT, + VkImageLayout.GENERAL) + } + + override fun buildCommandBuffers() { + // Destroy command buffers if already present + if (!checkCommandBuffers()) { + destroyCommandBuffers() + createCommandBuffers() + } + + val cmdBufInfo = vk.CommandBufferBeginInfo() + + val clearValues = vk.ClearValue(2) + clearValues[0].color(defaultClearColor) + clearValues[1].depthStencil.set(1f, 0) + + val renderPassBeginInfo = vk.RenderPassBeginInfo { + renderPass = this@ImageProcessing.renderPass + renderArea.apply { + offset.set(0, 0) + extent.set(size.x, size.y) + } + this.clearValues = clearValues + } + for (i in drawCmdBuffers.indices) { + // Set target frame buffer + renderPassBeginInfo.framebuffer(frameBuffers[i]) + + drawCmdBuffers[i].apply { + + begin(cmdBufInfo) + + // Image memory barrier to make sure that compute shader writes are finished before sampling from the texture + val imageMemoryBarrier = vk.ImageMemoryBarrier { + // We won't be changing the layout of the image + oldLayout = VkImageLayout.GENERAL + newLayout = VkImageLayout.GENERAL + image = textureComputeTarget.image + subresourceRange.set(VkImageAspect.COLOR_BIT.i, 0, 1, 0, 1) + srcAccessMask = VkAccess.SHADER_WRITE_BIT.i + dstAccessMask = VkAccess.SHADER_READ_BIT.i + } + pipelineBarrier( + VkPipelineStage.COMPUTE_SHADER_BIT.i, + VkPipelineStage.FRAGMENT_SHADER_BIT.i, + VK_FLAGS_NONE, + imageMemoryBarriers = imageMemoryBarrier) + beginRenderPass(renderPassBeginInfo, VkSubpassContents.INLINE) + + val viewport = vk.Viewport(size.x * 0.5f, size.y.f) + setViewport(viewport) + + setScissor(size) + + bindVertexBuffers(VERTEX_BUFFER_BIND_ID, vertexBuffer.buffer) + bindIndexBuffer(indexBuffer.buffer, 0, VkIndexType.UINT32) + + // Left (pre compute) + bindDescriptorSets(VkPipelineBindPoint.GRAPHICS, graphics.pipelineLayout, graphics.descriptorSetPreCompute) + bindPipeline(VkPipelineBindPoint.GRAPHICS, graphics.pipeline) + + drawIndexed(indexCount, 1, 0, 0, 0) + + // Right (post compute) + bindDescriptorSets(VkPipelineBindPoint.GRAPHICS, graphics.pipelineLayout, graphics.descriptorSetPostCompute) + bindPipeline(VkPipelineBindPoint.GRAPHICS, graphics.pipeline) + + viewport.x = size.x / 2f + setViewport(viewport) + drawIndexed(indexCount, 1, 0, 0, 0) + + endRenderPass() + + end() + } + } + } + + fun buildComputeCommandBuffer() { + // Flush the queue if we're rebuilding the command buffer after a pipeline change to ensure it's not currently in use + compute.queue.waitIdle() + + val cmdBufInfo = vk.CommandBufferBeginInfo() + + compute.commandBuffer.apply { + + begin(cmdBufInfo) + + bindPipeline(VkPipelineBindPoint.COMPUTE, compute.pipelines[compute.pipelineIndex]) + bindDescriptorSets(VkPipelineBindPoint.COMPUTE, compute.pipelineLayout, compute.descriptorSet) + + VK10.vkCmdDispatch(compute.commandBuffer, textureComputeTarget.size.x / 16, textureComputeTarget.size.y / 16, 1) + + end() + } + } + + /** Setup vertices for a single uv-mapped quad */ + fun generateQuad() { + // Setup vertices for a single uv-mapped quad made from two triangles + val vertices = appBuffer.floatBufferOf( + +1f, +1f, 0f, 1f, 1f, + -1f, +1f, 0f, 0f, 1f, + -1f, -1f, 0f, 0f, 0f, + +1f, -1f, 0f, 1f, 0f) + + // Setup indices + val indices = appBuffer.intBufferOf(0, 1, 2, 2, 3, 0) + indexCount = indices.capacity + + // Create buffers + // For the sake of simplicity we won't stage the vertex data to the gpu memory + // Vertex buffer + vulkanDevice.createBuffer( + VkBufferUsage.VERTEX_BUFFER_BIT.i, + VkMemoryProperty.HOST_VISIBLE_BIT or VkMemoryProperty.HOST_COHERENT_BIT, + vertexBuffer, + vertices) + // Index buffer + vulkanDevice.createBuffer( + VkBufferUsage.INDEX_BUFFER_BIT.i, + VkMemoryProperty.HOST_VISIBLE_BIT or VkMemoryProperty.HOST_COHERENT_BIT, + indexBuffer, + indices) + } + + fun setupVertexDescriptions() { + // Binding description + vertices.bindingDescriptions = vk.VertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, Vertex.size, VkVertexInputRate.VERTEX) + + // Attribute descriptions + // Describes memory layout and shader positions + vertices.attributeDescriptions = vk.VertexInputAttributeDescription( + // Location 0: Position + VERTEX_BUFFER_BIND_ID, 0, VkFormat.R32G32B32_SFLOAT, Vertex.offPos, + // Location 1: Texture coordinates + VERTEX_BUFFER_BIND_ID, 1, VkFormat.R32G32_SFLOAT, Vertex.offUv) + + // Assign to vertex buffer + vertices.inputState = vk.PipelineVertexInputStateCreateInfo { + vertexBindingDescription = vertices.bindingDescriptions + vertexAttributeDescriptions = vertices.attributeDescriptions + } + } + + fun setupDescriptorPool() { + + val poolSizes = vk.DescriptorPoolSize( + // Graphics pipelines uniform buffers + VkDescriptorType.UNIFORM_BUFFER, 2, + // Graphics pipelines image samplers for displaying compute output image + VkDescriptorType.COMBINED_IMAGE_SAMPLER, 2, + // Compute pipelines uses a storage image for image reads and writes + VkDescriptorType.STORAGE_IMAGE, 2) + val descriptorPoolInfo = vk.DescriptorPoolCreateInfo(poolSizes, 3) + descriptorPool = device createDescriptorPool descriptorPoolInfo + } + + fun setupDescriptorSetLayout() { + + val setLayoutBindings = vk.DescriptorSetLayoutBinding( + // Binding 0: Vertex shader uniform buffer + VkDescriptorType.UNIFORM_BUFFER, VkShaderStage.VERTEX_BIT.i, 0, + // Binding 1: Fragment shader input image + VkDescriptorType.COMBINED_IMAGE_SAMPLER, VkShaderStage.FRAGMENT_BIT.i, 1) + + val descriptorLayout = vk.DescriptorSetLayoutCreateInfo(setLayoutBindings) + graphics.descriptorSetLayout = device createDescriptorSetLayout descriptorLayout + + val pipelineLayoutCreateInfo = vk.PipelineLayoutCreateInfo(graphics.descriptorSetLayout) + graphics.pipelineLayout = device createPipelineLayout pipelineLayoutCreateInfo + } + + fun setupDescriptorSet() { + + val allocInfo = vk.DescriptorSetAllocateInfo(descriptorPool, graphics.descriptorSetLayout) + + // Input image (before compute post processing) + graphics.descriptorSetPreCompute = device allocateDescriptorSets allocInfo + val baseImageWriteDescriptorSets = vk.WriteDescriptorSet( + graphics.descriptorSetPreCompute, VkDescriptorType.UNIFORM_BUFFER, 0, uniformBufferVS.descriptor, + graphics.descriptorSetPreCompute, VkDescriptorType.COMBINED_IMAGE_SAMPLER, 1, textureColorMap.descriptor) + + device updateDescriptorSets baseImageWriteDescriptorSets + + // Final image (after compute shader processing) + graphics.descriptorSetPostCompute = device allocateDescriptorSets allocInfo + val writeDescriptorSets = vk.WriteDescriptorSet( + graphics.descriptorSetPostCompute, VkDescriptorType.UNIFORM_BUFFER, 0, uniformBufferVS.descriptor, + graphics.descriptorSetPostCompute, VkDescriptorType.COMBINED_IMAGE_SAMPLER, 1, textureComputeTarget.descriptor) + + device updateDescriptorSets writeDescriptorSets + + } + + fun preparePipelines() { + + val inputAssemblyState = vk.PipelineInputAssemblyStateCreateInfo(VkPrimitiveTopology.TRIANGLE_LIST, 0, false) + + val rasterizationState = vk.PipelineRasterizationStateCreateInfo(VkPolygonMode.FILL, VkCullMode.NONE.i, VkFrontFace.CLOCKWISE) + + val blendAttachmentState = vk.PipelineColorBlendAttachmentState(0xf, false) + + val colorBlendState = vk.PipelineColorBlendStateCreateInfo(blendAttachmentState) + + val depthStencilState = vk.PipelineDepthStencilStateCreateInfo(true, true, VkCompareOp.LESS_OR_EQUAL) + + val viewportState = vk.PipelineViewportStateCreateInfo(1, 1) + + val multisampleState = vk.PipelineMultisampleStateCreateInfo(VkSampleCount.`1_BIT`) + + val dynamicStateEnables = listOf(VkDynamicState.VIEWPORT, VkDynamicState.SCISSOR) + val dynamicState = vk.PipelineDynamicStateCreateInfo(dynamicStateEnables) + + // Rendering pipeline + // Load shaders + val shaderStages = vk.PipelineShaderStageCreateInfo(2).also { + it[0].loadShader("$assetPath/shaders/computeshader/texture.vert.spv", VkShaderStage.VERTEX_BIT) + it[1].loadShader("$assetPath/shaders/computeshader/texture.frag.spv", VkShaderStage.FRAGMENT_BIT) + } + val pipelineCreateInfo = vk.GraphicsPipelineCreateInfo(graphics.pipelineLayout, renderPass).also { + it.vertexInputState = vertices.inputState + it.inputAssemblyState = inputAssemblyState + it.rasterizationState = rasterizationState + it.colorBlendState = colorBlendState + it.multisampleState = multisampleState + it.viewportState = viewportState + it.depthStencilState = depthStencilState + it.dynamicState = dynamicState + it.stages = shaderStages + it.renderPass = renderPass + } + graphics.pipeline = device.createGraphicsPipelines(pipelineCache, pipelineCreateInfo) + } + + /** Find and create a compute capable device queue */ + fun getComputeQueue() { + + val queueFamilyProperties = physicalDevice.queueFamilyProperties + + // Some devices have dedicated compute queues, so we first try to find a queue that supports compute and not graphics + var computeQueueFound = false + for (i in queueFamilyProperties.indices) + if (queueFamilyProperties[i].queueFlags has VkQueueFlag.COMPUTE_BIT && queueFamilyProperties[i].queueFlags hasnt VkQueueFlag.GRAPHICS_BIT) { + compute.queueFamilyIndex = i + computeQueueFound = true + break + } + // If there is no dedicated compute queue, just find the first queue family that supports compute + if (!computeQueueFound) + for (i in queueFamilyProperties.indices) { + if (queueFamilyProperties[i].queueFlags has VkQueueFlag.COMPUTE_BIT) { + compute.queueFamilyIndex = i + computeQueueFound = true + break + } + } + + // Compute is mandatory in Vulkan, so there must be at least one queue family that supports compute + assert(computeQueueFound) + // Get a compute queue from the device + compute.queue = device getQueue compute.queueFamilyIndex + } + + fun prepareCompute() { + + getComputeQueue() + + // Create compute pipeline + // Compute pipelines are created separate from graphics pipelines even if they use the same queue + + val setLayoutBindings = vk.DescriptorSetLayoutBinding( + // Binding 0: Input image (read-only) + VkDescriptorType.STORAGE_IMAGE, VkShaderStage.COMPUTE_BIT.i, 0, + // Binding 1: Output image (write) + VkDescriptorType.STORAGE_IMAGE, VkShaderStage.COMPUTE_BIT.i, 1) + + val descriptorLayout = vk.DescriptorSetLayoutCreateInfo(setLayoutBindings) + compute.descriptorSetLayout = device createDescriptorSetLayout descriptorLayout + + val pipelineLayoutCreateInfo = vk.PipelineLayoutCreateInfo(compute.descriptorSetLayout) + + compute.pipelineLayout = device createPipelineLayout pipelineLayoutCreateInfo + + val allocInfo = vk.DescriptorSetAllocateInfo(descriptorPool, compute.descriptorSetLayout) + + compute.descriptorSet = device allocateDescriptorSets allocInfo + val computeWriteDescriptorSets = vk.WriteDescriptorSet( + compute.descriptorSet, VkDescriptorType.STORAGE_IMAGE, 0, textureColorMap.descriptor, + compute.descriptorSet, VkDescriptorType.STORAGE_IMAGE, 1, textureComputeTarget.descriptor) + + device updateDescriptorSets computeWriteDescriptorSets + + // Create compute shader pipelines + val computePipelineCreateInfo = vk.ComputePipelineCreateInfo(compute.pipelineLayout) + + // One pipeline for each effect + shaderNames += listOf("emboss", "edgedetect", "sharpen") + for (shaderName in shaderNames) { + val fileName = "$assetPath/shaders/computeshader/$shaderName.comp.spv" + computePipelineCreateInfo.stage.loadShader(fileName, VkShaderStage.COMPUTE_BIT) + val pipeline = device.createComputePipelines(pipelineCache, computePipelineCreateInfo) + compute.pipelines += pipeline + } + // Separate command pool as queue family for compute may be different than graphics + val cmdPoolInfo = vk.CommandPoolCreateInfo { + queueFamilyIndex = compute.queueFamilyIndex + flags = VkCommandPoolCreate.RESET_COMMAND_BUFFER_BIT.i + } + compute.commandPool = device createCommandPool cmdPoolInfo + + // Create a command buffer for compute operations + val cmdBufAllocateInfo = vk.CommandBufferAllocateInfo(compute.commandPool, VkCommandBufferLevel.PRIMARY, 1) + + compute.commandBuffer = device allocateCommandBuffer cmdBufAllocateInfo + + // Fence for compute CB sync + val fenceCreateInfo = vk.FenceCreateInfo(VkFenceCreate.SIGNALED_BIT.i) + compute.fence = device createFence fenceCreateInfo + + // Build a single command buffer containing the compute dispatch commands + buildComputeCommandBuffer() + } + + /** Prepare and initialize uniform buffer containing shader uniforms */ + fun prepareUniformBuffers() { + // Vertex shader uniform buffer block + vulkanDevice.createBuffer( + VkBufferUsage.UNIFORM_BUFFER_BIT.i, + VkMemoryProperty.HOST_VISIBLE_BIT or VkMemoryProperty.HOST_COHERENT_BIT, + uniformBufferVS, + uboVS.size.L) + + // Map persistent + uniformBufferVS.map() + + updateUniformBuffers() + } + + fun updateUniformBuffers() { + // Vertex shader uniform buffer block + uboVS.projection = glm.perspective(60f.rad, size.x * 0.5f / size.y, 0.1f, 256f) + val viewMatrix = glm.translate(Mat4(1f), 0f, 0f, zoom) + + uboVS.model = viewMatrix * glm.translate(Mat4(1f), cameraPos) + .rotateAssign(rotation.x.rad, 1f, 0f, 0f) + .rotateAssign(rotation.y.rad, 0f, 1f, 0f) + .rotateAssign(rotation.z.rad, 0f, 0f, 1f) + + uboVS.pack() + memCopy(uboVS.address, uniformBufferVS.mapped[0], uboVS.size.L) + } + + fun draw() { + + super.prepareFrame() + + submitInfo.commandBuffer = drawCmdBuffers[currentBuffer] + queue submit submitInfo + + super.submitFrame() + + // Submit compute commands + // Use a fence to ensure that compute command buffer has finished executin before using it again + device.waitForFence(compute.fence, true, UINT64_MAX) + device.resetFence(compute.fence) + + val computeSubmitInfo = vk.SubmitInfo { commandBuffer = compute.commandBuffer } + compute.queue.submit(computeSubmitInfo, compute.fence) + } + + override fun prepare() { + super.prepare() + loadAssets() + generateQuad() + setupVertexDescriptions() + prepareUniformBuffers() + prepareTextureTarget(textureComputeTarget, textureColorMap.size, VkFormat.R8G8B8A8_UNORM) + setupDescriptorSetLayout() + preparePipelines() + setupDescriptorPool() + setupDescriptorSet() + prepareCompute() + buildCommandBuffers() + prepared = true + window.show() + } + + override fun render() { + if (!prepared) + return + draw() + } + + override fun viewChanged() = updateUniformBuffers() + +// virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) +// { +// if (overlay->header("Settings")) { +// if (overlay->comboBox("Shader", &compute.pipelineIndex, shaderNames)) { +// buildComputeCommandBuffer() +// } +// } +// } +} \ No newline at end of file