Heavily reworked this sample

Code cleanup, code restructuring, simplified and lots of new code comments
This commit is contained in:
Sascha Willems 2024-01-12 12:45:14 +01:00
parent 44ff7a1a9d
commit d82ebc8f32
5 changed files with 369 additions and 571 deletions

View file

@ -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 * Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de
* *
@ -8,44 +14,37 @@
#include "vulkanexamplebase.h" #include "vulkanexamplebase.h"
#if defined(__ANDROID__)
#define TEX_DIM 1024
#else
#define TEX_DIM 2048
#endif
class VulkanExample : public VulkanExampleBase class VulkanExample : public VulkanExampleBase
{ {
public: 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 // Resources for the graphics part of the example. The graphics pipeline simply displays the compute shader output
struct { struct Graphics {
VkDescriptorSetLayout descriptorSetLayout; // Raytraced image display shader binding layout VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE };
VkDescriptorSet descriptorSetPreCompute; // Raytraced image display shader bindings before compute shader image manipulation VkDescriptorSet descriptorSet{ VK_NULL_HANDLE };
VkDescriptorSet descriptorSet; // Raytraced image display shader bindings after compute shader image manipulation VkPipeline pipeline{ VK_NULL_HANDLE };
VkPipeline pipeline; // Raytraced image display pipeline VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE };
VkPipelineLayout pipelineLayout; // Layout of the graphics pipeline
} graphics; } graphics;
// Resources for the compute part of the example // Resources for the compute part of the example
struct { struct Compute {
struct { // Object properties for planes and spheres are passed via a shade storage buffer
vks::Buffer spheres; // (Shader) storage buffer object with scene spheres // There is no vertex data, the compute shader calculates the primitives on the fly
vks::Buffer planes; // (Shader) storage buffer object with scene planes vks::Buffer objectStorageBuffer;
} storageBuffers; vks::Buffer uniformBuffer; // Uniform buffer object containing scene parameters
vks::Buffer uniformBuffer; // Uniform buffer object containing scene data VkQueue queue{ VK_NULL_HANDLE }; // Separate queue for compute commands (queue family may differ from the one used for graphics)
VkQueue queue; // 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)
VkCommandPool commandPool; // 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
VkCommandBuffer commandBuffer; // Command buffer storing the dispatch commands and barriers VkFence fence{ VK_NULL_HANDLE }; // Synchronization fence to avoid rewriting compute CB if still in use
VkFence fence; // Synchronization fence to avoid rewriting compute CB if still in use VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Compute shader binding layout
VkDescriptorSetLayout descriptorSetLayout; // Compute shader binding layout VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; // Compute shader bindings
VkDescriptorSet descriptorSet; // Compute shader bindings VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the compute pipeline
VkPipelineLayout pipelineLayout; // Layout of the compute pipeline VkPipeline pipeline{ VK_NULL_HANDLE }; // Compute raytracing pipeline
VkPipeline pipeline; // Compute raytracing pipeline struct UniformDataCompute { // Compute shader uniform block object
struct UBOCompute { // Compute shader uniform block object
glm::vec3 lightPos; glm::vec3 lightPos;
float aspectRatio; // Aspect ratio of the viewport float aspectRatio{ 1.0f };
glm::vec4 fogColor = glm::vec4(0.0f); glm::vec4 fogColor = glm::vec4(0.0f);
struct { struct {
glm::vec3 pos = glm::vec3(0.0f, 0.0f, 4.0f); glm::vec3 pos = glm::vec3(0.0f, 0.0f, 4.0f);
@ -53,33 +52,31 @@ public:
float fov = 10.0f; float fov = 10.0f;
} camera; } camera;
glm::mat4 _pad; glm::mat4 _pad;
} ubo; } uniformData;
} compute; } compute;
// SSBO sphere declaration // Definitions for scene objects
struct Sphere { // Shader uses std140 layout (so we only use vec4 instead of vec3) // The sample uses spheres and planes that are passed to the compute shader via a shader storage buffer
glm::vec3 pos; // The computer shader uses the object type to select different calculations
float radius; enum class SceneObjectType { Sphere = 0, Plane = 1 };
glm::vec3 diffuse; // Spheres and planes are described by different properties, we use a union for this
float specular; union SceneObjectProperty {
uint32_t id; // Id used to identify sphere for raytracing glm::vec4 positionAndRadius;
glm::ivec3 _pad; glm::vec4 normalAndDistance;
}; };
struct SceneObject {
// SSBO plane declaration SceneObjectProperty objectProperties;
struct Plane {
glm::vec3 normal;
float distance;
glm::vec3 diffuse; glm::vec3 diffuse;
float specular; float specular{ 1.0f };
uint32_t id; uint32_t id{ 0 };
glm::ivec3 _pad; 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() VulkanExample() : VulkanExampleBase()
{ {
title = "Compute shader ray tracing"; title = "Compute shader ray tracing";
compute.ubo.aspectRatio = (float)width / (float)height;
timerSpeed *= 0.25f; timerSpeed *= 0.25f;
camera.type = Camera::CameraType::lookat; camera.type = Camera::CameraType::lookat;
@ -97,6 +94,7 @@ public:
~VulkanExample() ~VulkanExample()
{ {
if (device) {
// Graphics // Graphics
vkDestroyPipeline(device, graphics.pipeline, nullptr); vkDestroyPipeline(device, graphics.pipeline, nullptr);
vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
@ -109,29 +107,38 @@ public:
vkDestroyFence(device, compute.fence, nullptr); vkDestroyFence(device, compute.fence, nullptr);
vkDestroyCommandPool(device, compute.commandPool, nullptr); vkDestroyCommandPool(device, compute.commandPool, nullptr);
compute.uniformBuffer.destroy(); compute.uniformBuffer.destroy();
compute.storageBuffers.spheres.destroy(); compute.objectStorageBuffer.destroy();
compute.storageBuffers.planes.destroy();
textureComputeTarget.destroy(); storageImage.destroy();
}
} }
// Prepare a texture target that is used to store compute shader calculations // Prepare a storage image that is used to store the compute shader ray tracing output
void prepareTextureTarget(vks::Texture *tex, uint32_t width, uint32_t height, VkFormat format) 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 // Get device properties for the requested texture format
VkFormatProperties formatProperties; VkFormatProperties formatProperties;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &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); assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
// Prepare blit target texture // Prepare blit target texture
tex->width = width; storageImage.width = textureSize;
tex->height = height; storageImage.height = textureSize;
VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = format; imageCreateInfo.format = format;
imageCreateInfo.extent = { width, height, 1 }; imageCreateInfo.extent = { textureSize, textureSize, 1 };
imageCreateInfo.mipLevels = 1; imageCreateInfo.mipLevels = 1;
imageCreateInfo.arrayLayers = 1; imageCreateInfo.arrayLayers = 1;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
@ -144,23 +151,40 @@ public:
VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
VkMemoryRequirements memReqs; VkMemoryRequirements memReqs;
VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image)); VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &storageImage.image));
vkGetImageMemoryRequirements(device, tex->image, &memReqs); vkGetImageMemoryRequirements(device, storageImage.image, &memReqs);
memAllocInfo.allocationSize = memReqs.size; memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory)); VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &storageImage.deviceMemory));
VK_CHECK_RESULT(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0)); VK_CHECK_RESULT(vkBindImageMemory(device, storageImage.image, storageImage.deviceMemory, 0));
VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
storageImage.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
tex->imageLayout = VK_IMAGE_LAYOUT_GENERAL; vks::tools::setImageLayout(layoutCmd, storageImage.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, storageImage.imageLayout);
vks::tools::setImageLayout( // 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, layoutCmd,
tex->image, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_IMAGE_ASPECT_COLOR_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED, VK_FLAGS_NONE,
tex->imageLayout); 0, nullptr,
0, nullptr,
1, &imageMemoryBarrier);
}
vulkanDevice->flushCommandBuffer(layoutCmd, queue, true); vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
// Create sampler // Create sampler
@ -177,21 +201,21 @@ public:
sampler.minLod = 0.0f; sampler.minLod = 0.0f;
sampler.maxLod = 0.0f; sampler.maxLod = 0.0f;
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; 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 // Create image view
VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo(); VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.viewType = VK_IMAGE_VIEW_TYPE_2D;
view.format = format; view.format = format;
view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
view.image = tex->image; view.image = storageImage.image;
VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &tex->view)); VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &storageImage.view));
// Initialize a descriptor for later use // Initialize a descriptor for later use
tex->descriptor.imageLayout = tex->imageLayout; storageImage.descriptor.imageLayout = storageImage.imageLayout;
tex->descriptor.imageView = tex->view; storageImage.descriptor.imageView = storageImage.view;
tex->descriptor.sampler = tex->sampler; storageImage.descriptor.sampler = storageImage.sampler;
tex->device = vulkanDevice; storageImage.device = vulkanDevice;
} }
void buildCommandBuffers() void buildCommandBuffers()
@ -223,7 +247,7 @@ public:
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
imageMemoryBarrier.newLayout = 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 }; imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute)
{ {
@ -308,7 +332,7 @@ public:
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
imageMemoryBarrier.newLayout = 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 }; imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute)
{ {
@ -330,7 +354,7 @@ public:
vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline); 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); 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) if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute)
{ {
@ -352,248 +376,120 @@ public:
vkEndCommandBuffer(compute.commandBuffer); vkEndCommandBuffer(compute.commandBuffer);
} }
uint32_t currentId = 0; // Id used to identify objects by the ray tracing shader // Setup and fill the compute shader storage buffes containing object definitions for the raytraced scene
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
void prepareStorageBuffers() void prepareStorageBuffers()
{ {
// Spheres // Id used to identify objects by the ray tracing shader
std::vector<Sphere> spheres; uint32_t currentId = 0;
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);
// Stage std::vector<SceneObject> sceneObjects{};
vks::Buffer stagingBuffer;
vulkanDevice->createBuffer( // Add some spheres to the scene
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, //std::vector<Sphere> spheres;
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, // Lambda to simplify object creation
&stagingBuffer, auto addSphere = [&sceneObjects, &currentId](glm::vec3 pos, float radius, glm::vec3 diffuse, float specular) {
storageBufferSize, SceneObject sphere{};
spheres.data()); sphere.id = currentId++;
sphere.objectProperties.positionAndRadius = glm::vec4(pos, radius);
vulkanDevice->createBuffer( sphere.diffuse = diffuse;
// The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline sphere.specular = specular;
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, sphere.objectType = (uint32_t)SceneObjectType::Sphere;
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, sceneObjects.push_back(sphere);
&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, &copyRegion);
vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
stagingBuffer.destroy();
// Planes
std::vector<Plane> 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);
// 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());
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, &copyRegion);
// 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);
}
vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
stagingBuffer.destroy();
}
void setupDescriptorPool()
{
std::vector<VkDescriptorPoolSize> 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
}; };
VkDescriptorPoolCreateInfo descriptorPoolInfo = auto addPlane = [&sceneObjects, &currentId](glm::vec3 normal, float distance, glm::vec3 diffuse, float specular) {
vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); 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);
const float roomDim = 4.0f;
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);
VkDeviceSize storageBufferSize = sceneObjects.size() * sizeof(SceneObject);
// 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, &copyRegion);
vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
}
// The descriptor pool will be shared between graphics and compute
void setupDescriptorPool()
{
// @todo: probably wrong
std::vector<VkDescriptorPoolSize> 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);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); 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<VkDescriptorSetLayoutBinding> setLayoutBindings = // Setup descriptors
{
// Binding 0 : Fragment shader image sampler
vks::initializers::descriptorSetLayoutBinding(
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_SHADER_STAGE_FRAGMENT_BIT,
0)
};
// 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<VkDescriptorSetLayoutBinding> setLayoutBindings = {
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0)
};
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); 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<VkWriteDescriptorSet> writeDescriptorSets = {
vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &storageImage.descriptor)
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
// Layout
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
}
void setupDescriptorSet() // Pipeline
{ VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkDescriptorSetAllocateInfo allocInfo = VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
vks::initializers::descriptorSetAllocateInfo( VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
descriptorPool, VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
&graphics.descriptorSetLayout, VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
1); VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::vector<VkWriteDescriptorSet> 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<uint32_t>(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<VkDynamicState> dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState =
vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
// Display pipeline
std::array<VkPipelineShaderStageCreateInfo,2> shaderStages; std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
shaderStages[0] = loadShader(getShadersPath() + "computeraytracing/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); 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); shaderStages[1] = loadShader(getShadersPath() + "computeraytracing/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
vks::initializers::pipelineCreateInfo(
graphics.pipelineLayout,
renderPass,
0);
VkPipelineVertexInputStateCreateInfo emptyInputState{}; VkPipelineVertexInputStateCreateInfo emptyInputState{};
emptyInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; 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.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.pColorBlendState = &colorBlendState;
@ -604,11 +500,10 @@ public:
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size()); pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data(); pipelineCreateInfo.pStages = shaderStages.data();
pipelineCreateInfo.renderPass = renderPass; pipelineCreateInfo.renderPass = renderPass;
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline)); 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() void prepareCompute()
{ {
// Create a compute capable device queue // Create a compute capable device queue
@ -622,89 +517,39 @@ public:
queueCreateInfo.queueCount = 1; queueCreateInfo.queueCount = 1;
vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue); 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<VkDescriptorSetLayoutBinding> setLayoutBindings = { std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
// Binding 0: Storage image (raytraced output) vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 0),
vks::initializers::descriptorSetLayoutBinding( vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 1),
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 2),
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)
}; };
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VkDescriptorSetLayoutCreateInfo descriptorLayout =
vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout));
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1);
vks::initializers::pipelineLayoutCreateInfo(
&compute.descriptorSetLayout,
1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
VkDescriptorSetAllocateInfo allocInfo =
vks::initializers::descriptorSetAllocateInfo(
descriptorPool,
&compute.descriptorSetLayout,
1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = {
std::vector<VkWriteDescriptorSet> 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),
// Binding 0: Output storage image vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, &compute.objectStorageBuffer.descriptor),
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)
}; };
vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr);
// Create compute shader pipelines // Create the compute shader pipeline
VkComputePipelineCreateInfo computePipelineCreateInfo = VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1);
vks::initializers::computePipelineCreateInfo( VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
compute.pipelineLayout,
0);
VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computeraytracing/raytracing.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computeraytracing/raytracing.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline)); 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 = {}; VkCommandPoolCreateInfo cmdPoolInfo = {};
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
@ -712,12 +557,7 @@ public:
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool)); VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
// Create a command buffer for compute operations // Create a command buffer for compute operations
VkCommandBufferAllocateInfo cmdBufAllocateInfo = VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(compute.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
vks::initializers::commandBufferAllocateInfo(
compute.commandPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
1);
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer)); VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer));
// Fence for compute CB sync // Fence for compute CB sync
@ -728,30 +568,37 @@ public:
buildComputeCommandBuffer(); buildComputeCommandBuffer();
} }
// Prepare and initialize uniform buffer containing shader uniforms
void prepareUniformBuffers() void prepareUniformBuffers()
{ {
// Compute shader parameter uniform buffer block // Compute shader parameter uniform buffer block
vulkanDevice->createBuffer( 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));
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&compute.uniformBuffer,
sizeof(compute.ubo));
updateUniformBuffers();
} }
void updateUniformBuffers() void updateUniformBuffers()
{ {
compute.ubo.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f; compute.uniformData.aspectRatio = (float)width / (float)height;
compute.ubo.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f; compute.uniformData.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f;
compute.ubo.lightPos.z = 0.0f + cos(glm::radians(timer * 360.0f)) * 2.0f; compute.uniformData.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f;
compute.ubo.camera.pos = camera.position * -1.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()); 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(); compute.uniformBuffer.unmap();
} }
void prepare()
{
VulkanExampleBase::prepare();
prepareStorageImage();
prepareStorageBuffers();
prepareUniformBuffers();
setupDescriptorPool();
prepareGraphics();
prepareCompute();
buildCommandBuffers();
prepared = true;
}
void draw() void draw()
{ {
// Submit compute commands // Submit compute commands
@ -775,36 +622,12 @@ public:
VulkanExampleBase::submitFrame(); 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() virtual void render()
{ {
if (!prepared) if (!prepared)
return; return;
updateUniformBuffers();
draw(); draw();
if (!paused)
{
updateUniformBuffers();
}
}
virtual void viewChanged()
{
compute.ubo.aspectRatio = (float)width / (float)height;
updateUniformBuffers();
} }
}; };

