/* * Vulkan Example - Using position fetch with hardware accelerated ray tracing * * Shows how to use the VK_KHR_ray_tracing_position_fetch extension to fetch the vertex positions in the shader from a hit triangle as stored in the acceleration structure * See https://www.khronos.org/blog/introducing-vulkan-ray-tracing-position-fetch-extension * * Copyright (C) 2024 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #include "VulkanRaytracingSample.h" #include "VulkanglTFModel.h" class VulkanExample : public VulkanRaytracingSample { public: AccelerationStructure bottomLevelAS{}; AccelerationStructure topLevelAS{}; std::vector shaderGroups{}; struct ShaderBindingTables { ShaderBindingTable raygen; ShaderBindingTable miss; ShaderBindingTable hit; } shaderBindingTables; struct UniformData { glm::mat4 viewInverse; glm::mat4 projInverse; glm::vec4 lightPos; } uniformData; vks::Buffer ubo; VkPipeline pipeline{ VK_NULL_HANDLE }; VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; vkglTF::Model scene; // This extension comes with a dedicated feature structure VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR enabledRayTracingPositionFetchFeatures{}; // This sample is derived from an extended base class that saves most of the ray tracing setup boiler plate VulkanExample() : VulkanRaytracingSample() { title = "Ray tracing position fetch"; timerSpeed *= 0.5f; camera.rotationSpeed *= 0.25f; camera.type = Camera::CameraType::firstperson; camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f); camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f)); camera.setTranslation(glm::vec3(0.0f, 3.0f, -10.0f)); enableExtensions(); // Enable new extension enabledDeviceExtensions.push_back(VK_KHR_RAY_TRACING_POSITION_FETCH_EXTENSION_NAME); // The corresponding GLSL extension is GL_EXT_ray_tracing_position_fetch } ~VulkanExample() { vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); deleteStorageImage(); deleteAccelerationStructure(bottomLevelAS); deleteAccelerationStructure(topLevelAS); shaderBindingTables.raygen.destroy(); shaderBindingTables.miss.destroy(); shaderBindingTables.hit.destroy(); ubo.destroy(); } /* Create the bottom level acceleration structure containing the scene's actual geometry (vertices, triangles) */ void createBottomLevelAccelerationStructure() { // Instead of a simple triangle, we'll be loading a more complex scene for this example // The shaders are accessing the vertex and index buffers of the scene, so the proper usage flag has to be set on the vertex and index buffers for the scene vkglTF::memoryPropertyFlags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue, glTFLoadingFlags); VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{}; VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{}; vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.vertices.buffer); indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.indices.buffer); uint32_t numTriangles = static_cast(scene.indices.count) / 3; // Build VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR(); accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; accelerationStructureGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; accelerationStructureGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; accelerationStructureGeometry.geometry.triangles.vertexData = vertexBufferDeviceAddress; accelerationStructureGeometry.geometry.triangles.maxVertex = scene.vertices.count - 1; accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(vkglTF::Vertex); accelerationStructureGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32; accelerationStructureGeometry.geometry.triangles.indexData = indexBufferDeviceAddress; accelerationStructureGeometry.geometry.triangles.transformData.deviceAddress = 0; accelerationStructureGeometry.geometry.triangles.transformData.hostAddress = nullptr; // Get size info VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; // We will access vertex positions from the bottom AS in the shader, so we need to set the VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; accelerationStructureBuildGeometryInfo.geometryCount = 1; accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR(); vkGetAccelerationStructureBuildSizesKHR(device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &accelerationStructureBuildGeometryInfo, &numTriangles, &accelerationStructureBuildSizesInfo); createAccelerationStructure(bottomLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, accelerationStructureBuildSizesInfo); // Create a small scratch buffer used during build of the bottom level acceleration structure ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize); VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; // We will access vertex positions from the bottom AS in the shader, so we need to set the VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; accelerationBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle; accelerationBuildGeometryInfo.geometryCount = 1; accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress; VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{}; accelerationStructureBuildRangeInfo.primitiveCount = numTriangles; accelerationStructureBuildRangeInfo.primitiveOffset = 0; accelerationStructureBuildRangeInfo.firstVertex = 0; accelerationStructureBuildRangeInfo.transformOffset = 0; std::vector accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo }; // Build the acceleration structure on the device via a one-time command buffer submission // Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); vkCmdBuildAccelerationStructuresKHR(commandBuffer, 1, &accelerationBuildGeometryInfo, accelerationBuildStructureRangeInfos.data()); vulkanDevice->flushCommandBuffer(commandBuffer, queue); deleteScratchBuffer(scratchBuffer); } /* The top level acceleration structure contains the scene's object instances */ void createTopLevelAccelerationStructure() { VkTransformMatrixKHR transformMatrix = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }; VkAccelerationStructureInstanceKHR instance{}; instance.transform = transformMatrix; instance.instanceCustomIndex = 0; instance.mask = 0xFF; instance.instanceShaderBindingTableRecordOffset = 0; instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; instance.accelerationStructureReference = bottomLevelAS.deviceAddress; // Buffer for instance data vks::Buffer instancesBuffer; VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &instancesBuffer, sizeof(VkAccelerationStructureInstanceKHR), &instance)); VkDeviceOrHostAddressConstKHR instanceDataDeviceAddress{}; instanceDataDeviceAddress.deviceAddress = getBufferDeviceAddress(instancesBuffer.buffer); VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR(); accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; accelerationStructureGeometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR; accelerationStructureGeometry.geometry.instances.arrayOfPointers = VK_FALSE; accelerationStructureGeometry.geometry.instances.data = instanceDataDeviceAddress; // Get size info VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; accelerationStructureBuildGeometryInfo.geometryCount = 1; accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; uint32_t primitive_count = 1; VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR(); vkGetAccelerationStructureBuildSizesKHR(device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &accelerationStructureBuildGeometryInfo, &primitive_count, &accelerationStructureBuildSizesInfo); createAccelerationStructure(topLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, accelerationStructureBuildSizesInfo); // Create a small scratch buffer used during build of the top level acceleration structure ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize); VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; accelerationBuildGeometryInfo.dstAccelerationStructure = topLevelAS.handle; accelerationBuildGeometryInfo.geometryCount = 1; accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress; VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{}; accelerationStructureBuildRangeInfo.primitiveCount = 1; accelerationStructureBuildRangeInfo.primitiveOffset = 0; accelerationStructureBuildRangeInfo.firstVertex = 0; accelerationStructureBuildRangeInfo.transformOffset = 0; std::vector accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo }; // Build the acceleration structure on the device via a one-time command buffer submission // Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); vkCmdBuildAccelerationStructuresKHR(commandBuffer, 1, &accelerationBuildGeometryInfo, accelerationBuildStructureRangeInfos.data()); vulkanDevice->flushCommandBuffer(commandBuffer, queue); deleteScratchBuffer(scratchBuffer); instancesBuffer.destroy(); } /* Create the Shader Binding Tables that binds the programs and top-level acceleration structure SBT Layout used in this sample: /-----------\ | raygen | |-----------| | miss | |-----------| | hit | \-----------/ */ void createShaderBindingTables() { const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize; const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment); const uint32_t groupCount = static_cast(shaderGroups.size()); const uint32_t sbtSize = groupCount * handleSizeAligned; std::vector shaderHandleStorage(sbtSize); VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data())); createShaderBindingTable(shaderBindingTables.raygen, 1); createShaderBindingTable(shaderBindingTables.miss, 1); createShaderBindingTable(shaderBindingTables.hit, 1); // Copy handles memcpy(shaderBindingTables.raygen.mapped, shaderHandleStorage.data(), handleSize); memcpy(shaderBindingTables.miss.mapped, shaderHandleStorage.data() + handleSizeAligned, handleSize); memcpy(shaderBindingTables.hit.mapped, shaderHandleStorage.data() + handleSizeAligned * 2, handleSize); } /* Create the descriptor sets used for the ray tracing dispatch */ void createDescriptors() { // Pools std::vector poolSizes = { { VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 } }; VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet)); // Descriptors VkWriteDescriptorSetAccelerationStructureKHR descriptorAccelerationStructureInfo = vks::initializers::writeDescriptorSetAccelerationStructureKHR(); descriptorAccelerationStructureInfo.accelerationStructureCount = 1; descriptorAccelerationStructureInfo.pAccelerationStructures = &topLevelAS.handle; VkWriteDescriptorSet accelerationStructureWrite{}; accelerationStructureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; // The specialized acceleration structure descriptor has to be chained accelerationStructureWrite.pNext = &descriptorAccelerationStructureInfo; accelerationStructureWrite.dstSet = descriptorSet; accelerationStructureWrite.dstBinding = 0; accelerationStructureWrite.descriptorCount = 1; accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL }; VkDescriptorBufferInfo vertexBufferDescriptor{ scene.vertices.buffer, 0, VK_WHOLE_SIZE }; VkDescriptorBufferInfo indexBufferDescriptor{ scene.indices.buffer, 0, VK_WHOLE_SIZE }; std::vector writeDescriptorSets = { // Binding 0: Top level acceleration structure accelerationStructureWrite, // Binding 1: Ray tracing result image vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor), // Binding 2: Uniform data vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &ubo.descriptor), }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); } /* Create our ray tracing pipeline */ void createRayTracingPipeline() { std::vector setLayoutBindings = { // Binding 0: Acceleration structure vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 0), // Binding 1: Storage image vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_RAYGEN_BIT_KHR, 1), // Binding 2: Uniform buffer vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, 2), }; VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout)); VkPipelineLayoutCreateInfo pPipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCI, nullptr, &pipelineLayout)); /* Setup ray tracing shader groups */ std::vector shaderStages; // Ray generation group { shaderStages.push_back(loadShader(getShadersPath() + "raytracingpositionfetch/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR)); VkRayTracingShaderGroupCreateInfoKHR shaderGroup{}; shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; shaderGroup.generalShader = static_cast(shaderStages.size()) - 1; shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR; shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR; shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; shaderGroups.push_back(shaderGroup); } // Miss group { shaderStages.push_back(loadShader(getShadersPath() + "raytracingpositionfetch/miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR)); VkRayTracingShaderGroupCreateInfoKHR shaderGroup{}; shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; shaderGroup.generalShader = static_cast(shaderStages.size()) - 1; shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR; shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR; shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; shaderGroups.push_back(shaderGroup); } // Closest hit group { shaderStages.push_back(loadShader(getShadersPath() + "raytracingpositionfetch/closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR)); VkRayTracingShaderGroupCreateInfoKHR shaderGroup{}; shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; shaderGroup.generalShader = VK_SHADER_UNUSED_KHR; shaderGroup.closestHitShader = static_cast(shaderStages.size()) - 1; shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR; shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; shaderGroups.push_back(shaderGroup); } VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI = vks::initializers::rayTracingPipelineCreateInfoKHR(); rayTracingPipelineCI.stageCount = static_cast(shaderStages.size()); rayTracingPipelineCI.pStages = shaderStages.data(); rayTracingPipelineCI.groupCount = static_cast(shaderGroups.size()); rayTracingPipelineCI.pGroups = shaderGroups.data(); rayTracingPipelineCI.maxPipelineRayRecursionDepth = 1; rayTracingPipelineCI.layout = pipelineLayout; VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline)); } /* Create the uniform buffer used to pass matrices to the ray tracing ray generation shader */ void createUniformBuffer() { VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &ubo, sizeof(uniformData), &uniformData)); VK_CHECK_RESULT(ubo.map()); updateUniformBuffers(); } /* If the window has been resized, we need to recreate the storage image and it's descriptor */ void handleResize() { // Recreate image createStorageImage(swapChain.colorFormat, { width, height, 1 }); // Update descriptor VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL }; VkWriteDescriptorSet resultImageWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor); vkUpdateDescriptorSets(device, 1, &resultImageWrite, 0, VK_NULL_HANDLE); resized = false; } /* Command buffer generation */ void buildCommandBuffers() { if (resized) { handleResize(); } VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkImageSubresourceRange subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout, 0, 1, &descriptorSet, 0, 0); /* Dispatch the ray tracing commands */ VkStridedDeviceAddressRegionKHR emptySbtEntry = {}; vkCmdTraceRaysKHR( drawCmdBuffers[i], &shaderBindingTables.raygen.stridedDeviceAddressRegion, &shaderBindingTables.miss.stridedDeviceAddressRegion, &shaderBindingTables.hit.stridedDeviceAddressRegion, &emptySbtEntry, width, height, 1); /* Copy ray tracing output to swap chain image */ // Prepare current swap chain image as transfer destination vks::tools::setImageLayout( drawCmdBuffers[i], swapChain.images[i], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange); // Prepare ray tracing output image as transfer source vks::tools::setImageLayout( drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, subresourceRange); VkImageCopy copyRegion{}; copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; copyRegion.srcOffset = { 0, 0, 0 }; copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; copyRegion.dstOffset = { 0, 0, 0 }; copyRegion.extent = { width, height, 1 }; vkCmdCopyImage(drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapChain.images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); // Transition swap chain image back for presentation vks::tools::setImageLayout( drawCmdBuffers[i], swapChain.images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, subresourceRange); // Transition ray tracing output image back to general layout vks::tools::setImageLayout( drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, subresourceRange); drawUI(drawCmdBuffers[i], frameBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void updateUniformBuffers() { uniformData.projInverse = glm::inverse(camera.matrices.perspective); uniformData.viewInverse = glm::inverse(camera.matrices.view); uniformData.lightPos = glm::vec4(cos(glm::radians(timer * 360.0f)) * 25.0f, 25.0f, 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f, 0.0f); memcpy(ubo.mapped, &uniformData, sizeof(uniformData)); } void getEnabledFeatures() { // Enable features required for ray tracing using feature chaining via pNext enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE; enabledRayTracingPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; enabledRayTracingPipelineFeatures.rayTracingPipeline = VK_TRUE; enabledRayTracingPipelineFeatures.pNext = &enabledBufferDeviceAddresFeatures; enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE; enabledAccelerationStructureFeatures.pNext = &enabledRayTracingPipelineFeatures; // VK_KHR_ray_tracing_position_fetch has a new feature struct enabledRayTracingPositionFetchFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_POSITION_FETCH_FEATURES_KHR; enabledRayTracingPositionFetchFeatures.rayTracingPositionFetch = VK_TRUE; enabledRayTracingPositionFetchFeatures.pNext = &enabledAccelerationStructureFeatures; deviceCreatepNextChain = &enabledRayTracingPositionFetchFeatures; } void prepare() { VulkanRaytracingSample::prepare(); // Create the acceleration structures used to render the ray traced scene createBottomLevelAccelerationStructure(); createTopLevelAccelerationStructure(); createStorageImage(swapChain.colorFormat, { width, height, 1 }); createUniformBuffer(); createRayTracingPipeline(); createShaderBindingTables(); createDescriptors(); buildCommandBuffers(); prepared = true; } void draw() { VulkanExampleBase::prepareFrame(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } virtual void render() { if (!prepared) return; draw(); if (!paused || camera.updated) updateUniformBuffers(); } }; VULKAN_EXAMPLE_MAIN()