From d82ebc8f32902adf79f28d5bf6a84ba01a8318cd Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 12 Jan 2024 12:45:14 +0100 Subject: [PATCH] Heavily reworked this sample Code cleanup, code restructuring, simplified and lots of new code comments --- .../computeraytracing/computeraytracing.cpp | 661 +++++++----------- .../glsl/computeraytracing/raytracing.comp | 129 ++-- .../computeraytracing/raytracing.comp.spv | Bin 16888 -> 16252 bytes .../hlsl/computeraytracing/raytracing.comp | 150 ++-- .../computeraytracing/raytracing.comp.spv | Bin 16740 -> 14652 bytes 5 files changed, 369 insertions(+), 571 deletions(-) diff --git a/examples/computeraytracing/computeraytracing.cpp b/examples/computeraytracing/computeraytracing.cpp index c0f6badc..f6953f09 100644 --- a/examples/computeraytracing/computeraytracing.cpp +++ b/examples/computeraytracing/computeraytracing.cpp @@ -1,5 +1,11 @@ /* -* Vulkan Example - Compute shader ray tracing +* Vulkan Example - Compute shader based ray tracing +* +* This samples implements a basic ray tracer with materials and reflections using a compute shader +* Shader storage buffers are used to pass geometry information for spheres and planes to the computer shader +* The compute shader then uses these as the scene geometry for ray tracing and outputs the results to a storage image +* The graphics part of the sample then displays that image full screen +* Not to be confused with actual hardware accelerated ray tracing * * Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * @@ -8,44 +14,37 @@ #include "vulkanexamplebase.h" -#if defined(__ANDROID__) -#define TEX_DIM 1024 -#else -#define TEX_DIM 2048 -#endif - class VulkanExample : public VulkanExampleBase { public: - vks::Texture textureComputeTarget; + // The compute shader will store the ray traced output to a storage image + vks::Texture storageImage{}; - // Resources for the graphics part of the example - struct { - VkDescriptorSetLayout descriptorSetLayout; // Raytraced image display shader binding layout - VkDescriptorSet descriptorSetPreCompute; // Raytraced image display shader bindings before compute shader image manipulation - VkDescriptorSet descriptorSet; // Raytraced image display shader bindings after compute shader image manipulation - VkPipeline pipeline; // Raytraced image display pipeline - VkPipelineLayout pipelineLayout; // Layout of the graphics pipeline + // Resources for the graphics part of the example. The graphics pipeline simply displays the compute shader output + struct Graphics { + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; + VkPipeline pipeline{ VK_NULL_HANDLE }; + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; } graphics; // Resources for the compute part of the example - struct { - struct { - vks::Buffer spheres; // (Shader) storage buffer object with scene spheres - vks::Buffer planes; // (Shader) storage buffer object with scene planes - } storageBuffers; - vks::Buffer uniformBuffer; // Uniform buffer object containing scene data - VkQueue queue; // Separate queue for compute commands (queue family may differ from the one used for graphics) - VkCommandPool commandPool; // Use a separate command pool (queue family may differ from the one used for graphics) - VkCommandBuffer commandBuffer; // Command buffer storing the dispatch commands and barriers - VkFence fence; // Synchronization fence to avoid rewriting compute CB if still in use - VkDescriptorSetLayout descriptorSetLayout; // Compute shader binding layout - VkDescriptorSet descriptorSet; // Compute shader bindings - VkPipelineLayout pipelineLayout; // Layout of the compute pipeline - VkPipeline pipeline; // Compute raytracing pipeline - struct UBOCompute { // Compute shader uniform block object + struct Compute { + // Object properties for planes and spheres are passed via a shade storage buffer + // There is no vertex data, the compute shader calculates the primitives on the fly + vks::Buffer objectStorageBuffer; + vks::Buffer uniformBuffer; // Uniform buffer object containing scene parameters + VkQueue queue{ VK_NULL_HANDLE }; // Separate queue for compute commands (queue family may differ from the one used for graphics) + VkCommandPool commandPool{ VK_NULL_HANDLE }; // Use a separate command pool (queue family may differ from the one used for graphics) + VkCommandBuffer commandBuffer{ VK_NULL_HANDLE }; // Command buffer storing the dispatch commands and barriers + VkFence fence{ VK_NULL_HANDLE }; // Synchronization fence to avoid rewriting compute CB if still in use + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Compute shader binding layout + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; // Compute shader bindings + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the compute pipeline + VkPipeline pipeline{ VK_NULL_HANDLE }; // Compute raytracing pipeline + struct UniformDataCompute { // Compute shader uniform block object glm::vec3 lightPos; - float aspectRatio; // Aspect ratio of the viewport + float aspectRatio{ 1.0f }; glm::vec4 fogColor = glm::vec4(0.0f); struct { glm::vec3 pos = glm::vec3(0.0f, 0.0f, 4.0f); @@ -53,33 +52,31 @@ public: float fov = 10.0f; } camera; glm::mat4 _pad; - } ubo; + } uniformData; } compute; - // SSBO sphere declaration - struct Sphere { // Shader uses std140 layout (so we only use vec4 instead of vec3) - glm::vec3 pos; - float radius; - glm::vec3 diffuse; - float specular; - uint32_t id; // Id used to identify sphere for raytracing - glm::ivec3 _pad; + // Definitions for scene objects + // The sample uses spheres and planes that are passed to the compute shader via a shader storage buffer + // The computer shader uses the object type to select different calculations + enum class SceneObjectType { Sphere = 0, Plane = 1 }; + // Spheres and planes are described by different properties, we use a union for this + union SceneObjectProperty { + glm::vec4 positionAndRadius; + glm::vec4 normalAndDistance; }; - - // SSBO plane declaration - struct Plane { - glm::vec3 normal; - float distance; + struct SceneObject { + SceneObjectProperty objectProperties; glm::vec3 diffuse; - float specular; - uint32_t id; - glm::ivec3 _pad; + float specular{ 1.0f }; + uint32_t id{ 0 }; + uint32_t objectType{ 0 }; + // Due to alignment rules we need to pad to make the element align at 16-bytes + glm::ivec2 _pad; }; VulkanExample() : VulkanExampleBase() { title = "Compute shader ray tracing"; - compute.ubo.aspectRatio = (float)width / (float)height; timerSpeed *= 0.25f; camera.type = Camera::CameraType::lookat; @@ -97,41 +94,51 @@ public: ~VulkanExample() { - // Graphics - vkDestroyPipeline(device, graphics.pipeline, nullptr); - vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); + if (device) { + // Graphics + vkDestroyPipeline(device, graphics.pipeline, nullptr); + vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); - // Compute - vkDestroyPipeline(device, compute.pipeline, nullptr); - vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); - vkDestroyFence(device, compute.fence, nullptr); - vkDestroyCommandPool(device, compute.commandPool, nullptr); - compute.uniformBuffer.destroy(); - compute.storageBuffers.spheres.destroy(); - compute.storageBuffers.planes.destroy(); + // Compute + vkDestroyPipeline(device, compute.pipeline, nullptr); + vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); + vkDestroyFence(device, compute.fence, nullptr); + vkDestroyCommandPool(device, compute.commandPool, nullptr); + compute.uniformBuffer.destroy(); + compute.objectStorageBuffer.destroy(); - textureComputeTarget.destroy(); + storageImage.destroy(); + } } - // Prepare a texture target that is used to store compute shader calculations - void prepareTextureTarget(vks::Texture *tex, uint32_t width, uint32_t height, VkFormat format) - { + // Prepare a storage image that is used to store the compute shader ray tracing output + void prepareStorageImage() + { +#if defined(__ANDROID__) + // Use a smaller image on Android for performance reasons + const uint32_t textureSize = 1024; +#else + const uint32_t textureSize = 2048; +#endif + + const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; + // Get device properties for the requested texture format VkFormatProperties formatProperties; vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); - // Check if requested image format supports image storage operations + // Check if requested image format supports image storage operations required for storing pixesl fromn the compute shader assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT); // Prepare blit target texture - tex->width = width; - tex->height = height; + storageImage.width = textureSize; + storageImage.height = textureSize; VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = format; - imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.extent = { textureSize, textureSize, 1 }; imageCreateInfo.mipLevels = 1; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; @@ -144,23 +151,40 @@ public: VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; - VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image)); - vkGetImageMemoryRequirements(device, tex->image, &memReqs); + VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &storageImage.image)); + vkGetImageMemoryRequirements(device, storageImage.image, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory)); - VK_CHECK_RESULT(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0)); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &storageImage.deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device, storageImage.image, storageImage.deviceMemory, 0)); VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - - tex->imageLayout = VK_IMAGE_LAYOUT_GENERAL; - vks::tools::setImageLayout( - layoutCmd, - tex->image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - tex->imageLayout); - + storageImage.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + vks::tools::setImageLayout(layoutCmd, storageImage.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, storageImage.imageLayout); + // Add an initial release barrier to the graphics queue, + // so that when the compute command buffer executes for the first time + // it doesn't complain about a lack of a corresponding "release" to its "acquire" + if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) + { + VkImageMemoryBarrier imageMemoryBarrier = {}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + imageMemoryBarrier.image = storageImage.image; + imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + imageMemoryBarrier.dstAccessMask = 0; + imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; + imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; + vkCmdPipelineBarrier( + layoutCmd, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_FLAGS_NONE, + 0, nullptr, + 0, nullptr, + 1, &imageMemoryBarrier); + } vulkanDevice->flushCommandBuffer(layoutCmd, queue, true); // Create sampler @@ -177,21 +201,21 @@ public: sampler.minLod = 0.0f; sampler.maxLod = 0.0f; sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &tex->sampler)); + VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &storageImage.sampler)); // Create image view VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo(); view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = format; view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - view.image = tex->image; - VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &tex->view)); + view.image = storageImage.image; + VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &storageImage.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; + storageImage.descriptor.imageLayout = storageImage.imageLayout; + storageImage.descriptor.imageView = storageImage.view; + storageImage.descriptor.sampler = storageImage.sampler; + storageImage.device = vulkanDevice; } void buildCommandBuffers() @@ -223,7 +247,7 @@ public: imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = textureComputeTarget.image; + imageMemoryBarrier.image = storageImage.image; imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) { @@ -308,7 +332,7 @@ public: imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = textureComputeTarget.image; + imageMemoryBarrier.image = storageImage.image; imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) { @@ -330,7 +354,7 @@ public: vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline); vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0); - vkCmdDispatch(compute.commandBuffer, textureComputeTarget.width / 16, textureComputeTarget.height / 16, 1); + vkCmdDispatch(compute.commandBuffer, storageImage.width / 16, storageImage.height / 16, 1); if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) { @@ -352,248 +376,120 @@ public: vkEndCommandBuffer(compute.commandBuffer); } - uint32_t currentId = 0; // Id used to identify objects by the ray tracing shader - - Sphere newSphere(glm::vec3 pos, float radius, glm::vec3 diffuse, float specular) - { - Sphere sphere; - sphere.id = currentId++; - sphere.pos = pos; - sphere.radius = radius; - sphere.diffuse = diffuse; - sphere.specular = specular; - return sphere; - } - - Plane newPlane(glm::vec3 normal, float distance, glm::vec3 diffuse, float specular) - { - Plane plane; - plane.id = currentId++; - plane.normal = normal; - plane.distance = distance; - plane.diffuse = diffuse; - plane.specular = specular; - return plane; - } - - // Setup and fill the compute shader storage buffers containing primitives for the raytraced scene + // Setup and fill the compute shader storage buffes containing object definitions for the raytraced scene void prepareStorageBuffers() { - // Spheres - std::vector spheres; - spheres.push_back(newSphere(glm::vec3(1.75f, -0.5f, 0.0f), 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f)); - spheres.push_back(newSphere(glm::vec3(0.0f, 1.0f, -0.5f), 1.0f, glm::vec3(0.65f, 0.77f, 0.97f), 32.0f)); - spheres.push_back(newSphere(glm::vec3(-1.75f, -0.75f, -0.5f), 1.25f, glm::vec3(0.9f, 0.76f, 0.46f), 32.0f)); - VkDeviceSize storageBufferSize = spheres.size() * sizeof(Sphere); + // Id used to identify objects by the ray tracing shader + uint32_t currentId = 0; - // Stage - vks::Buffer stagingBuffer; + std::vector sceneObjects{}; - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &stagingBuffer, - storageBufferSize, - spheres.data()); + // Add some spheres to the scene + //std::vector spheres; + // Lambda to simplify object creation + auto addSphere = [&sceneObjects, ¤tId](glm::vec3 pos, float radius, glm::vec3 diffuse, float specular) { + SceneObject sphere{}; + sphere.id = currentId++; + sphere.objectProperties.positionAndRadius = glm::vec4(pos, radius); + sphere.diffuse = diffuse; + sphere.specular = specular; + sphere.objectType = (uint32_t)SceneObjectType::Sphere; + sceneObjects.push_back(sphere); + }; + + auto addPlane = [&sceneObjects, ¤tId](glm::vec3 normal, float distance, glm::vec3 diffuse, float specular) { + SceneObject plane{}; + plane.id = currentId++; + plane.objectProperties.normalAndDistance = glm::vec4(normal, distance); + plane.diffuse = diffuse; + plane.specular = specular; + plane.objectType = (uint32_t)SceneObjectType::Plane; + sceneObjects.push_back(plane); + }; + + addSphere(glm::vec3(1.75f, -0.5f, 0.0f), 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f); + addSphere(glm::vec3(0.0f, 1.0f, -0.5f), 1.0f, glm::vec3(0.65f, 0.77f, 0.97f), 32.0f); + addSphere(glm::vec3(-1.75f, -0.75f, -0.5f), 1.25f, glm::vec3(0.9f, 0.76f, 0.46f), 32.0f); - vulkanDevice->createBuffer( - // The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &compute.storageBuffers.spheres, - storageBufferSize); - - // Copy to staging buffer - VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - VkBufferCopy copyRegion = {}; - copyRegion.size = storageBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.spheres.buffer, 1, ©Region); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - - // Planes - std::vector planes; const float roomDim = 4.0f; - planes.push_back(newPlane(glm::vec3(0.0f, 1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(0.0f, -1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(0.0f, 0.0f, 1.0f), roomDim, glm::vec3(1.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(0.0f, 0.0f, -1.0f), roomDim, glm::vec3(0.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(-1.0f, 0.0f, 0.0f), roomDim, glm::vec3(1.0f, 0.0f, 0.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(1.0f, 0.0f, 0.0f), roomDim, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f)); - storageBufferSize = planes.size() * sizeof(Plane); + addPlane(glm::vec3(0.0f, 1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f); + addPlane(glm::vec3(0.0f, -1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f); + addPlane(glm::vec3(0.0f, 0.0f, 1.0f), roomDim, glm::vec3(1.0f), 32.0f); + addPlane(glm::vec3(0.0f, 0.0f, -1.0f), roomDim, glm::vec3(0.0f), 32.0f); + addPlane(glm::vec3(-1.0f, 0.0f, 0.0f), roomDim, glm::vec3(1.0f, 0.0f, 0.0f), 32.0f); + addPlane(glm::vec3(1.0f, 0.0f, 0.0f), roomDim, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f); - // Stage - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &stagingBuffer, - storageBufferSize, - planes.data()); + VkDeviceSize storageBufferSize = sceneObjects.size() * sizeof(SceneObject); - vulkanDevice->createBuffer( - // The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &compute.storageBuffers.planes, - storageBufferSize); - - // Copy to staging buffer - copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - copyRegion.size = storageBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.planes.buffer, 1, ©Region); - // Add an initial release barrier to the graphics queue, - // so that when the compute command buffer executes for the first time - // it doesn't complain about a lack of a corresponding "release" to its "acquire" - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - VkImageMemoryBarrier imageMemoryBarrier = {}; - imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = textureComputeTarget.image; - imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - imageMemoryBarrier.dstAccessMask = 0; - imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - vkCmdPipelineBarrier( - copyCmd, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &imageMemoryBarrier); - } + // Copy the data to the device + vks::Buffer stagingBuffer; + vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, storageBufferSize, sceneObjects.data()); + vulkanDevice->createBuffer(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &compute.objectStorageBuffer, storageBufferSize); + VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + VkBufferCopy copyRegion = { 0, 0, storageBufferSize}; + vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.objectStorageBuffer.buffer, 1, ©Region); vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); } + // The descriptor pool will be shared between graphics and compute void setupDescriptorPool() { - std::vector poolSizes = - { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), // Compute UBO - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4), // Graphics image samplers - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1), // Storage image for ray traced image output - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2), // Storage buffer for the scene primitives + // @todo: probably wrong + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2), }; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); - + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } - void setupDescriptorSetLayout() + // Prepare the graphics resources used to display the ray traced output of the compute shader + void prepareGraphics() { - std::vector setLayoutBindings = - { - // Binding 0 : Fragment shader image sampler - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 0) - }; + // Setup descriptors + // The graphics pipeline uses one set and one binding + // Binding 0: Storage image with raytraced output as a sampled image for displaying it + + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0) + }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &storageImage.descriptor) + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); + + // Layout VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - } - void setupDescriptorSet() - { - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo( - descriptorPool, - &graphics.descriptorSetLayout, - 1); - - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - - std::vector writeDescriptorSets = - { - // Binding 0 : Fragment shader texture sampler - vks::initializers::writeDescriptorSet( - graphics.descriptorSet, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 0, - &textureComputeTarget.descriptor) - }; - - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = - vks::initializers::pipelineInputAssemblyStateCreateInfo( - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - 0, - VK_FALSE); - - VkPipelineRasterizationStateCreateInfo rasterizationState = - vks::initializers::pipelineRasterizationStateCreateInfo( - VK_POLYGON_MODE_FILL, - VK_CULL_MODE_FRONT_BIT, - VK_FRONT_FACE_COUNTER_CLOCKWISE, - 0); - - VkPipelineColorBlendAttachmentState blendAttachmentState = - vks::initializers::pipelineColorBlendAttachmentState( - 0xf, - VK_FALSE); - - VkPipelineColorBlendStateCreateInfo colorBlendState = - vks::initializers::pipelineColorBlendStateCreateInfo( - 1, - &blendAttachmentState); - - VkPipelineDepthStencilStateCreateInfo depthStencilState = - vks::initializers::pipelineDepthStencilStateCreateInfo( - VK_FALSE, - VK_FALSE, - VK_COMPARE_OP_LESS_OR_EQUAL); - - VkPipelineViewportStateCreateInfo viewportState = - vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); - - VkPipelineMultisampleStateCreateInfo multisampleState = - vks::initializers::pipelineMultisampleStateCreateInfo( - VK_SAMPLE_COUNT_1_BIT, - 0); - - std::vector dynamicStateEnables = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState = - vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - // Display pipeline + // Pipeline + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0); + std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); std::array shaderStages; shaderStages[0] = loadShader(getShadersPath() + "computeraytracing/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getShadersPath() + "computeraytracing/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); - VkGraphicsPipelineCreateInfo pipelineCreateInfo = - vks::initializers::pipelineCreateInfo( - graphics.pipelineLayout, - renderPass, - 0); - VkPipelineVertexInputStateCreateInfo emptyInputState{}; emptyInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - emptyInputState.vertexAttributeDescriptionCount = 0; - emptyInputState.pVertexAttributeDescriptions = nullptr; - emptyInputState.vertexBindingDescriptionCount = 0; - emptyInputState.pVertexBindingDescriptions = nullptr; - pipelineCreateInfo.pVertexInputState = &emptyInputState; + VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass, 0); + pipelineCreateInfo.pVertexInputState = &emptyInputState; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; @@ -604,11 +500,10 @@ public: pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); pipelineCreateInfo.pStages = shaderStages.data(); pipelineCreateInfo.renderPass = renderPass; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline)); } - // Prepare the compute pipeline that generates the ray traced image + // Prepare the compute resources that generates the ray traced image void prepareCompute() { // Create a compute capable device queue @@ -622,89 +517,39 @@ public: queueCreateInfo.queueCount = 1; vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue); + // Setup descriptors + + // The compute pipeline uses one set and four bindings + // Binding 0: Storage image for raytraced output + // Binding 1: Uniform buffer with parameters + // Binding 2: Shader storage buffer with scene object definitions + std::vector setLayoutBindings = { - // Binding 0: Storage image (raytraced output) - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_SHADER_STAGE_COMPUTE_BIT, - 0), - // Binding 1: Uniform buffer block - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 1), - // Binding 1: Shader storage buffer for the spheres - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 2), - // Binding 1: Shader storage buffer for the planes - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 3) + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 0), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 1), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 2), }; - - VkDescriptorSetLayoutCreateInfo descriptorLayout = - vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); - VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = - vks::initializers::pipelineLayoutCreateInfo( - &compute.descriptorSetLayout, - 1); - - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo( - descriptorPool, - &compute.descriptorSetLayout, - 1); - + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - - std::vector computeWriteDescriptorSets = - { - // Binding 0: Output storage image - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - 0, - &textureComputeTarget.descriptor), - // Binding 1: Uniform buffer block - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 1, - &compute.uniformBuffer.descriptor), - // Binding 2: Shader storage buffer for the spheres - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - 2, - &compute.storageBuffers.spheres.descriptor), - // Binding 2: Shader storage buffer for the planes - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - 3, - &compute.storageBuffers.planes.descriptor) + std::vector computeWriteDescriptorSets = { + vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, &storageImage.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &compute.uniformBuffer.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, &compute.objectStorageBuffer.descriptor), }; - vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); - // Create compute shader pipelines - VkComputePipelineCreateInfo computePipelineCreateInfo = - vks::initializers::computePipelineCreateInfo( - compute.pipelineLayout, - 0); + // Create the compute shader pipeline + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); + VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0); computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computeraytracing/raytracing.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline)); - // Separate command pool as queue family for compute may be different than graphics + // Separate command pool as queue family for compute may be different from the graphics one VkCommandPoolCreateInfo cmdPoolInfo = {}; cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; @@ -712,12 +557,7 @@ public: VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool)); // Create a command buffer for compute operations - VkCommandBufferAllocateInfo cmdBufAllocateInfo = - vks::initializers::commandBufferAllocateInfo( - compute.commandPool, - VK_COMMAND_BUFFER_LEVEL_PRIMARY, - 1); - + VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(compute.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer)); // Fence for compute CB sync @@ -728,30 +568,37 @@ public: buildComputeCommandBuffer(); } - // Prepare and initialize uniform buffer containing shader uniforms void prepareUniformBuffers() { // Compute shader parameter uniform buffer block - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &compute.uniformBuffer, - sizeof(compute.ubo)); - - updateUniformBuffers(); + vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &compute.uniformBuffer, sizeof(Compute::UniformDataCompute)); } void updateUniformBuffers() { - compute.ubo.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f; - compute.ubo.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f; - compute.ubo.lightPos.z = 0.0f + cos(glm::radians(timer * 360.0f)) * 2.0f; - compute.ubo.camera.pos = camera.position * -1.0f; + compute.uniformData.aspectRatio = (float)width / (float)height; + compute.uniformData.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f; + compute.uniformData.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f; + compute.uniformData.lightPos.z = 0.0f + cos(glm::radians(timer * 360.0f)) * 2.0f; + compute.uniformData.camera.pos = camera.position * -1.0f; VK_CHECK_RESULT(compute.uniformBuffer.map()); - memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo)); + memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformDataCompute)); compute.uniformBuffer.unmap(); } + void prepare() + { + VulkanExampleBase::prepare(); + prepareStorageImage(); + prepareStorageBuffers(); + prepareUniformBuffers(); + setupDescriptorPool(); + prepareGraphics(); + prepareCompute(); + buildCommandBuffers(); + prepared = true; + } + void draw() { // Submit compute commands @@ -775,36 +622,12 @@ public: VulkanExampleBase::submitFrame(); } - void prepare() - { - VulkanExampleBase::prepare(); - prepareTextureTarget(&textureComputeTarget, TEX_DIM, TEX_DIM, VK_FORMAT_R8G8B8A8_UNORM); - prepareStorageBuffers(); - prepareUniformBuffers(); - setupDescriptorSetLayout(); - preparePipelines(); - setupDescriptorPool(); - setupDescriptorSet(); - prepareCompute(); - buildCommandBuffers(); - prepared = true; - } - virtual void render() { if (!prepared) return; - draw(); - if (!paused) - { - updateUniformBuffers(); - } - } - - virtual void viewChanged() - { - compute.ubo.aspectRatio = (float)width / (float)height; updateUniformBuffers(); + draw(); } }; diff --git a/shaders/glsl/computeraytracing/raytracing.comp b/shaders/glsl/computeraytracing/raytracing.comp index 051b80bd..256560d4 100644 --- a/shaders/glsl/computeraytracing/raytracing.comp +++ b/shaders/glsl/computeraytracing/raytracing.comp @@ -1,3 +1,5 @@ +// Copyright 2023 Sascha Willems + // Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org) #version 450 @@ -13,6 +15,9 @@ layout (binding = 0, rgba8) uniform writeonly image2D resultImage; #define REFLECTIONSTRENGTH 0.4 #define REFLECTIONFALLOFF 0.5 +#define SceneObjectTypeSphere 0 +#define SceneObjectTypePlane 1 + struct Camera { vec3 pos; @@ -29,32 +34,18 @@ layout (binding = 1) uniform UBO mat4 rotMat; } ubo; -struct Sphere +struct SceneObject { - vec3 pos; - float radius; + vec4 objectProperties; vec3 diffuse; float specular; int id; + int objectType; }; -struct Plane +layout (std140, binding = 2) buffer SceneObjects { - vec3 normal; - float distance; - vec3 diffuse; - float specular; - int id; -}; - -layout (std140, binding = 2) buffer Spheres -{ - Sphere spheres[ ]; -}; - -layout (std140, binding = 3) buffer Planes -{ - Plane planes[ ]; + SceneObject sceneObjects[ ]; }; void reflectRay(inout vec3 rayD, in vec3 mormal) @@ -78,11 +69,11 @@ float lightSpecular(vec3 normal, vec3 lightDir, float specularFactor) // Sphere =========================================================== -float sphereIntersect(in vec3 rayO, in vec3 rayD, in Sphere sphere) +float sphereIntersect(in vec3 rayO, in vec3 rayD, in SceneObject sphere) { - vec3 oc = rayO - sphere.pos; + vec3 oc = rayO - sphere.objectProperties.xyz; float b = 2.0 * dot(oc, rayD); - float c = dot(oc, oc) - sphere.radius*sphere.radius; + float c = dot(oc, oc) - sphere.objectProperties.w * sphere.objectProperties.w; float h = b*b - 4.0*c; if (h < 0.0) { @@ -93,21 +84,21 @@ float sphereIntersect(in vec3 rayO, in vec3 rayD, in Sphere sphere) return t; } -vec3 sphereNormal(in vec3 pos, in Sphere sphere) +vec3 sphereNormal(in vec3 pos, in SceneObject sphere) { - return (pos - sphere.pos) / sphere.radius; + return (pos - sphere.objectProperties.xyz) / sphere.objectProperties.w; } // Plane =========================================================== -float planeIntersect(vec3 rayO, vec3 rayD, Plane plane) +float planeIntersect(vec3 rayO, vec3 rayD, SceneObject plane) { - float d = dot(rayD, plane.normal); + float d = dot(rayD, plane.objectProperties.xyz); if (d == 0.0) return 0.0; - float t = -(plane.distance + dot(rayO, plane.normal)) / d; + float t = -(plane.objectProperties.w + dot(rayO, plane.objectProperties.xyz)) / d; if (t < 0.0) return 0.0; @@ -119,40 +110,48 @@ float planeIntersect(vec3 rayO, vec3 rayD, Plane plane) int intersect(in vec3 rayO, in vec3 rayD, inout float resT) { int id = -1; + float t = -1000.0f; - for (int i = 0; i < spheres.length(); i++) + for (int i = 0; i < sceneObjects.length(); i++) { - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < resT)) + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + t = sphereIntersect(rayO, rayD, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + t = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((t > EPSILON) && (t < resT)) { - id = spheres[i].id; - resT = tSphere; + id = sceneObjects[i].id; + resT = t; } } - for (int i = 0; i < planes.length(); i++) - { - float tplane = planeIntersect(rayO, rayD, planes[i]); - if ((tplane > EPSILON) && (tplane < resT)) - { - id = planes[i].id; - resT = tplane; - } - } - return id; } float calcShadow(in vec3 rayO, in vec3 rayD, in int objectId, inout float t) { - for (int i = 0; i < spheres.length(); i++) + for (int i = 0; i < sceneObjects.length(); i++) { - if (spheres[i].id == objectId) + if (sceneObjects[i].id == objectId) continue; - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < t)) + + float tLoc = MAXLEN; + + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + tLoc = sphereIntersect(rayO, rayD, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + tLoc = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((tLoc > EPSILON) && (tLoc < t)) { - t = tSphere; + t = tLoc; return SHADOW; } } @@ -180,30 +179,22 @@ vec3 renderScene(inout vec3 rayO, inout vec3 rayD, inout int id) vec3 pos = rayO + t * rayD; vec3 lightVec = normalize(ubo.lightPos - pos); vec3 normal; - - // Planes - - // Spheres - - for (int i = 0; i < planes.length(); i++) + + for (int i = 0; i < sceneObjects.length(); i++) { - if (objectID == planes[i].id) - { - normal = planes[i].normal; + if (objectID == sceneObjects[i].id) { + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + normal = sphereNormal(pos, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + normal = sceneObjects[i].objectProperties.xyz; + } + // Lighting float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, planes[i].specular); - color = diffuse * planes[i].diffuse + specular; - } - } - - for (int i = 0; i < spheres.length(); i++) - { - if (objectID == spheres[i].id) - { - normal = sphereNormal(pos, spheres[i]); - float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, spheres[i].specular); - color = diffuse * spheres[i].diffuse + specular; + float specular = lightSpecular(normal, lightVec, sceneObjects[i].specular); + color = diffuse * sceneObjects[i].diffuse + specular; } } diff --git a/shaders/glsl/computeraytracing/raytracing.comp.spv b/shaders/glsl/computeraytracing/raytracing.comp.spv index ee022372759e95510875c0ef79a19012925023bd..4b706dfe5c14772c09eb0e87886e714c73a64938 100644 GIT binary patch literal 16252 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 Jnb@CQ{u@Y(kV^mn literal 16888 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 b>g>zB^04g&jy0N%O&+!h;Miwkzd!#E4V<>i diff --git a/shaders/hlsl/computeraytracing/raytracing.comp b/shaders/hlsl/computeraytracing/raytracing.comp index 9f53c7dc..d1842398 100644 --- a/shaders/hlsl/computeraytracing/raytracing.comp +++ b/shaders/hlsl/computeraytracing/raytracing.comp @@ -1,4 +1,5 @@ // Copyright 2020 Google LLC +// Copyright 2023 Sascha Willems // Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org) @@ -12,6 +13,9 @@ RWTexture2D resultImage : register(u0); #define REFLECTIONSTRENGTH 0.4 #define REFLECTIONFALLOFF 0.5 +#define SceneObjectTypeSphere 0 +#define SceneObjectTypePlane 1 + struct Camera { float3 pos; @@ -30,26 +34,16 @@ struct UBO cbuffer ubo : register(b1) { UBO ubo; } -struct Sphere +struct SceneObject { - float3 pos; - float radius; + float4 objectProperties; float3 diffuse; float specular; int id; + int objectType; }; -struct Plane -{ - float3 normal; - float distance; - float3 diffuse; - float specular; - int id; -}; - -StructuredBuffer spheres : register(t2); -StructuredBuffer planes : register(t3); +StructuredBuffer sceneObjects : register(t2); void reflectRay(inout float3 rayD, in float3 mormal) { @@ -72,11 +66,11 @@ float lightSpecular(float3 normal, float3 lightDir, float specularFactor) // Sphere =========================================================== -float sphereIntersect(in float3 rayO, in float3 rayD, in Sphere sphere) +float sphereIntersect(in float3 rayO, in float3 rayD, in SceneObject sphere) { - float3 oc = rayO - sphere.pos; + float3 oc = rayO - sphere.objectProperties.xyz; float b = 2.0 * dot(oc, rayD); - float c = dot(oc, oc) - sphere.radius*sphere.radius; + float c = dot(oc, oc) - sphere.objectProperties.w * sphere.objectProperties.w; float h = b*b - 4.0*c; if (h < 0.0) { @@ -87,21 +81,21 @@ float sphereIntersect(in float3 rayO, in float3 rayD, in Sphere sphere) return t; } -float3 sphereNormal(in float3 pos, in Sphere sphere) +float3 sphereNormal(in float3 pos, in SceneObject sphere) { - return (pos - sphere.pos) / sphere.radius; + return (pos - sphere.objectProperties.xyz) / sphere.objectProperties.w; } // Plane =========================================================== -float planeIntersect(float3 rayO, float3 rayD, Plane plane) +float planeIntersect(float3 rayO, float3 rayD, SceneObject plane) { - float d = dot(rayD, plane.normal); + float d = dot(rayD, plane.objectProperties.xyz); if (d == 0.0) return 0.0; - float t = -(plane.distance + dot(rayO, plane.normal)) / d; + float t = -(plane.objectProperties.w + dot(rayO, plane.objectProperties.xyz)) / d; if (t < 0.0) return 0.0; @@ -113,33 +107,25 @@ float planeIntersect(float3 rayO, float3 rayD, Plane plane) int intersect(in float3 rayO, in float3 rayD, inout float resT) { int id = -1; + float t = MAXLEN; - uint spheresLength; - uint spheresStride; - spheres.GetDimensions(spheresLength, spheresStride); + uint sceneObjectsLength; + uint sceneObjectsStride; + sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride); - int i; - for (i = 0; i < spheresLength; i++) - { - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < resT)) - { - id = spheres[i].id; - resT = tSphere; + for (int i = 0; i < sceneObjectsLength; i++) { + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + t = sphereIntersect(rayO, rayD, sceneObjects[i]); } - } - - uint planesLength; - uint planesStride; - planes.GetDimensions(planesLength, planesStride); - - for (i = 0; i < planesLength; i++) - { - float tplane = planeIntersect(rayO, rayD, planes[i]); - if ((tplane > EPSILON) && (tplane < resT)) + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + t = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((t > EPSILON) && (t < resT)) { - id = planes[i].id; - resT = tplane; + id = sceneObjects[i].id; + resT = t; } } @@ -148,18 +134,29 @@ int intersect(in float3 rayO, in float3 rayD, inout float resT) float calcShadow(in float3 rayO, in float3 rayD, in int objectId, inout float t) { - uint spheresLength; - uint spheresStride; - spheres.GetDimensions(spheresLength, spheresStride); + uint sceneObjectsLength; + uint sceneObjectsStride; + sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride); - for (int i = 0; i < spheresLength; i++) - { - if (spheres[i].id == objectId) + for (int i = 0; i < sceneObjectsLength; i++) { + if (sceneObjects[i].id == objectId) continue; - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < t)) + + float tLoc = MAXLEN; + + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { - t = tSphere; + tLoc = sphereIntersect(rayO, rayD, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) + { + tLoc = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((tLoc > EPSILON) && (tLoc < t)) + { + t = tLoc; return SHADOW; } } @@ -188,38 +185,25 @@ float3 renderScene(inout float3 rayO, inout float3 rayD, inout int id) float3 lightVec = normalize(ubo.lightPos - pos); float3 normal; - // Planes + uint sceneObjectsLength; + uint sceneObjectsStride; + sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride); - // Spheres - - uint planesLength; - uint planesStride; - planes.GetDimensions(planesLength, planesStride); - - int i; - for (i = 0; i < planesLength; i++) - { - if (objectID == planes[i].id) + for (int i = 0; i < sceneObjectsLength; i++) { + if (objectID == sceneObjects[i].id) { - normal = planes[i].normal; + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + normal = sphereNormal(pos, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + normal = sceneObjects[i].objectProperties.xyz; + } + // Lighting float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, planes[i].specular); - color = diffuse * planes[i].diffuse + specular; - } - } - - uint spheresLength; - uint spheresStride; - spheres.GetDimensions(spheresLength, spheresStride); - - for (i = 0; i < spheresLength; i++) - { - if (objectID == spheres[i].id) - { - normal = sphereNormal(pos, spheres[i]); - float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, spheres[i].specular); - color = diffuse * spheres[i].diffuse + specular; + float specular = lightSpecular(normal, lightVec, sceneObjects[i].specular); + color = diffuse * sceneObjects[i].diffuse + specular; } } diff --git a/shaders/hlsl/computeraytracing/raytracing.comp.spv b/shaders/hlsl/computeraytracing/raytracing.comp.spv index d5a9950ef4a776ce8cc6c10ae012ebeb737ad73d..ed2571b668aaa1f76c532c2fbbc5a435973980a4 100644 GIT binary patch literal 14652 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 K;~92n3iMxHf%=62 literal 16740 zcmZvi2b`AGmBwF~f^-xWEFd6Oa8#t(kS2&xK~a;qwqa&~QJ5jaU}DT}bo0w@wq=`L zqlmrt-jYo;O%qMG7wpC!d(`~@-*?Z<{odtGuJ?JKbMCq4*7u!Z>iTZmuPFKyMKPpU zT}Q>UanZM;SWkivEE+3({y~cmYFgdh+BA8}Zu$)=sts&C6rTp_zLb^C?HyDV>o9b+ zjVZ%Y(Z7!W>AwVH13u%x-6yYVYns&B)V{KLSsT28|J74>wXI&WqI*F_RU03^YnF5Z zX={hi)#-t5>zPbRX8--QL-woAc7nWpg@L zbaqw!8!B-eaZB^cwytJlRJBTuo!tjFcbjv4u>(GHdVH$f7^~>oHrnlXMQ7)UwlROT zYM&=n@^@~kV;NBF46e-0;_j|BE!}In+FEC?S-P~Xt7-A76+DTXpI%kaXKw)Ry3#aSK~R>_SR~DjTIj=SWTqg8P3Hk7M+o-3QY zSY;NB+ch#y?9+x~Kru~wb64}qrjwexn&z+QT++N^LB~m*E$)hr1#>aeuAy+lb+vW0 zwskFTX-i`|L#_Gbg;k&W;>;>fZR(1%;MF;6D9#2iS<=+j(cN`&_@4u4NgK_*doG-9 zQR(kI>dqx6usP1gYVb>R()4V~eQsnp?W7XWOw|Q|Yg=mwN`Ser>Pb zGideeYGOLO)w#TZIzNMQpE!f^@)=Ys?`yRU?Q8XVN_9W=D{ch$&hP4)``{*Q`C8mu z<6Ap_x7673`D-X{#TK&$tgPK_HU71+Zm+TBvHBNx)Yu|w?ONPfq#%B!lP z{SIwuXIE3p^0t-}!TLRsv?ZObJ?H&}YTKSz>#IDA)mS{9+O>3cbhmfVDUb0UOL{n~ z^Zt4_8W@Vu$A}3J4ws;#K&m)p7^NBW0~ovz8JzHiX6fM}YHj9$w{`fiLzBm@&Jon?OyeIeL;gxo2 zuQkKVx%(~Ln(}G^M_wm_^Sn-|@+`0V;^dOgH%dP9$~J=#cgbJWM!`rFQ@*T1TjbJVY#qndGS z8#$_#bKDgz&(WV1e0uw=ul)R=7CGu;?9GU_5%m~~IZa5sKiDVu0B~-1uiJie{yT_z z3`Jk@U`kP(djAOc5RjTXer@Iw#HkelytiZR2! zDVV3nf3qqt%wcn|IhgN6W=cMSqED2FTzmv2RJO~T)G za(yD+DDa5eADte2G@Nnl8+;phQLLR6d`yL(I&%wt<{Jk#Z|B{y#v@i6Lr-IqsIOz) zp4v9%Fq--H9&o%d2P3iU2-mOb)30*w!me%`eMU25KYew7tt%NK2xc^d-NF#-hf8MyPxHA5_b&pd5IfCz94be z#`uR&n{&iJI^me>V-t^fZHe1{Q(ARVk7X~PPOXpqTkO~VE}}MH`*5Cbpw`d!QAcjq zqv@-UZJ(hwudr|0r^;PV`&yV-ozYBd(V`Qd|T=Uiv7rUqHfH%``9@NyZg3|LOAweLnR+=KJML*@SH+zEcfFn z7;P_~Mg6UeJC46E@qfs;^YiVD>wjv-ovYI_uKzz~T>tN6d@6hz#eDW9AJ5fvisPBV zf3?ZAjidf9ZJn#-{MWPaPjIzE8Mk}(pTTNA-@?az^gX!xEPTx8U%<{mf3SVX^@$k& z3O0uG88QA1t{yS|9c&EWZ^n@86Jz)fu(>Z}WajyOxSGYs*sI9Td*DCOwAb`%~?aRG=2AVeGxbAXo z_H!oK*xyh6o&{DLhV8VZoeduf*B1Ss1Mcm=q3{km7fs#yGQQ{IJh=Lz6zhDjn(y%M zChZ4s`_Sh4*zN+bx=&~qCe3+W#F!o1S~PvN`OPpW=@-HE+dug@cK5it*!}Gs?aA61 zXC1tg+WFB>yMFF_ZDRNQCisZm@3#M#b9XV=^Jne2-H(@m^|_JpcpqO1RvQlf0H3?S zw^MvRfos2vqHZj40PdVQL zH*amRMrsk`R_x%|eJ~LtO z;j<{t@7dJ8i}XE;i$C5?@&ihILBiSLkc0b? z?;`kvCH~Wl>;JQi8~^7Sp9B9ziT^U=*JFPuGmgUdHu5zr_9bk7%#`f)c;5#Mfrr@vbZJi!*Njmt@@hF3q_8 zT~^|kXI%d)GOquXC4N=L^}jmf=66krUz>5`U6*m!^Tv!D@1~6Fe{+f7l5zcS&A9%z zmH6!$*Z+=;>wjm7-<5Ia^X`n>{+^86-rv*b>v><|-kt815frieWF#f-z5Bu51KQjp z^0567?7g7P{UR58*8DkoE_aLHcE5)=Qu}E4?5T-8cm7N*_RRGMdk6mk>>Xomf8N=D z1nc8{XWT!5)wIhW0~`AW`t|+%I9MNb-_IXgQ+#|sYkz{GZeQXU+mm3&YhIppdDxx; zJ6>&`dwJyeG&s-k8MvC?bN*cQEZDhloZ4(R5&Rrjo9_+p}@_w6fSf7k3>IA-noc`s^n z9QwTqR=X>$!E0c(QP{%g_2d)$&v5tM9jVP5aDCJ-PuiPcV`=-4e&46|aUHe2MNxAd z#m08rZ-evocn7XFi*Y@J{atXZhqkTJqV0Qd?T*tuEsvc40(P9*%w6ujjU24y?l*I? zj<_F!%_+V`J_4)7w}>{i{2aJP<2-!=_AL9@zwZ_`vG0`)ou^OH&BHqK_$xS{o6q2C z+C5L7gN+^M=?kzv>Yk_3Y_P4|WIV2ec2cP5_9Zr!`?(J|&XeypdD!}b<2?DElShtq z;5^5AxSHq5-zhYJoeR&CHrx3-hDNZqI8Xh+YCdsJHcDE4p86-vp@iRnq}g1}eKjy? zai07=#~?KOF|KiJr=RQM`&xUPr@`QLv~eyRvv&Rb4$$T}JWoTwYH^;1!qxKgG%Wc< zn~mZ5dD;Z7k9wS^;b3EF^E_=y?c+LX+mxc_I*MaGHUsDDu{m5V&eI5RtcSMzJdK2F zcbuLldE~qW*l}tzce(pNamU3naNhURaJ4ua&w}G@Xv@#Wb8zjkm!1b3U){a5p4!Kpw7o!4GbeH6@FF#LNt6m6F!9=6xuuBW!kspVlCS6BU(556s2 zpVQed?%nZleboJZg4%Xq_sfK2A5HD=B8)!X#Ou5f+g4%iK>k9x%29UO7B8+Qk4^@uYG?ED4a z1Flc3;bgEr>am7Xz_EtfH>164XbjhFDw^{%h?$g6Oa7iw+iEu#f3DXS?cT$#rtJ!_ zT$}6mJ|$nb50Vyp|3i4Jo3`PU7}G~!?a|N2VE0qpsh@!LQ4gO_!R8RZANwmx`8QEM-ecPR z+bVVY5=USC!RO%@d;mP}dmvmb?$$wI$7ufA^1F2~TzlNDL%_yY_ipuXw|vY=n}6G- zW=>*r@DAJ4iZJip;^wfMPjGjQam&2|%su{l^zny)nKX_^&v)upNvlVTpZm5(vmfIc$9DQTt{d^w9%J5%V2;&0Y!3C_aDDQ- zY9BQ9_y*V)Y&&(^Ex>;U*mmw&V|(WJLsO6QGZU;9#eX+(-e;j1Q@c6(yTOW<)@2m= zs%cw`zdZ8a2CSy-W@>rZ#(?7;;O}eXVVg@l{e$lh*C+10d2oHy_Kh$ zdus={cKf`8SaR3yXz)Sg*+A`@$9wBwH1+a(>ku?;@qSqdCRF9yPn+=%pjMADEdo21 z;D^HXiTm>~us-S$cQH8PYB#RGV^)tihl8EJ;77pqi8VYDtdDxE;g`Y2oj}oEes6sR z&H0J<)>o6i_n&RGo6CXJ+U(o)-jSec+HOr;o9ng{wtU@ohO5PUYXUgdO69 zZ2Jb-|GD7(z!7IAINmvTVv~n$7C7#LyRgY)OtZm`#h7)d z?*7=Euh>KL;A*yueW0el@#ceVukD`1Bh~@fjHT^fYI)d><~*pyTpt6^=XxYBw`0-N zW6y7o|8a2jg^b7bJszxP@u3S=xHrFMbn}XQmn0tlme>NOYsDB67&Io39? zHubnOmL^~C4DE4e#Q9o=rcV^xnfLP4t_kd1I1YWCtD~s3$6PHS8MT;ydDy-LUP}yZ z_fgBkb^>kAL<@c*T%X`8N_=JVk2|shu8;b+(|UJ;)noovfrk-OJ@(Ak!1kpr?vpOC z`Dim&^HPgk4k7Q@>t|q-hixI)edHMy$G=^=Chlb)|0ZY}ap!=a%CLW%u09Xlze^YU zP4X<*yWlx${~hxpwCBP8j1YW%#%=#X#*Oh}#_ji|5`Q`4{yWkuiO0A3t8njjALqpW z)x`F_RnqO(`^LNSHL!VGJ3qcdUI*(F-{yY?t7+d7`x{_m$G7>LV13m6Hh+uyZHkZg zsrGj$>h>j$zTO4reZ2=)i*ND2fE|*smi9Bqdg549^TpxMl^H*>_w$I>d@g4s;IL4;Ub`$CE z3$V8MJ|~@uX72GF-zRCf_4xHon)g-s)g_IiQPEuA`lQu^<2$~=Mik>3$9DQTF87)C z_dl!0Jvu<{{3bkTp#uLee)o& zv9!6agQ*mmm9 zf&2SQVEfc&zRtUv`D#B1>==R{oOr}M1g?*I_$&k)Cwvxx#}G?B`aKkETW$91`l>~Y z!@z2Bh8Ba>{Fd;%91iyJyl6XuqUJsmyJrVum#atIBf*X*-rZjYtA+h5VEc{lp09%4 z>VDs7i~NrQ+g5vg3;i8f-Su}&?oqXfeKc6j+{F2LT9?k#G3argj?M6K2`|C#c(CJY zruIC!N0xx|^VE{KeS1Dy;l_yb(FQh7em<7M)nhL%1KUpB@z{r2jAuDm?H2Y@7k#vY z)nbiK0QR)FF`iDaZSwJ~f~&`Pz6Q3P z`e|wZcY%$qE%wA}uzHNM8|*k^o!5Z%QFmUPYqjt{39J@-_v>J_n3r#WeVi9Jj&wVB=~RyMO)$URmXG