View file

@ -1,3 +1,5 @@
// Copyright 2023 Sascha Willems
// Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org) // Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org)
#version 450 #version 450
@ -13,6 +15,9 @@ layout (binding = 0, rgba8) uniform writeonly image2D resultImage;
#define REFLECTIONSTRENGTH 0.4 #define REFLECTIONSTRENGTH 0.4
#define REFLECTIONFALLOFF 0.5 #define REFLECTIONFALLOFF 0.5
#define SceneObjectTypeSphere 0
#define SceneObjectTypePlane 1
struct Camera struct Camera
{ {
vec3 pos; vec3 pos;
@ -29,32 +34,18 @@ layout (binding = 1) uniform UBO
mat4 rotMat; mat4 rotMat;
} ubo; } ubo;
struct Sphere struct SceneObject
{ {
vec3 pos; vec4 objectProperties;
float radius;
vec3 diffuse; vec3 diffuse;
float specular; float specular;
int id; int id;
int objectType;
}; };
struct Plane layout (std140, binding = 2) buffer SceneObjects
{ {
vec3 normal; SceneObject sceneObjects[ ];
float distance;
vec3 diffuse;
float specular;
int id;
};
layout (std140, binding = 2) buffer Spheres
{
Sphere spheres[ ];
};
layout (std140, binding = 3) buffer Planes
{
Plane planes[ ];
}; };
void reflectRay(inout vec3 rayD, in vec3 mormal) void reflectRay(inout vec3 rayD, in vec3 mormal)
@ -78,11 +69,11 @@ float lightSpecular(vec3 normal, vec3 lightDir, float specularFactor)
// Sphere =========================================================== // 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 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; float h = b*b - 4.0*c;
if (h < 0.0) if (h < 0.0)
{ {
@ -93,21 +84,21 @@ float sphereIntersect(in vec3 rayO, in vec3 rayD, in Sphere sphere)
return t; 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 =========================================================== // 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) if (d == 0.0)
return 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) if (t < 0.0)
return 0.0; return 0.0;
@ -119,24 +110,22 @@ float planeIntersect(vec3 rayO, vec3 rayD, Plane plane)
int intersect(in vec3 rayO, in vec3 rayD, inout float resT) int intersect(in vec3 rayO, in vec3 rayD, inout float resT)
{ {
int id = -1; 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]); // Sphere
if ((tSphere > EPSILON) && (tSphere < resT)) if (sceneObjects[i].objectType == SceneObjectTypeSphere) {
{ t = sphereIntersect(rayO, rayD, sceneObjects[i]);
id = spheres[i].id;
resT = tSphere;
} }
// Plane
if (sceneObjects[i].objectType == SceneObjectTypePlane) {
t = planeIntersect(rayO, rayD, sceneObjects[i]);
} }
if ((t > EPSILON) && (t < resT))
for (int i = 0; i < planes.length(); i++)
{ {
float tplane = planeIntersect(rayO, rayD, planes[i]); id = sceneObjects[i].id;
if ((tplane > EPSILON) && (tplane < resT)) resT = t;
{
id = planes[i].id;
resT = tplane;
} }
} }
@ -145,14 +134,24 @@ int intersect(in vec3 rayO, in vec3 rayD, inout float resT)
float calcShadow(in vec3 rayO, in vec3 rayD, in int objectId, inout float t) 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; 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; return SHADOW;
} }
} }
@ -181,29 +180,21 @@ vec3 renderScene(inout vec3 rayO, inout vec3 rayD, inout int id)
vec3 lightVec = normalize(ubo.lightPos - pos); vec3 lightVec = normalize(ubo.lightPos - pos);
vec3 normal; vec3 normal;
// Planes for (int i = 0; i < sceneObjects.length(); i++)
// Spheres
for (int i = 0; i < planes.length(); i++)
{ {
if (objectID == planes[i].id) if (objectID == sceneObjects[i].id) {
{ // Sphere
normal = planes[i].normal; if (sceneObjects[i].objectType == SceneObjectTypeSphere) {
float diffuse = lightDiffuse(normal, lightVec); normal = sphereNormal(pos, sceneObjects[i]);
float specular = lightSpecular(normal, lightVec, planes[i].specular);
color = diffuse * planes[i].diffuse + specular;
} }
// Plane
if (sceneObjects[i].objectType == SceneObjectTypePlane) {
normal = sceneObjects[i].objectProperties.xyz;
} }
// Lighting
for (int i = 0; i < spheres.length(); i++)
{
if (objectID == spheres[i].id)
{
normal = sphereNormal(pos, spheres[i]);
float diffuse = lightDiffuse(normal, lightVec); float diffuse = lightDiffuse(normal, lightVec);
float specular = lightSpecular(normal, lightVec, spheres[i].specular); float specular = lightSpecular(normal, lightVec, sceneObjects[i].specular);
color = diffuse * spheres[i].diffuse + specular; color = diffuse * sceneObjects[i].diffuse + specular;
} }
} }

View file

@ -1,4 +1,5 @@
// Copyright 2020 Google LLC // Copyright 2020 Google LLC
// Copyright 2023 Sascha Willems
// Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org) // Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org)
@ -12,6 +13,9 @@ RWTexture2D<float4> resultImage : register(u0);
#define REFLECTIONSTRENGTH 0.4 #define REFLECTIONSTRENGTH 0.4
#define REFLECTIONFALLOFF 0.5 #define REFLECTIONFALLOFF 0.5
#define SceneObjectTypeSphere 0
#define SceneObjectTypePlane 1
struct Camera struct Camera
{ {
float3 pos; float3 pos;
@ -30,26 +34,16 @@ struct UBO
cbuffer ubo : register(b1) { UBO ubo; } cbuffer ubo : register(b1) { UBO ubo; }
struct Sphere struct SceneObject
{ {
float3 pos; float4 objectProperties;
float radius;
float3 diffuse; float3 diffuse;
float specular; float specular;
int id; int id;
int objectType;
}; };
struct Plane StructuredBuffer<SceneObject> sceneObjects : register(t2);
{
float3 normal;
float distance;
float3 diffuse;
float specular;
int id;
};
StructuredBuffer<Sphere> spheres : register(t2);
StructuredBuffer<Plane> planes : register(t3);
void reflectRay(inout float3 rayD, in float3 mormal) void reflectRay(inout float3 rayD, in float3 mormal)
{ {
@ -72,11 +66,11 @@ float lightSpecular(float3 normal, float3 lightDir, float specularFactor)
// Sphere =========================================================== // 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 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; float h = b*b - 4.0*c;
if (h < 0.0) if (h < 0.0)
{ {
@ -87,21 +81,21 @@ float sphereIntersect(in float3 rayO, in float3 rayD, in Sphere sphere)
return t; 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 =========================================================== // 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) if (d == 0.0)
return 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) if (t < 0.0)
return 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 intersect(in float3 rayO, in float3 rayD, inout float resT)
{ {
int id = -1; int id = -1;
float t = MAXLEN;
uint spheresLength; uint sceneObjectsLength;
uint spheresStride; uint sceneObjectsStride;
spheres.GetDimensions(spheresLength, spheresStride); sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride);
int i; for (int i = 0; i < sceneObjectsLength; i++) {
for (i = 0; i < spheresLength; i++) // Sphere
{ if (sceneObjects[i].objectType == SceneObjectTypeSphere) {
float tSphere = sphereIntersect(rayO, rayD, spheres[i]); t = sphereIntersect(rayO, rayD, sceneObjects[i]);
if ((tSphere > EPSILON) && (tSphere < resT))
{
id = spheres[i].id;
resT = tSphere;
} }
// Plane
if (sceneObjects[i].objectType == SceneObjectTypePlane) {
t = planeIntersect(rayO, rayD, sceneObjects[i]);
} }
if ((t > EPSILON) && (t < resT))
uint planesLength;
uint planesStride;
planes.GetDimensions(planesLength, planesStride);
for (i = 0; i < planesLength; i++)
{ {
float tplane = planeIntersect(rayO, rayD, planes[i]); id = sceneObjects[i].id;
if ((tplane > EPSILON) && (tplane < resT)) resT = t;
{
id = planes[i].id;
resT = tplane;
} }
} }
@ -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) float calcShadow(in float3 rayO, in float3 rayD, in int objectId, inout float t)
{ {
uint spheresLength; uint sceneObjectsLength;
uint spheresStride; uint sceneObjectsStride;
spheres.GetDimensions(spheresLength, spheresStride); sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride);
for (int i = 0; i < spheresLength; i++) for (int i = 0; i < sceneObjectsLength; i++) {
{ if (sceneObjects[i].id == objectId)
if (spheres[i].id == objectId)
continue; 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; return SHADOW;
} }
} }
@ -188,38 +185,25 @@ float3 renderScene(inout float3 rayO, inout float3 rayD, inout int id)
float3 lightVec = normalize(ubo.lightPos - pos); float3 lightVec = normalize(ubo.lightPos - pos);
float3 normal; float3 normal;
// Planes uint sceneObjectsLength;
uint sceneObjectsStride;
sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride);
// Spheres for (int i = 0; i < sceneObjectsLength; i++) {
if (objectID == sceneObjects[i].id)
uint planesLength;
uint planesStride;
planes.GetDimensions(planesLength, planesStride);
int i;
for (i = 0; i < planesLength; i++)
{ {
if (objectID == planes[i].id) // Sphere
{ if (sceneObjects[i].objectType == SceneObjectTypeSphere) {
normal = planes[i].normal; normal = sphereNormal(pos, sceneObjects[i]);
float diffuse = lightDiffuse(normal, lightVec);
float specular = lightSpecular(normal, lightVec, planes[i].specular);
color = diffuse * planes[i].diffuse + specular;
} }
// Plane
if (sceneObjects[i].objectType == SceneObjectTypePlane) {
normal = sceneObjects[i].objectProperties.xyz;
} }
// Lighting
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 diffuse = lightDiffuse(normal, lightVec);
float specular = lightSpecular(normal, lightVec, spheres[i].specular); float specular = lightSpecular(normal, lightVec, sceneObjects[i].specular);
color = diffuse * spheres[i].diffuse + specular; color = diffuse * sceneObjects[i].diffuse + specular;
} }
} }