diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9484aee..14077ec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,3 +52,4 @@ add_subdirectory(ray_tracing_manyhits)
add_subdirectory(ray_tracing_rayquery)
add_subdirectory(ray_tracing_reflections)
+add_subdirectory(ray_tracing_indirect_scissor)
diff --git a/docs/Images/indirect_scissor/bounding.png b/docs/Images/indirect_scissor/bounding.png
new file mode 100644
index 0000000..0465eb9
Binary files /dev/null and b/docs/Images/indirect_scissor/bounding.png differ
diff --git a/docs/Images/indirect_scissor/bounding2.png b/docs/Images/indirect_scissor/bounding2.png
new file mode 100644
index 0000000..03a5b36
Binary files /dev/null and b/docs/Images/indirect_scissor/bounding2.png differ
diff --git a/docs/Images/indirect_scissor/intro.png b/docs/Images/indirect_scissor/intro.png
new file mode 100644
index 0000000..463a797
Binary files /dev/null and b/docs/Images/indirect_scissor/intro.png differ
diff --git a/docs/Images/indirect_scissor/rgb.png b/docs/Images/indirect_scissor/rgb.png
new file mode 100644
index 0000000..6c60977
Binary files /dev/null and b/docs/Images/indirect_scissor/rgb.png differ
diff --git a/docs/Images/indirect_scissor/shadows.png b/docs/Images/indirect_scissor/shadows.png
new file mode 100644
index 0000000..a8f068b
Binary files /dev/null and b/docs/Images/indirect_scissor/shadows.png differ
diff --git a/docs/setup.md b/docs/setup.md
index a954bae..2dc23eb 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -35,16 +35,17 @@ See also [build_all](https://github.com/nvpro-samples/build_all) from nvpro-samp
## Latest Vulkan SDK
This repository tries to always be up to date with the latest Vulkan SDK, therefore we suggest to download and install it.
+Version 1.2.162.0 and up has ray tracing extensions support.
**Vulkan SDK**: https://vulkan.lunarg.com/sdk/home
-## Beta Installation
+## Driver
-KHR ray tracing is still in Beta, therefore you will need the latest
-Vulkan driver.
+NVIDIA driver 160.0 and up support Vulkan ray tracing.
-**Latest driver**: https://developer.nvidia.com/vulkan-driver
+* Standard driver: https://www.nvidia.com/Download/index.aspx
+* Vulkan beta driver: https://developer.nvidia.com/vulkan-driver
## CMake
diff --git a/docs/vkrt_tuto_indirect_scissor.md.htm b/docs/vkrt_tuto_indirect_scissor.md.htm
new file mode 100644
index 0000000..3892bc8
--- /dev/null
+++ b/docs/vkrt_tuto_indirect_scissor.md.htm
@@ -0,0 +1,1505 @@
+
+**NVIDIA Vulkan Ray Tracing Tutorial**
+**Trace Rays Indirect**
+
+Authors: David Zhao Akeley
+
+
+
+This is an extension of the [Vulkan ray tracing tutorial](vkrt_tutorial.md.htm).
+
+We will discuss the `vkCmdTraceRaysIndirectKHR` command, which allows the
+`width`, `height`, and `depth` of a trace ray command to be specifed by a
+buffer on the device, rather than directly by the host. As a demonstration,
+this example will add colorful lanterns to the scene that add their own light
+and shadows, with a finite radius of effect. A compute shader will calculate
+scissor rectangles for each lantern, and an indirect trace rays command will
+dispatch rays for lanterns only within those scissor rectangles.
+
+# Outline
+
+The basic idea is to split up ray tracing into seperate passes. The first pass
+is similar to the original tutorial: it fills in the entire output image,
+calculating lighting from the main light in the scene. Subsequently, one
+pass is run within a scissor rectangle for each lantern to add its light
+contribution to the output image.
+
+The steps to accomplish this are:
+
+* Add a buffer to store lantern positions, colors, and scissor rectangles.
+ These lanterns are separate from the OBJ geometry loaded in the main
+ tutorial. Run a compute shader each frame to fill in the scissor rectangles.
+
+* Build a BLAS for a lantern, and add lantern instances to the TLAS.
+ As lanterns are self-illuminating, the closest hit shader used to shade
+ ordinary OBJ geometry is inappropriate, so, we will add a new hit group (new
+ closest-hit shader) for lanterns to the SBT, and set `hitGroupId`
+ for lantern instances to `1` so this hit group is used.
+
+* Modify the ray generation shader so that it emulates additive blending,
+ and supports drawing within scissor rectangles (in the main tutorial,
+ the raygen shader assumes the ray trace dispatch covers the whole screen).
+
+* Add shadow rays in lantern passes, cast towards the lantern whose light
+ contribution is being added in the current pass. To detect whether the
+ expected lantern was hit, we add a new miss shader and two new closest
+ hit shaders (one for OBJ instances, one for lanterns) that return the
+ index of the lantern hit (if any).
+
+* Add one `vkCmdTraceRaysIndirectKHR` call in `HelloVulkan::raytrace` for
+ each lantern in the scene.
+
+If everything goes well, we should see something like this (the "lantern debug"
+checkbox enables visualizing the scissor rectangles).
+
+
+
+# Lantern Scissor Rectangles
+
+In this step we set up the buffer storing lantern info, and a compute shader
+for calculating the scissor rectangles.
+
+## Allocate Host Storage
+
+We first need to stage the vector of lanterns on the host in a vector.
+Since this is not an animation example, we won't be concerned with
+keeping track of changes to this vector: changes are forbidden after the
+acceleration structures are built.
+
+In `hello_vulkan.h`, declare a struct for holding information about
+the lanterns on the host.
+
+```` C
+ // Information on each colored lantern illuminating the scene.
+ struct Lantern
+ {
+ nvmath::vec3f position;
+ nvmath::vec3f color;
+ float brightness;
+ float radius; // Max world-space distance that light illuminates.
+ };
+````
+
+Then declare a vector of `Lantern` and add
+a new function for configuring a new lantern in the scene.
+
+```` C
+ // Array of lanterns in scene. Not modifiable after acceleration structure build.
+ std::vector m_lanterns;
+ void addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius);
+````
+
+The `addLantern` function is implemented as
+
+```` C
+// Add a light-emitting colored lantern to the scene. May only be called before TLAS build.
+void HelloVulkan::addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius)
+{
+ assert(m_lanternCount == 0); // Indicates TLAS build has not happened yet.
+
+ m_lanterns.push_back({pos, color, brightness, radius});
+}
+````
+
+In `main.cpp`, we insert calls for adding some lanterns.
+
+```` C
+ // Creation of the example
+ helloVk.loadModel(nvh::findFile("media/scenes/Medieval_building.obj", defaultSearchPaths, true));
+ helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true));
+ helloVk.addLantern({ 8.000f, 1.100f, 3.600f}, {1.0f, 0.0f, 0.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 8.000f, 0.600f, 3.900f}, {0.0f, 1.0f, 0.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 8.000f, 1.100f, 4.400f}, {0.0f, 0.0f, 1.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 1.730f, 1.812f, -1.604f}, {0.0f, 0.4f, 0.4f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 1.730f, 1.862f, 1.916f}, {0.0f, 0.2f, 0.4f}, 0.3f, 3.0f);
+ helloVk.addLantern({-2.000f, 1.900f, -0.700f}, {0.8f, 0.8f, 0.6f}, 0.4f, 3.9f);
+ helloVk.addLantern({ 0.100f, 0.080f, -2.392f}, {1.0f, 0.0f, 1.0f}, 0.5f, 5.0f);
+ helloVk.addLantern({ 1.948f, 0.080f, 0.598f}, {1.0f, 1.0f, 1.0f}, 0.6f, 6.0f);
+ helloVk.addLantern({-2.300f, 0.080f, 2.100f}, {0.0f, 0.7f, 0.0f}, 0.6f, 6.0f);
+ helloVk.addLantern({-1.400f, 4.300f, 0.150f}, {1.0f, 1.0f, 0.0f}, 0.7f, 7.0f);
+````
+
+## Lantern Device Storage
+
+In `hello_vulkan.h`, declare a struct for storing lanterns on the device.
+This includes the host information, plus a scissor rectangle.
+```` C
+ // Information on each colored lantern, plus the info needed for dispatching the
+ // indirect ray trace command used to add its brightness effect.
+ // The dispatched ray trace covers pixels (offsetX, offsetY) to
+ // (offsetX + indirectCommand.width - 1, offsetY + indirectCommand.height - 1).
+ struct LanternIndirectEntry
+ {
+ // Filled in by the device using a compute shader.
+ // NOTE: I rely on indirectCommand being the first member.
+ VkTraceRaysIndirectCommandKHR indirectCommand;
+ int32_t offsetX;
+ int32_t offsetY;
+
+ // Filled in by the host.
+ Lantern lantern;
+ };
+````
+
+!!! NOTE
+ `VkTraceRaysIndirectCommandKHR` is just a struct of 3 `int32_t` defining
+ the `width`, `height`, `depth` of a trace ray command.
+
+
+We also declare an equivalent structure for shaders in the file
+`LanternIndirectEntry.glsl`. We avoid using `vec3` due to differences
+in alignment in C++ and GLSL.
+```` C
+struct LanternIndirectEntry
+{
+ // VkTraceRaysIndirectCommandKHR
+ int indirectWidth;
+ int indirectHeight;
+ int indirectDepth;
+
+ // Pixel coordinate of scissor rect upper-left.
+ int offsetX;
+ int offsetY;
+
+ // Lantern starts here:
+ // Can't use vec3 due to alignment.
+ float x, y, z;
+ float red, green, blue;
+ float brightness;
+ float radius;
+};
+````
+
+To store the lanterns on the device, declare the Vulkan buffer of `LanternIndirectEntry`
+in `hello_vulkan.h`
+
+```` C
+ // Buffer to source vkCmdTraceRaysIndirectKHR indirect parameters and lantern color,
+ // position, etc. from when doing lantern lighting passes.
+ nvvk::Buffer m_lanternIndirectBuffer;
+ VkDeviceSize m_lanternCount = 0; // Set to actual lantern count after TLAS build, as
+ // that is the point no more lanterns may be added.
+````
+
+and fill it with a `createLanternIndirectBuffer` function. For performance,
+we allocate a device-local buffer. We need usage flags for
+
+* Storage buffer use, so the compute shader can write to it.
+* Indirect buffer use, so we can source indirect parameters from it when dispatching a ray trace.
+* Device address use, as `vkCmdTraceRaysIndirectKHR` expects a device address.
+* Transfer dst use, so the buffer can be initialized with the lantern colors and positions from `m_lanterns`.
+
+```` C
+// Allocate the buffer used to pass lantern info + ray trace indirect parameters to ray tracer.
+// Fill in the lantern info from m_lanterns (indirect info is filled per-frame on device
+// using a compute shader). Must be called only after TLAS build.
+//
+// The buffer is an array of LanternIndirectEntry, entry i is for m_lanterns[i].
+void HelloVulkan::createLanternIndirectBuffer()
+{
+ assert(m_lanternCount > 0);
+ assert(m_lanternCount == m_lanterns.size());
+
+ // m_alloc behind the scenes uses cmdBuf to transfer data to the buffer.
+ nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex);
+ vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();
+
+ using Usage = vk::BufferUsageFlagBits;
+ m_lanternIndirectBuffer =
+ m_alloc.createBuffer(sizeof(LanternIndirectEntry) * m_lanternCount,
+ Usage::eIndirectBuffer | Usage::eTransferDst
+ | Usage::eShaderDeviceAddress | Usage::eStorageBuffer,
+ vk::MemoryPropertyFlagBits::eDeviceLocal);
+
+ std::vector entries(m_lanternCount);
+ for (size_t i = 0; i < m_lanternCount; ++i) entries[i].lantern = m_lanterns[i];
+ cmdBuf.updateBuffer(m_lanternIndirectBuffer.buffer, 0, entries.size() * sizeof entries[0], entries.data());
+
+ cmdBufGet.submitAndWait(cmdBuf);
+}
+````
+
+Call this function in `main.cpp`, after the AS build (the AS build will be modified later).
+
+```` C
+ helloVk.initRayTracing();
+ helloVk.createBottomLevelAS();
+ helloVk.createTopLevelAS();
+ helloVk.createLanternIndirectBuffer();
+````
+
+## Set up Compute Shader
+
+The compute shader will need the view and projection matrices, plus the Z near plane,
+screen dimensions, and length of the `LanternIndirectBuffer` array, in order to
+compute the scissor rectangles. Feed all of this in with a push constant, declared
+in `hello_vulkan.h`
+
+```` C
+ // Push constant for compute shader filling lantern indirect buffer.
+ // Barely fits in 128-byte push constant limit guaranteed by spec.
+ struct LanternIndirectPushConstants
+ {
+ nvmath::vec4 viewRowX; // First 3 rows of view matrix.
+ nvmath::vec4 viewRowY; // Set w=1 implicitly in shader.
+ nvmath::vec4 viewRowZ;
+
+ nvmath::mat4 proj; // Perspective matrix
+ float nearZ; // Near plane used to create projection matrix.
+
+ // Pixel dimensions of output image (needed to scale NDC to screen coordinates).
+ int32_t screenX;
+ int32_t screenY;
+
+ // Length of the LanternIndirectEntry array.
+ int32_t lanternCount;
+ } m_lanternIndirectPushConstants;
+````
+
+!!! NOTE Push Constant Limit
+ The Vulkan spec only guarantees 128 bytes of push constant, so to make everything
+ fit, we have to chop off the implicit bottom row of the view matrix.
+
+This push constant is consumed in the compute shader `shaders/lanternIndirect.comp`.
+We go through `lanternCount` iterations of a loop that fill in the scissor
+rectangle for each `LanternIndirectEntry` (splitting the work among 128
+invocations of the work group).
+
+```` C
+#version 460
+#extension GL_GOOGLE_include_directive : enable
+
+// Compute shader for filling in raytrace indirect parameters for each lantern
+// based on the current camera position (passed as view and proj matrix in
+// push constant).
+//
+// Designed to be dispatched with only one work group; it alone fills in
+// the entire lantern array (of length lanternCount, in also push constant).
+
+#define LOCAL_SIZE 128
+layout(local_size_x = LOCAL_SIZE, local_size_y = 1, local_size_z = 1) in;
+
+#include "LanternIndirectEntry.glsl"
+
+layout(binding = 0, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+layout(push_constant) uniform Constants
+{
+ vec4 viewRowX;
+ vec4 viewRowY;
+ vec4 viewRowZ;
+ mat4 proj;
+ float nearZ;
+ int screenX;
+ int screenY;
+ int lanternCount;
+}
+pushC;
+
+// Copy the technique of "2D Polyhedral Bounds of a Clipped,
+// Perspective-Projected 3D Sphere" M. Mara M. McGuire
+// http://jcgt.org/published/0002/02/05/paper.pdf
+// to compute a screen-space rectangle covering the given Lantern's
+// light radius-of-effect. Result is in screen (pixel) coordinates.
+void getScreenCoordBox(in LanternIndirectEntry lantern, out ivec2 lower, out ivec2 upper);
+
+// Use the xyz and radius of lanterns[i] plus the transformation matrices
+// in pushC to fill in the offset and indirect parameters of lanterns[i]
+// (defines the screen rectangle that this lantern's light is bounded in).
+void fillIndirectEntry(int i)
+{
+ LanternIndirectEntry lantern = lanterns.lanterns[i];
+ ivec2 lower, upper;
+ getScreenCoordBox(lantern, lower, upper);
+
+ lanterns.lanterns[i].indirectWidth = max(0, upper.x - lower.x);
+ lanterns.lanterns[i].indirectHeight = max(0, upper.y - lower.y);
+ lanterns.lanterns[i].indirectDepth = 1;
+ lanterns.lanterns[i].offsetX = lower.x;
+ lanterns.lanterns[i].offsetY = lower.y;
+}
+
+void main()
+{
+ for (int i = int(gl_LocalInvocationID.x); i < pushC.lanternCount; i += LOCAL_SIZE)
+ {
+ fillIndirectEntry(i);
+ }
+}
+
+/** Center is in camera space */
+void getBoundingBox(
+ in vec3 center,
+ in float radius,
+ in float nearZ,
+ in mat4 projMatrix,
+ out vec2 ndc_low,
+ out vec2 ndc_high) {
+````
+!!! TIP
+ Omitted code for computing scissor rectangles, taken from "2D Polyhedral Bounds of a Clipped,
+ Perspective-Projected 3D Sphere" by Michael Mara and Morgan McGuire.
+ http://jcgt.org/published/0002/02/05/paper.pdf
+```` C
+}
+
+void getScreenCoordBox(in LanternIndirectEntry lantern, out ivec2 lower, out ivec2 upper)
+{
+ vec4 lanternWorldCenter = vec4(lantern.x, lantern.y, lantern.z, 1);
+ vec3 center = vec3(
+ dot(pushC.viewRowX, lanternWorldCenter),
+ dot(pushC.viewRowY, lanternWorldCenter),
+ dot(pushC.viewRowZ, lanternWorldCenter));
+ vec2 ndc_low, ndc_high;
+ float paperNearZ = -abs(pushC.nearZ); // Paper expected negative nearZ, took 2 days to figure out!
+ getBoundingBox(center, lantern.radius, paperNearZ, pushC.proj, ndc_low, ndc_high);
+
+ // Convert NDC [-1,+1]^2 coordinates to screen coordinates, and clamp to stay in bounds.
+
+ lower.x = clamp(int((ndc_low.x * 0.5 + 0.5) * pushC.screenX), 0, pushC.screenX);
+ lower.y = clamp(int((ndc_low.y * 0.5 + 0.5) * pushC.screenY), 0, pushC.screenY);
+ upper.x = clamp(int((ndc_high.x * 0.5 + 0.5) * pushC.screenX), 0, pushC.screenX);
+ upper.y = clamp(int((ndc_high.y * 0.5 + 0.5) * pushC.screenY), 0, pushC.screenY);
+}
+````
+
+Now we just have to fill out the usual boilerplate for setting up the descriptor
+set (passes the `LanternIndirectEntry` array) and compute pipeline. We only have
+to allocate one descriptor as the `LanternIndirectEntry` array never changes.
+
+`hello_vulkan.h`:
+
+```` C
+ nvvk::DescriptorSetBindings m_lanternIndirectDescSetLayoutBind;
+ vk::DescriptorPool m_lanternIndirectDescPool;
+ vk::DescriptorSetLayout m_lanternIndirectDescSetLayout;
+ vk::DescriptorSet m_lanternIndirectDescSet;
+ vk::PipelineLayout m_lanternIndirectCompPipelineLayout;
+ vk::Pipeline m_lanternIndirectCompPipeline;
+````
+
+`hello_vulkan.cpp`:
+
+```` C
+//--------------------------------------------------------------------------------------------------
+// The compute shader just needs read/write access to the buffer of LanternIndirectEntry.
+void HelloVulkan::createLanternIndirectDescriptorSet()
+{
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+ using vkDSLB = vk::DescriptorSetLayoutBinding;
+
+ // Lantern buffer (binding = 0)
+ m_lanternIndirectDescSetLayoutBind.addBinding( //
+ vkDSLB(0, vkDT::eStorageBuffer, 1, vkSS::eCompute));
+
+ m_lanternIndirectDescPool = m_lanternIndirectDescSetLayoutBind.createPool(m_device);
+ m_lanternIndirectDescSetLayout = m_lanternIndirectDescSetLayoutBind.createLayout(m_device);
+ m_lanternIndirectDescSet =
+ m_device.allocateDescriptorSets({m_lanternIndirectDescPool, 1, &m_lanternIndirectDescSetLayout})[0];
+
+ assert(m_lanternIndirectBuffer.buffer);
+ vk::DescriptorBufferInfo lanternBufferInfo{
+ m_lanternIndirectBuffer.buffer, 0, m_lanternCount * sizeof(LanternIndirectEntry)};
+
+ std::vector writes;
+ writes.emplace_back(m_lanternIndirectDescSetLayoutBind.makeWrite(m_lanternIndirectDescSet, 0, &lanternBufferInfo));
+ m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr);
+}
+
+// Create compute pipeline used to fill m_lanternIndirectBuffer with parameters
+// for dispatching the correct number of ray traces.
+void HelloVulkan::createLanternIndirectCompPipeline()
+{
+ // Compile compute shader and package as stage.
+ vk::ShaderModule computeShader =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternIndirect.comp.spv", true, defaultSearchPaths, true));
+ vk::PipelineShaderStageCreateInfo stageInfo;
+ stageInfo.setStage(vk::ShaderStageFlagBits::eCompute);
+ stageInfo.setModule(computeShader);
+ stageInfo.setPName("main");
+
+ // Set up push constant and pipeline layout.
+ constexpr auto pushSize = sizeof(m_lanternIndirectPushConstants);
+ vk::PushConstantRange pushCRange = {vk::ShaderStageFlagBits::eCompute, 0, pushSize};
+ static_assert(pushSize <= 128, "Spec guarantees only 128 byte push constant");
+ vk::PipelineLayoutCreateInfo layoutInfo;
+ layoutInfo.setSetLayoutCount(1);
+ layoutInfo.setPSetLayouts(&m_lanternIndirectDescSetLayout);
+ layoutInfo.setPushConstantRangeCount(1);
+ layoutInfo.setPPushConstantRanges(&pushCRange);
+ m_lanternIndirectCompPipelineLayout = m_device.createPipelineLayout(layoutInfo);
+
+ // Create compute pipeline.
+ vk::ComputePipelineCreateInfo pipelineInfo;
+ pipelineInfo.setStage(stageInfo);
+ pipelineInfo.setLayout(m_lanternIndirectCompPipelineLayout);
+ m_lanternIndirectCompPipeline = static_cast(m_device.createComputePipeline({}, pipelineInfo));
+
+ m_device.destroy(computeShader);
+}
+````
+
+`main.cpp` (add after indirect buffer initialization).
+
+```` C
+ // #VKRay
+ helloVk.initRayTracing();
+ helloVk.createBottomLevelAS();
+ helloVk.createTopLevelAS();
+ helloVk.createLanternIndirectBuffer();
+ helloVk.createRtDescriptorSet();
+ helloVk.createRtPipeline();
+ helloVk.createLanternIndirectDescriptorSet();
+ helloVk.createLanternIndirectCompPipeline();
+````
+
+## Call Compute Shader
+
+In `HelloVulkan::raytrace`, we have to fill in the earlier push constant and
+dispatch the compute shader before moving
+on to the actual ray tracing. This is rather verbose due to the need for a
+pipeline barrier synchronizing access to the `LanternIndirectEntry` array
+between the compute shader and indirect draw stages.
+
+```` C
+void HelloVulkan::raytrace(const vk::CommandBuffer& cmdBuf, const nvmath::vec4f& clearColor)
+{
+ // Before tracing rays, we need to dispatch the compute shaders that
+ // fill in the ray trace indirect parameters for each lantern pass.
+
+ // First, barrier before, ensure writes aren't visible to previous frame.
+ vk::BufferMemoryBarrier bufferBarrier;
+ bufferBarrier.setSrcAccessMask(vk::AccessFlagBits::eIndirectCommandRead);
+ bufferBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderWrite);
+ bufferBarrier.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ bufferBarrier.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ bufferBarrier.setBuffer(m_lanternIndirectBuffer.buffer);
+ bufferBarrier.offset = 0;
+ bufferBarrier.size = m_lanternCount * sizeof m_lanterns[0];
+ cmdBuf.pipelineBarrier( //
+ vk::PipelineStageFlagBits::eDrawIndirect, //
+ vk::PipelineStageFlagBits::eComputeShader,//
+ vk::DependencyFlags(0), //
+ {}, {bufferBarrier}, {});
+
+ // Bind compute shader, update push constant and descriptors, dispatch compute.
+ cmdBuf.bindPipeline(vk::PipelineBindPoint::eCompute, m_lanternIndirectCompPipeline);
+ nvmath::mat4 view = getViewMatrix();
+ m_lanternIndirectPushConstants.viewRowX = view.row(0);
+ m_lanternIndirectPushConstants.viewRowY = view.row(1);
+ m_lanternIndirectPushConstants.viewRowZ = view.row(2);
+ m_lanternIndirectPushConstants.proj = getProjMatrix();
+ m_lanternIndirectPushConstants.nearZ = nearZ;
+ m_lanternIndirectPushConstants.screenX = m_size.width;
+ m_lanternIndirectPushConstants.screenY = m_size.height;
+ m_lanternIndirectPushConstants.lanternCount = m_lanternCount;
+ cmdBuf.pushConstants(
+ m_lanternIndirectCompPipelineLayout,
+ vk::ShaderStageFlagBits::eCompute,
+ 0, m_lanternIndirectPushConstants);
+ cmdBuf.bindDescriptorSets(
+ vk::PipelineBindPoint::eCompute, m_lanternIndirectCompPipelineLayout, 0, {m_lanternIndirectDescSet}, {});
+ cmdBuf.dispatch(1, 1, 1);
+
+ // Ensure compute results are visible when doing indirect ray trace.
+ bufferBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
+ bufferBarrier.setDstAccessMask(vk::AccessFlagBits::eIndirectCommandRead);
+ cmdBuf.pipelineBarrier( //
+ vk::PipelineStageFlagBits::eComputeShader, //
+ vk::PipelineStageFlagBits::eDrawIndirect, //
+ vk::DependencyFlags(0), //
+ {}, {bufferBarrier}, {});
+
+
+ // Now move on to the actual ray tracing.
+ m_debug.beginLabel(cmdBuf, "Ray trace");
+````
+
+!!! TIP `eDrawIndirect`
+ `vk::PipelineStageFlagBits::eDrawIndirect` (`VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT`)
+ covers the stage that sources indirect paramaters for compute and ray trace
+ indirect commands, not just graphics draw indirect commands.
+
+Since the near plane and view/projection matrices are used in multiple places now,
+they were factored out to common code in `hello_vulkan.h`.
+
+```` C
+ nvmath::mat4 getViewMatrix()
+ {
+ return CameraManip.getMatrix();
+ }
+
+ static constexpr float nearZ = 0.1f;
+ nvmath::mat4 getProjMatrix()
+ {
+ const float aspectRatio = m_size.width / static_cast(m_size.height);
+ return nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, nearZ, 1000.0f);
+ }
+````
+
+The function for updating the uniform buffer is tweaked to match.
+
+```` C
+void HelloVulkan::updateUniformBuffer(const vk::CommandBuffer& cmdBuf)
+{
+ const float aspectRatio = m_size.width / static_cast(m_size.height);
+
+ CameraMatrices ubo = {};
+ ubo.view = getViewMatrix();
+ ubo.proj = getProjMatrix();
+````
+
+# Lantern Acceleration Structures and Closest Hit Shader
+
+## Bottom-level Acceleration Structure
+
+Lanterns will be drawn as spheres approximated by a triangular mesh. Declare
+in `hello_vulkan.h` functions for generating this mesh, and declare Vulkan
+buffers for storing the mesh's positions and indices, and a `BlasInput`
+for delivering this sphere mesh to the BLAS builder.
+
+```` C
+private:
+ void fillLanternVerts(std::vector& vertices, std::vector& indices);
+ void createLanternModel();
+
+ // Used to store lantern model, generated at runtime.
+ const float m_lanternModelRadius = 0.125;
+ nvvk::Buffer m_lanternVertexBuffer;
+ nvvk::Buffer m_lanternIndexBuffer;
+ nvvk::RaytracingBuilderKHR::BlasInput m_lanternBlasInput{};
+
+ // Index of lantern's BLAS in the BLAS array stored in m_rtBuilder.
+ size_t m_lanternBlasId;
+````
+
+In order to focus on the ray tracing, I omit the code for generating those vertex and index
+buffers. The relevent code in `HelloVulkan::createLanternModel` for creating the `BlasInput` is
+
+```` C
+ // Package vertex and index buffers as BlasInput.
+ vk::DeviceAddress vertexAddress = m_device.getBufferAddress({m_lanternVertexBuffer.buffer});
+ vk::DeviceAddress indexAddress = m_device.getBufferAddress({m_lanternIndexBuffer.buffer});
+
+ uint32_t maxPrimitiveCount = uint32_t(indices.size() / 3);
+
+ // Describe buffer as packed array of float vec3.
+ vk::AccelerationStructureGeometryTrianglesDataKHR triangles;
+ triangles.setVertexFormat(vk::Format::eR32G32B32Sfloat); // vec3 vertex position data.
+ triangles.setVertexData(vertexAddress);
+ triangles.setVertexStride(sizeof(nvmath::vec3f));
+ // Describe index data (32-bit unsigned int)
+ triangles.setIndexType(vk::IndexType::eUint32);
+ triangles.setIndexData(indexAddress);
+ // Indicate identity transform by setting transformData to null device pointer.
+ triangles.setTransformData({});
+ triangles.setMaxVertex(vertices.size());
+
+ // Identify the above data as containing opaque triangles.
+ vk::AccelerationStructureGeometryKHR asGeom;
+ asGeom.setGeometryType(vk::GeometryTypeKHR::eTriangles);
+ asGeom.setFlags(vk::GeometryFlagBitsKHR::eOpaque);
+ asGeom.geometry.setTriangles(triangles);
+
+ // The entire array will be used to build the BLAS.
+ vk::AccelerationStructureBuildRangeInfoKHR offset;
+ offset.setFirstVertex(0);
+ offset.setPrimitiveCount(maxPrimitiveCount);
+ offset.setPrimitiveOffset(0);
+ offset.setTransformOffset(0);
+
+ // Our blas is made from only one geometry, but could be made of many geometries
+ m_lanternBlasInput.asGeometry.emplace_back(asGeom);
+ m_lanternBlasInput.asBuildOffsetInfo.emplace_back(offset);
+````
+
+The principle difference from before is that the vertex array is now a packed array of
+float 3-vectors, hence, we call `triangles.setVertexStride(sizeof(nvmath::vec3f));`.
+
+Then, we add a call to create a lantern model and add the lantern model to the list of
+BLAS to build in `HelloVulkan::createBottomLevelAS`. Since we'll need the index of
+the lantern BLAS later to add lantern instances in the TLAS build, store the
+BLAS index for the lantern in `m_lanternBlasId`.
+
+```` C
+// Build the array of BLAS in m_rtBuilder. There are `m_objModel.size() + 1`-many BLASes.
+// The first `m_objModel.size()` are used for OBJ model BLASes, and the last one
+// is used for the lanterns (model generated at runtime).
+void HelloVulkan::createBottomLevelAS()
+{
+ // BLAS - Storing each primitive in a geometry
+ std::vector allBlas;
+ allBlas.reserve(m_objModel.size() + 1);
+
+ // Add OBJ models.
+ for(const auto& obj : m_objModel)
+ {
+ auto blas = objectToVkGeometryKHR(obj);
+
+ // We could add more geometry in each BLAS, but we add only one for now
+ allBlas.emplace_back(blas);
+ }
+
+ // Add lantern model.
+ createLanternModel();
+ m_lanternBlasId = allBlas.size();
+ allBlas.emplace_back(m_lanternBlasInput);
+
+ m_rtBuilder.buildBlas(allBlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
+}
+````
+
+## Top-level acceleration structure
+
+In the TLAS build function, we add a loop for adding each lantern instance. This is also
+the point that the lanterns are set-in-stone (no more modifications to `m_lanterns`), so
+write `m_lanternCount`.
+
+```` C
+// Build the TLAS in m_rtBuilder. Requires that the BLASes were already built and
+// that all ObjInstance and lanterns have been added. One instance with hitGroupId=0
+// is created for every OBJ instance, and one instance with hitGroupId=1 for each lantern.
+//
+// gl_InstanceCustomIndexEXT will be the index of the instance or lantern in m_objInstance or
+// m_lanterns respectively.
+void HelloVulkan::createTopLevelAS()
+{
+ assert(m_lanternCount == 0);
+ m_lanternCount = m_lanterns.size();
+
+ std::vector tlas;
+ tlas.reserve(m_objInstance.size() + m_lanternCount);
+
+ // Add the OBJ instances.
+ for(int i = 0; i < static_cast(m_objInstance.size()); i++)
+ {
+ nvvk::RaytracingBuilderKHR::Instance rayInst;
+ rayInst.transform = m_objInstance[i].transform; // Position of the instance
+ rayInst.instanceCustomId = i; // gl_InstanceCustomIndexEXT
+ rayInst.blasId = m_objInstance[i].objIndex;
+ rayInst.hitGroupId = 0; // We will use the same hit group for all OBJ
+ rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+ tlas.emplace_back(rayInst);
+ }
+
+ // Add lantern instances.
+ for(int i = 0; i < static_cast(m_lanterns.size()); ++i)
+ {
+ nvvk::RaytracingBuilderKHR::Instance lanternInstance;
+ lanternInstance.transform = nvmath::translation_mat4(m_lanterns[i].position);
+ lanternInstance.instanceCustomId = i;
+ lanternInstance.blasId = m_lanternBlasId;
+ lanternInstance.hitGroupId = 1; // Next hit group is for lanterns.
+ lanternInstance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+ tlas.emplace_back(lanternInstance);
+ }
+
+ m_rtBuilder.buildTlas(tlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
+}
+````
+
+The principle differences are:
+
+* `instanceCustomId` is set to the index of the lantern in `m_lanternIndirectBuffer`, so we
+ can look up the lantern color in the forthcoming closest hit shader.
+
+* `hitGroupId` is set to `1`, so that lanterns will use a new closest hit shader instead
+ of the old one for OBJs.
+
+!!! TIP Helper Reminders
+ `instanceCustomId` corresponds to `VkAccelerationStructureInstanceKHR::instanceCustomIndex` in host
+ code and `gl_InstanceCustomIndexEXT` in shader code.
+
+ `hitGroupId` corresponds to `VkAccelerationStructureInstanceKHR::instanceShaderBindingTableRecordOffset`.
+
+ `blasId` has no Vulkan equivalent; it is translated to a BLAS device address in the `m_rtBuilder` helper.
+
+## Lantern Primary Ray Closest Hit Shader
+
+We now implement the closest hit shader for lanterns hit by primary rays (rays
+cast starting from the eye). First, we need to do a bit of preparation:
+
+* Add a bool to `hitPayload` to control whether additive blending is enabled or
+ not. The lanterns will be drawn at a constant brightness, so additive blending
+ is enabled for rays hitting OBJ instances and disabled for rays hitting lanterns.
+ The raygen shader will be updated later to take this bool into account.
+
+* Access the GLSL definition of `LanternIndirectEntry` so we can look up the lantern color.
+
+* Add a descriptor set to the raytrace pipeline to deliver the
+
+We do the first two tasks in `raycommon.glsl`.
+
+```` C
+#include "LanternIndirectEntry.glsl"
+
+struct hitPayload
+{
+ vec3 hitValue;
+ bool additiveBlending;
+};
+````
+
+The last task is done in `HelloVulkan::createRtDescriptorSet`
+
+```` C
+// This descriptor set holds the Acceleration structure, output image, and lanterns array buffer.
+//
+void HelloVulkan::createRtDescriptorSet()
+{
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+ using vkDSLB = vk::DescriptorSetLayoutBinding;
+
+ // ...
+
+ // Lantern buffer (binding = 2)
+ m_rtDescSetLayoutBind.addBinding( //
+ vkDSLB(2, vkDT::eStorageBuffer, 1, vkSS::eRaygenKHR | vkSS::eClosestHitKHR));
+ assert(m_lanternCount > 0);
+
+ // ...
+
+ std::vector writes;
+
+ // ...
+
+ writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &lanternBufferInfo));
+ m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr);
+}
+````
+
+Now we can implement the new closest hit shader. Name this shader `lantern.rchit`.
+
+```` C
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_EXT_nonuniform_qualifier : enable
+#extension GL_EXT_scalar_block_layout : enable
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// Closest hit shader invoked when a primary ray hits a lantern.
+
+// clang-format off
+layout(location = 0) rayPayloadInEXT hitPayload prd;
+
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+// clang-format on
+
+void main()
+{
+ // Just look up this lantern's color. Self-illuminating, so no lighting calculations.
+ LanternIndirectEntry lantern = lanterns.lanterns[nonuniformEXT(gl_InstanceCustomIndexEXT)];
+ prd.hitValue = vec3(lantern.red, lantern.green, lantern.blue);
+ prd.additiveBlending = false;
+}
+````
+
+This shader is fairly simple, we just had to look up the lantern color and return it in the
+payload. Here, we used the fact that in the TLAS instances setup, we set a lantern instance's
+`gl_InstanceCustomIndexEXT` to its position in the lanterns array.
+
+Now we just have to add the new hit group to the pipeline. This is more of the same,
+in the `HelloVulkan::createRtPipeline` function, we add the lantern closest hit
+group after the OBJ hit group, to match the `hitGroupId`s assigned earlier in the
+TLAS build.
+
+```` C
+ // OBJ Primary Ray Hit Group - Closest Hit + AnyHit (not used)
+ vk::ShaderModule chitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/raytrace.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR hg{vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, chitSM, "main"});
+ hg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(hg);
+
+ // Lantern Primary Ray Hit Group
+ vk::ShaderModule lanternChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lantern.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternChitSM, "main"});
+ lanternHg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(lanternHg);
+
+ // ...
+
+ m_device.destroy(lanternChitSM);
+````
+
+We don't have to modify `HelloVulkan::createRtShaderBindingTable`. Changes to the number of
+group handles to copy into the SBT are picked up automatically from `m_rtShaderGroups.size()`.
+
+# Ray Generation Shader
+
+## Draw Within Scissor Rectangle
+
+The original ray generation shader assumed that `gl_LaunchSizeEXT` is the size of the entire
+screen. As this is no longer the case for scissor rectangles, we communicate the screen
+size through push constant instead. In addition, we also add to the push constants a number
+indicating which lantern pass is currently being drawn (-1 for the original full screen pass).
+
+Modify `m_rtPushConstants` in `hello_vulkan.h`.
+
+```` C
+ // Push constant for ray trace pipeline.
+ struct RtPushConstant
+ {
+ // Background color
+ nvmath::vec4f clearColor;
+
+ // Information on the light in the sky used when lanternPassNumber = -1.
+ nvmath::vec3f lightPosition;
+ float lightIntensity;
+ int32_t lightType;
+
+ // -1 if this is the full-screen pass. Otherwise, this pass is to add light
+ // from lantern number lanternPassNumber. We use this to lookup trace indirect
+ // parameters in m_lanternIndirectBuffer.
+ int32_t lanternPassNumber;
+
+ // Pixel dimensions of the output image.
+ int32_t screenX;
+ int32_t screenY;
+
+ // See m_lanternDebug.
+ int32_t lanternDebug;
+ } m_rtPushConstants;
+````
+
+We also update the GLSL push constant to match. Since the raygen shader now needs
+access to the push constant, move the push constant definition from `raytrace.rchit`
+to `raycommon.glsl`.
+
+```` C
+layout(push_constant) uniform Constants
+{
+ vec4 clearColor;
+ vec3 lightPosition;
+ float lightIntensity;
+ int lightType; // 0: point, 1: infinite
+ int lanternPassNumber; // -1 if this is the full-screen pass. Otherwise, used to lookup trace indirect parameters.
+ int screenX;
+ int screenY;
+ int lanternDebug;
+}
+pushC;
+````
+
+(`lanternDebug` will be used later to toggle visualising the scissor rectangles)
+
+
+This move also requires us to tweak `raytrace.rmiss`.
+
+````
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+layout(location = 0) rayPayloadInEXT hitPayload prd;
+
+void main()
+{
+ prd.hitValue = pushC.clearColor.xyz * 0.8;
+ prd.additiveBlending = false;
+}
+````
+
+We will cover initializing the new push constants later, when we look at `vkCmdTraceRaysIndirectKHR`.
+
+In `raytrace.rgen`, we have to replace the old code for calculating the pixel center.
+
+```` C
+void main()
+{
+ const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
+ const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy);
+````
+
+with
+
+```` C
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+void main()
+{
+ // Global light pass is a full screen rectangle (lower corner 0,0), but
+ // lantern passes are only run within rectangles that may be offset.
+ ivec2 pixelOffset = ivec2(0);
+ if (pushC.lanternPassNumber >= 0)
+ {
+ pixelOffset.x = lanterns.lanterns[pushC.lanternPassNumber].offsetX;
+ pixelOffset.y = lanterns.lanterns[pushC.lanternPassNumber].offsetY;
+ }
+
+ const ivec2 pixelIntCoord = ivec2(gl_LaunchIDEXT.xy) + pixelOffset;
+ const vec2 pixelCenter = vec2(pixelIntCoord) + vec2(0.5);
+ const vec2 inUV = pixelCenter / vec2(pushC.screenX, pushC.screenY);
+ vec2 d = inUV * 2.0 - 1.0;
+````
+
+Let's recap why this works. If `pushC.lanternPassNumber` is negative, we're drawing
+the first, full-screen pass, and this code behaves identically as before, except
+that `inUV` performs division by `(pushC.screenX, pushC.screenY)` instead of
+relying on `gl_LaunchSizeEXT` to be the screen size.
+
+Otherwise (`pushC.lanternPassNumber >= 0`), we're drawing a scissor rectangle for
+the given lantern number. Look up that lantern's `LanternIndirectEntry` in the
+array (notice that the descriptor binding for it is added). Its scissor rectangle
+is defined by:
+
+* `LanternIndirectEntry::offsetX`,`offsetY`: the pixel coordinate of the scissor box's
+ upper-left.
+
+* `LanternIndirectEntry::width`,`height`: the dimensions of the scissor box (not
+ directly used here; consumed by `vkCmdTraceRaysIndirectKHR`).
+
+The `gl_LaunchIDEXT` variable ranges from `(0,0)` to `(width-1, height-1)`, so to
+cover the correct pixels within the scissor, we just have to reposition
+`gl_LaunchIDEXT` by the offset `(offsetX, offsetY)`.
+
+## Additive Blending
+
+We also have to emulate additive blending. Instead of always writing to the output
+image:
+
+```` C
+ imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.0));
+````
+
+we do
+
+```` C
+ // Either add to or replace output image color based on prd.additiveBlending.
+ // Global pass always replaces color as it is the first pass.
+ vec3 oldColor = vec3(0);
+ if (prd.additiveBlending && pushC.lanternPassNumber >= 0) {
+ oldColor = imageLoad(image, pixelIntCoord).rgb;
+ }
+ imageStore(image, pixelIntCoord, vec4(prd.hitValue + oldColor, 1.0));
+````
+
+thus adding the ray payload's color to the old image color if `prd.additiveBlending`
+is true and this is not the first, full-screen pass (the first pass must replace the
+output image color as its existing contents are garbage).
+
+# Lantern Shadow Rays
+
+We now have to set up a system for casting shadow rays from the OBJ closest hit
+shader to the lanterns. This requires us to
+
+* Detect in `raycast.rchit` whether we are in a lantern pass, and use this
+ to decide between casting shadow rays to the main light (as in the base
+ tutorial) or casting shadow rays to a lantern.
+
+* Declare a payload for which lantern (if any) was hit, and add a new miss shader
+ and two new closest hit shaders for filling that payload.
+
+* Use the `sbtRecordOffset` parameter of `traceRayEXT` to skip over the earlier
+ hit groups.
+
+## New payload
+
+In `raytrace.rchit` (called when an OBJ instance is hit by a primary ray), declare
+the new payload and the array of lanterns.
+
+```` C
+layout(location = 2) rayPayloadEXT int hitLanternInstance;
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+````
+
+## New shaders
+
+We need a few simple shaders to report the number of the lantern hit (if any) by the shadow ray.
+First is the miss shader, `lanternShadow.rmiss`.
+
+```` C
+// Miss shader invoked when tracing shadow rays (rays towards lantern)
+// in lantern passes. Misses shouldn't really happen, but if they do,
+// report we did not hit any lantern by setting hitLanternInstance = -1.
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+void main()
+{
+ hitLanternInstance = -1;
+}
+````
+
+Then a closest hit shader for OBJ instances hit by a lantern shadow ray.
+This also returns `-1` for "no lantern". Call this `lanternShadowObj.rchit`.
+
+```` C
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// During a lantern pass, this closest hit shader is invoked when
+// shadow rays (rays towards lantern) hit a regular OBJ. Report back
+// that no lantern was hit (-1).
+
+// clang-format off
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+// clang-format on
+
+void main()
+{
+ hitLanternInstance = -1;
+}
+````
+
+Finally, a closest hit shader for lantern instances, named `lanternShadowLantern.rchit`.
+
+```` C
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// During a lantern pass, this closest hit shader is invoked when
+// shadow rays (rays towards lantern) hit a lantern. Report back
+// which lantern was hit.
+
+// clang-format off
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+// clang-format on
+
+void main()
+{
+ hitLanternInstance = gl_InstanceCustomIndexEXT;
+}
+````
+
+Note that we really need to report back the lantern number, and
+not just a boolean "lantern hit" flag. In order to have lanterns cast
+shadows on each other, we must be able to detect that the shadow ray
+hit the "wrong" lantern.
+
+
+
+## Add Shaders to Pipeline
+
+We add the new miss shader as miss shader 2 in the SBT, and the closest hit
+shaders as hit groups 2 and 3 in the SBT, following the earlier 2 hit
+groups for primary rays. Add the following code to `HelloVulkan::createRtPipeline`
+after loading `raytraceShadow.rmiss.spv`.
+
+```` C
+ // Miss shader 2 is invoked when a shadow ray for lantern lighting misses the
+ // lantern. It shouldn't be invoked, but I include it just in case.
+ vk::ShaderModule lanternmissSM = nvvk::createShaderModule(
+ m_device, nvh::loadFile("shaders/lanternShadow.rmiss.spv", true, paths, true));
+````
+
+and add this code for loading the last 2 closest hit shaders after loading
+`lantern.rchit.spv`:
+
+```` C
+ // OBJ Lantern Shadow Ray Hit Group
+ vk::ShaderModule lanternShadowObjChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternShadowObj.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternShadowObjHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowObjChitSM, "main"});
+ lanternShadowObjHg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(lanternShadowObjHg);
+
+ // Lantern Lantern Shadow Ray Hit Group
+ vk::ShaderModule lanternShadowLanternChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternShadowLantern.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternShadowLanternHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowLanternChitSM, "main"});
+ lanternShadowLanternHg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(lanternShadowLanternHg);
+````
+
+We need to destroy the added shader modules at the end of the function.
+
+```` C
+ m_device.destroy(shadowmissSM);
+ // ...
+ m_device.destroy(lanternShadowObjChitSM);
+ m_device.destroy(lanternShadowLanternChitSM);
+````
+
+Through all this, we still load shader stages in the same order as they will appear
+in the SBT in order to keep things simple (note `stages.size() - 1`). Add a comment
+at the top of this function to help us keep track of all the new shaders.
+
+```` C
+// Shader list:
+//
+// 0 ====== Ray Generation Shaders =====================================================
+//
+// Raygen shader: Ray generation shader. Casts primary rays from camera to scene.
+//
+// 1 ====== Miss Shaders ===============================================================
+//
+// Miss shader 0: Miss shader when casting primary rays. Fill in clear color.
+//
+// 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Miss shader 1: Miss shader when casting shadow rays towards main light.
+// Reports no shadow.
+//
+// 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Miss shader 2: Miss shader when casting shadow rays towards a lantern.
+// Reports no lantern hit (-1).
+//
+// 4 ====== Hit Groups for Primary Rays (sbtRecordOffset=0) ============================
+//
+// chit shader 0: Closest hit shader for primary rays hitting OBJ instances
+// (hitGroupId=0). Casts shadow ray (to sky light or to lantern,
+// depending on pass number) and returns specular
+// and diffuse light to add to output image.
+//
+// 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// chit shader 1: Closest hit shader for primary rays hitting lantern instances
+// (hitGroupId=1). Returns color value to replace the current
+// image pixel color with (lanterns are self-illuminating).
+//
+// 6 - - - - Hit Groups for Lantern Shadow Rays (sbtRecordOffset=2) - - - - - - - - - - -
+//
+// chit shader 2: Closest hit shader for OBJ instances hit when casting shadow
+// rays to a lantern. Returns -1 to report that the shadow ray
+// failed to reach the targetted lantern.
+//
+// 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// chit shader 3: Closest hit shader for lantern instances hit when casting shadow
+// rays to a lantern. Returns the gl_CustomInstanceIndexEXT [lantern
+// number] of the lantern hit.
+//
+// 8 =====================================================================================
+````
+
+## Compute Lighting Intensity
+
+Because the lanterns have color, we have to replace the scalar `lightIntensity`
+in `raytrace.rchit` with an RGB `colorIntensity`.
+
+```` C
+ // Vector toward the light
+ vec3 L;
+ vec3 colorIntensity = vec3(pushC.lightIntensity);
+ float lightDistance = 100000.0;
+````
+
+Then, we have to check if we're in a lantern pass (`lanternPassNumber >= 0`).
+If so, look up the lantern location in the `LanternIndirectEntry` array,
+and compute the light direction and intensity based on that position.
+
+```` C
+ // ray direction is towards lantern, if in lantern pass.
+ if (pushC.lanternPassNumber >= 0)
+ {
+ LanternIndirectEntry lantern = lanterns.lanterns[pushC.lanternPassNumber];
+ vec3 lDir = vec3(lantern.x, lantern.y, lantern.z) - worldPos;
+ lightDistance = length(lDir);
+ vec3 color = vec3(lantern.red, lantern.green, lantern.blue);
+ // Lantern light decreases linearly. Not physically accurate, but looks good
+ // and avoids a hard "edge" at the radius limit. Use a constant value
+ // if lantern debug is enabled to clearly see the covered screen rectangle.
+ float distanceFade =
+ pushC.lanternDebug != 0
+ ? 0.3
+ : max(0, (lantern.radius - lightDistance) / lantern.radius);
+ colorIntensity = color * lantern.brightness * distanceFade;
+ L = normalize(lDir);
+ }
+````
+
+otherwise, do the old lighting calculations, except we again have to
+replace `float lightIntensity` with `vec3 colorIntensity`.
+
+```` C
+ // Non-lantern pass may have point light...
+ else if(pushC.lightType == 0)
+ {
+ vec3 lDir = pushC.lightPosition - worldPos;
+ lightDistance = length(lDir);
+ colorIntensity = vec3(pushC.lightIntensity / (lightDistance * lightDistance));
+ L = normalize(lDir);
+ }
+ else // or directional light.
+ {
+ L = normalize(pushC.lightPosition - vec3(0));
+ }
+````
+
+!!! NOTE `lanternDebug`
+ When `lanternDebug` is on, I disable diminishing lighting with distance, so
+ that the light will reach the edge of the scissor box, making the scissor
+ box easy to see. To toggle this variable, I declare `bool m_lanternDebug`
+ in `hello_vulkan.h`, and allow ImGui to control it:
+
+ ```` C
+ void renderUI(HelloVulkan& helloVk)
+ {
+ ImGuiH::CameraWidget();
+ if(ImGui::CollapsingHeader("Light"))
+ {
+ // ...
+ ImGui::Checkbox("Lantern Debug", &helloVk.m_lanternDebug);
+ }
+ }
+ ````
+
+ Then, every frame I copy `m_lanternDebug` to the push constant. The reason
+ I cannot directly modify the push constant through ImGui is that ImGui expects
+ a `bool` (usually 8-bits) while Vulkan expects a 32-bit boolean.
+
+## Casting Lantern Shadow Rays
+
+Use an `if` to ensure the original shadow rays are cast only in the non-lantern pass.
+
+```` C
+ // Ordinary shadow from the simple tutorial.
+ if (pushC.lanternPassNumber < 0) {
+ isShadowed = true;
+ uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT
+ | gl_RayFlagsSkipClosestHitShaderEXT;
+ traceRayEXT(topLevelAS, // acceleration structure
+ flags, // rayFlags
+ 0xFF, // cullMask
+ 0, // sbtRecordOffset
+ 0, // sbtRecordStride
+ 1, // missIndex
+ origin, // ray origin
+ tMin, // ray min range
+ rayDir, // ray direction
+ tMax, // ray max range
+ 1 // payload (location = 1)
+ );
+ }
+````
+
+Otherwise, we cast a ray towards a lantern. This ray is different in that
+
+* We actually need the closest hit shaders to run to return `hitLanternInstance`,
+ so do not provide the flags
+ ` gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsSkipClosestHitShaderEXT`.
+
+* Use miss shader 2, which we added earlier.
+
+* Pass 2 as `sbtRecordOffset`, so that the closest hit shaders we just added (number 2 and 3)
+ are used when hitting OBJ instances (`hitGroupId=0`) and lantern instances (`hitGroupId=1`)
+ respectively.
+
+The code is
+
+```` C
+ // Lantern shadow ray. Cast a ray towards the lantern whose lighting is being
+ // added this pass. Only the closest hit shader for lanterns will set
+ // hitLanternInstance (payload 2) to non-negative value.
+ else {
+ // Skip ray if no light would be added anyway.
+ if (colorIntensity == vec3(0)) {
+ isShadowed = true;
+ }
+ else {
+ uint flags = gl_RayFlagsOpaqueEXT;
+ hitLanternInstance = -1;
+ traceRayEXT(topLevelAS, // acceleration structure
+ flags, // rayFlags
+ 0xFF, // cullMask
+ 2, // sbtRecordOffset : lantern shadow hit groups start at index 2.
+ 0, // sbtRecordStride
+ 2, // missIndex : lantern shadow miss shader is number 2.
+ origin, // ray origin
+ tMin, // ray min range
+ rayDir, // ray direction
+ tMax, // ray max range
+ 2 // payload (location = 2)
+ );
+ // Did we hit the lantern we expected?
+ isShadowed = (hitLanternInstance != pushC.lanternPassNumber);
+ }
+ }
+````
+
+Notice that we determine whether this lantern is shadowed at this pixel by
+checking if the hit lantern number matches the lantern whose light is being
+added this pass; again, this ensures lanterns correctly shadow each others' light.
+
+## Write Payload
+
+Replace `lightIntensity` with `colorIntensity` and ask the raygen shader
+for additive blending.
+
+```` C
+ prd.hitValue = colorIntensity * (attenuation * (diffuse + specular));
+ prd.additiveBlending = true;
+````
+
+# Trace Rays Indirect
+
+Everything is finally set up to actually run the extra lantern passes
+in `HelloVulkan::raytrace`. We've already dispatched the compute
+shader in an earlier section. After that, we can run the first raytrace
+pass. There are minimal changes from before, we just have to
+
+* Initialize the new push constant values (especially setting
+ `lanternPassNumber=-1` to indicate this is not a lantern pass).
+
+```` C
+ // Initialize push constant values
+ m_rtPushConstants.clearColor = clearColor;
+ m_rtPushConstants.lightPosition = m_pushConstant.lightPosition;
+ m_rtPushConstants.lightIntensity = m_pushConstant.lightIntensity;
+ m_rtPushConstants.lightType = m_pushConstant.lightType;
+ m_rtPushConstants.lanternPassNumber = -1; // Global non-lantern pass
+ m_rtPushConstants.screenX = m_size.width;
+ m_rtPushConstants.screenY = m_size.height;
+ m_rtPushConstants.lanternDebug = m_lanternDebug;
+````
+
+* Update the addresses of the raygen, miss, and hit group sections of the SBT
+ to account for the added shaders.
+
+```` C
+ using Stride = vk::StridedDeviceAddressRegionKHR;
+ std::array strideAddresses{
+ Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen
+ Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 3}, // miss
+ Stride{sbtAddress + 4u * groupSize, groupStride, groupSize * 4}, // hit
+ Stride{0u, 0u, 0u}}; // callable
+
+ // First pass, illuminate scene with global light.
+ cmdBuf.traceRaysKHR(
+ &strideAddresses[0], &strideAddresses[1], //
+ &strideAddresses[2], &strideAddresses[3], //
+ m_size.width, m_size.height, 1);
+````
+
+After that, we can open a loop for performing all lantern passes.
+
+```` C
+ // Lantern passes, ensure previous pass completed, then add light contribution from each lantern.
+ for (int i = 0; i < static_cast(m_lanternCount); ++i)
+ {
+````
+
+Because the additive blending in the shader requires read-modify-write operations,
+we need a barrier between every pass.
+
+```` C
+ // Barrier to ensure previous pass finished.
+ vk::Image offscreenImage{m_offscreenColor.image};
+ vk::ImageSubresourceRange colorRange(
+ vk::ImageAspectFlagBits::eColor, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS
+ );
+ vk::ImageMemoryBarrier imageBarrier;
+ imageBarrier.setOldLayout(vk::ImageLayout::eGeneral);
+ imageBarrier.setNewLayout(vk::ImageLayout::eGeneral);
+ imageBarrier.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ imageBarrier.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ imageBarrier.setImage(offscreenImage);
+ imageBarrier.setSubresourceRange(colorRange);
+ imageBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
+ imageBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
+ cmdBuf.pipelineBarrier(
+ vk::PipelineStageFlagBits::eRayTracingShaderKHR, //
+ vk::PipelineStageFlagBits::eRayTracingShaderKHR, //
+ vk::DependencyFlags(0), //
+ {}, {}, {imageBarrier});
+````
+
+Then, we can pass the number of the lantern pass being performed (`i`), and look
+up the indirect parameters for that entry. Unlike draw and dispatch indirect, the
+indirect paramater location is passed as a raw device address, so we need to
+perform manual device pointer arithmetic to look up the $i^{th}$ entry of the
+`LanternIndirectEntry` array. Take advantage of the fact that `VkTraceRaysIndirectCommandKHR`
+is the first member of `LanternIndirectEntry`.
+
+```` C
+ // Set lantern pass number.
+ m_rtPushConstants.lanternPassNumber = i;
+ cmdBuf.pushConstants(m_rtPipelineLayout,
+ vk::ShaderStageFlagBits::eRaygenKHR
+ | vk::ShaderStageFlagBits::eClosestHitKHR
+ | vk::ShaderStageFlagBits::eMissKHR,
+ 0, m_rtPushConstants);
+
+ // Execute lantern pass.
+ cmdBuf.traceRaysIndirectKHR(
+ &strideAddresses[0], &strideAddresses[1], //
+ &strideAddresses[2], &strideAddresses[3], //
+ m_device.getBufferAddress({m_lanternIndirectBuffer.buffer}) + i * sizeof(LanternIndirectEntry));
+ }
+````
+
+Everything should be in order now. We can see in this image that the cyan and purple lanterns
+are both shadowed by the doodad hanging off the side of the building, and the spikes on the
+roof cut shadows in the yellow lantern's light.
+
+
+
+Zoom out and enable the lantern debug checkbox to see the scissor rectangles.
+
+
+
+## Cleanup
+
+One last loose end, we have to clean up all the new resources is `HelloVulkan::destroyResources`.
+
+```` C
+// Destroying all allocations
+//
+void HelloVulkan::destroyResources()
+{
+ // ...
+
+ // #VKRay
+ // ...
+ m_device.destroy(m_lanternIndirectDescPool);
+ m_device.destroy(m_lanternIndirectDescSetLayout);
+ m_device.destroy(m_lanternIndirectCompPipeline);
+ m_device.destroy(m_lanternIndirectCompPipelineLayout);
+ m_alloc.destroy(m_lanternIndirectBuffer);
+ m_alloc.destroy(m_lanternVertexBuffer);
+ m_alloc.destroy(m_lanternIndexBuffer);
+}
+````
+
+# Final Code
+
+You can find the final code in the folder [ray_tracing_indirect_scissor](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_indirect_scissor)
+
+
+
+
+
+
+
+
diff --git a/docs/vkrt_tutorial.md.htm b/docs/vkrt_tutorial.md.htm
index 6c76782..be1b70f 100644
--- a/docs/vkrt_tutorial.md.htm
+++ b/docs/vkrt_tutorial.md.htm
@@ -1697,11 +1697,17 @@ the usage flag to include that stage. This was done in section Additions to the
## updateUniformBuffer
-The computation of the matrix inverses is done in `updateUniformBuffer`, after setting the `ubo.proj` matrix:
+The computation of the matrix inverses is done in `updateUniformBuffer`, after setting the `hostUBO.proj` matrix:
```` C
// #VKRay
-ubo.projInverse = nvmath::invert(ubo.proj);
+hostUBO.projInverse = nvmath::invert(hostUBO.proj);
+````
+
+We also have to indicate that the UBO will be used in the raytracing shaders.
+```` C
+ auto uboUsageStages = vk::PipelineStageFlagBits::eVertexShader
+ | vk::PipelineStageFlagBits::eRayTracingShaderKHR;
````
## Ray generation (raytrace.rgen)
@@ -2164,6 +2170,20 @@ At the end of the method, we destroy the shader module for the shadow miss shade
The spec does not guarantee a recursion check at runtime. If you exceed either
the recursion depth you reported in the raytrace pipeline create info, or the
physical device recursion limit, undefined behaviour results.
+
+ The KHR raytracing spec lowers the minimum guaranteed recursion limit from
+ 31 (in the original NV spec) to the much more modest limit of 1 (i.e. no
+ recursion at all). Since we now need a recursion limit of 2, we should check
+ that the device supports the needed level of recursion:
+
+ ```` C
+ // Spec only guarantees 1 level of "recursion". Check for that sad possibility here.
+ if (m_rtProperties.maxRayRecursionDepth <= 1) {
+ throw std::runtime_error("Device fails to support ray recursion (m_rtProperties.maxRayRecursionDepth <= 1)");
+ }
+ ````
+
+ Recall that `m_rtProperties` was filled in in `HelloVulkan::initRayTracing`.
## `traceRaysKHR`
diff --git a/ray_tracing_indirect_scissor/CMakeLists.txt b/ray_tracing_indirect_scissor/CMakeLists.txt
new file mode 100644
index 0000000..7b57615
--- /dev/null
+++ b/ray_tracing_indirect_scissor/CMakeLists.txt
@@ -0,0 +1,111 @@
+#*****************************************************************************
+# Copyright 2020 NVIDIA Corporation. All rights reserved.
+#*****************************************************************************
+
+cmake_minimum_required(VERSION 3.9.6 FATAL_ERROR)
+
+#--------------------------------------------------------------------------------------------------
+# Project setting
+get_filename_component(PROJNAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+SET(PROJNAME vk_${PROJNAME}_KHR)
+project(${PROJNAME} LANGUAGES C CXX)
+message(STATUS "-------------------------------")
+message(STATUS "Processing Project ${PROJNAME}:")
+
+
+#--------------------------------------------------------------------------------------------------
+# C++ target and defines
+set(CMAKE_CXX_STANDARD 17)
+add_executable(${PROJNAME})
+_add_project_definitions(${PROJNAME})
+
+
+#--------------------------------------------------------------------------------------------------
+# Source files for this project
+#
+file(GLOB SOURCE_FILES *.cpp *.hpp *.inl *.h *.c)
+file(GLOB EXTRA_COMMON ${TUTO_KHR_DIR}/common/*.*)
+list(APPEND COMMON_SOURCE_FILES ${EXTRA_COMMON})
+include_directories(${TUTO_KHR_DIR}/common)
+
+
+#--------------------------------------------------------------------------------------------------
+# GLSL to SPIR-V custom build
+#
+SET(VULKAN_TARGET_ENV vulkan1.2)
+UNSET(GLSL_SOURCES)
+UNSET(SPV_OUTPUT)
+file(GLOB_RECURSE GLSL_HEADER_FILES "shaders/*.h" "shaders/*.glsl")
+file(GLOB_RECURSE GLSL_SOURCE_FILES
+ "shaders/*.comp"
+ "shaders/*.frag"
+ "shaders/*.vert"
+ "shaders/*.rchit"
+ "shaders/*.rahit"
+ "shaders/*.rint"
+ "shaders/*.rmiss"
+ "shaders/*.rgen"
+ "shaders/*.rcall"
+ )
+foreach(GLSL ${GLSL_SOURCE_FILES})
+ get_filename_component(FILE_NAME ${GLSL} NAME)
+ _compile_GLSL(${GLSL} "shaders/${FILE_NAME}.spv" GLSL_SOURCES SPV_OUTPUT)
+endforeach(GLSL)
+list(APPEND GLSL_SOURCES ${GLSL_HEADER_FILES})
+
+
+#--------------------------------------------------------------------------------------------------
+# Sources
+target_sources(${PROJNAME} PUBLIC ${SOURCE_FILES} ${HEADER_FILES})
+target_sources(${PROJNAME} PUBLIC ${COMMON_SOURCE_FILES})
+target_sources(${PROJNAME} PUBLIC ${PACKAGE_SOURCE_FILES})
+target_sources(${PROJNAME} PUBLIC ${GLSL_SOURCES})
+
+
+#--------------------------------------------------------------------------------------------------
+# Sub-folders in Visual Studio
+#
+source_group("Common" FILES ${COMMON_SOURCE_FILES} ${PACKAGE_SOURCE_FILES})
+source_group("Sources" FILES ${SOURCE_FILES})
+source_group("Headers" FILES ${HEADER_FILES})
+source_group("Shader_Files" FILES ${GLSL_SOURCES})
+
+
+#--------------------------------------------------------------------------------------------------
+# Linkage
+#
+target_link_libraries(${PROJNAME} ${PLATFORM_LIBRARIES} shared_sources)
+
+foreach(DEBUGLIB ${LIBRARIES_DEBUG})
+ target_link_libraries(${PROJNAME} debug ${DEBUGLIB})
+endforeach(DEBUGLIB)
+
+foreach(RELEASELIB ${LIBRARIES_OPTIMIZED})
+ target_link_libraries(${PROJNAME} optimized ${RELEASELIB})
+endforeach(RELEASELIB)
+
+#--------------------------------------------------------------------------------------------------
+# copies binaries that need to be put next to the exe files (ZLib, etc.)
+#
+_copy_binaries_to_target( ${PROJNAME} )
+
+
+#install(FILES ${SPV_OUTPUT} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}/shaders")
+#install(FILES ${SPV_OUTPUT} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}/shaders")
+#install(FILES ${CUBIN_SOURCES} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}")
+#install(FILES ${CUBIN_SOURCES} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}")
+#install(DIRECTORY "../media" CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}")
+#install(DIRECTORY "../media" CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}")
+
+#----------------------------------------------------------------------------------------------------
+# Copying elements
+# Media
+# target_copy_to_output_dir(TARGET ${PROJECT_NAME} FILES "${TUTO_KHR_DIR}/media")
+# Spir-V Shaders
+target_copy_to_output_dir(
+ TARGET ${PROJECT_NAME}
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ DEST_SUBFOLDER "${PROJECT_NAME}/"
+ FILES ${SPV_OUTPUT}
+ )
+
diff --git a/ray_tracing_indirect_scissor/README.md b/ray_tracing_indirect_scissor/README.md
new file mode 100644
index 0000000..3cb49a5
--- /dev/null
+++ b/ray_tracing_indirect_scissor/README.md
@@ -0,0 +1,14 @@
+# NVIDIA Vulkan Ray Tracing Tutorial
+
+This example is the result of the ray tracing tutorial.
+The tutorial is adding ray tracing capability to an OBJ rasterizer in Vulkan
+
+If you haven't done it, [**Start Ray Tracing Tutorial**](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/).
+
+
+
+## Going Further
+
+Once the tutorial completed and the basics of ray tracing are in place, other tuturials are going further from this code base.
+
+See all other [additional ray tracing tutorials](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/vkrt_tuto_further.md.html)
diff --git a/ray_tracing_indirect_scissor/hello_vulkan.cpp b/ray_tracing_indirect_scissor/hello_vulkan.cpp
new file mode 100644
index 0000000..38086e2
--- /dev/null
+++ b/ray_tracing_indirect_scissor/hello_vulkan.cpp
@@ -0,0 +1,1468 @@
+/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of NVIDIA CORPORATION nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include
+#include
+
+extern std::vector defaultSearchPaths;
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "fileformats/stb_image.h"
+#include "obj_loader.h"
+
+#include "hello_vulkan.h"
+#include "nvh/alignment.hpp"
+#include "nvh/cameramanipulator.hpp"
+#include "nvh/fileoperations.hpp"
+#include "nvvk/commands_vk.hpp"
+#include "nvvk/descriptorsets_vk.hpp"
+#include "nvvk/pipeline_vk.hpp"
+#include "nvvk/renderpasses_vk.hpp"
+#include "nvvk/shaders_vk.hpp"
+
+
+// Holding the camera matrices
+struct CameraMatrices
+{
+ nvmath::mat4f view;
+ nvmath::mat4f proj;
+ nvmath::mat4f viewInverse;
+ // #VKRay
+ nvmath::mat4f projInverse;
+};
+
+
+//--------------------------------------------------------------------------------------------------
+// Keep the handle on the device
+// Initialize the tool to do all our allocations: buffers, images
+//
+void HelloVulkan::setup(const vk::Instance& instance,
+ const vk::Device& device,
+ const vk::PhysicalDevice& physicalDevice,
+ uint32_t queueFamily)
+{
+ AppBase::setup(instance, device, physicalDevice, queueFamily);
+ m_alloc.init(device, physicalDevice);
+ m_debug.setup(m_device);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Called at each frame to update the camera matrix
+//
+void HelloVulkan::updateUniformBuffer(const vk::CommandBuffer& cmdBuf)
+{
+ // Prepare new UBO contents on host.
+ const float aspectRatio = m_size.width / static_cast(m_size.height);
+ CameraMatrices hostUBO = {};
+ hostUBO.view = CameraManip.getMatrix();
+ hostUBO.proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f);
+ // hostUBO.proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK).
+ hostUBO.viewInverse = nvmath::invert(hostUBO.view);
+ // #VKRay
+ hostUBO.projInverse = nvmath::invert(hostUBO.proj);
+
+ // UBO on the device, and what stages access it.
+ vk::Buffer deviceUBO = m_cameraMat.buffer;
+ auto uboUsageStages = vk::PipelineStageFlagBits::eVertexShader
+ | vk::PipelineStageFlagBits::eRayTracingShaderKHR;
+
+ // Ensure that the modified UBO is not visible to previous frames.
+ vk::BufferMemoryBarrier beforeBarrier;
+ beforeBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderRead);
+ beforeBarrier.setDstAccessMask(vk::AccessFlagBits::eTransferWrite);
+ beforeBarrier.setBuffer(deviceUBO);
+ beforeBarrier.setOffset(0);
+ beforeBarrier.setSize(sizeof hostUBO);
+ cmdBuf.pipelineBarrier(
+ uboUsageStages,
+ vk::PipelineStageFlagBits::eTransfer,
+ vk::DependencyFlagBits::eDeviceGroup, {}, {beforeBarrier}, {});
+
+ // Schedule the host-to-device upload. (hostUBO is copied into the cmd
+ // buffer so it is okay to deallocate when the function returns).
+ cmdBuf.updateBuffer(m_cameraMat.buffer, 0, hostUBO);
+
+ // Making sure the updated UBO will be visible.
+ vk::BufferMemoryBarrier afterBarrier;
+ afterBarrier.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite);
+ afterBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
+ afterBarrier.setBuffer(deviceUBO);
+ afterBarrier.setOffset(0);
+ afterBarrier.setSize(sizeof hostUBO);
+ cmdBuf.pipelineBarrier(
+ vk::PipelineStageFlagBits::eTransfer,
+ uboUsageStages,
+ vk::DependencyFlagBits::eDeviceGroup, {}, {afterBarrier}, {});
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// Describing the layout pushed when rendering
+//
+void HelloVulkan::createDescriptorSetLayout()
+{
+ using vkDS = vk::DescriptorSetLayoutBinding;
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+ uint32_t nbTxt = static_cast(m_textures.size());
+ uint32_t nbObj = static_cast(m_objModel.size());
+
+ // Camera matrices (binding = 0)
+ m_descSetLayoutBind.addBinding(
+ vkDS(0, vkDT::eUniformBuffer, 1, vkSS::eVertex | vkSS::eRaygenKHR));
+ // Materials (binding = 1)
+ m_descSetLayoutBind.addBinding(
+ vkDS(1, vkDT::eStorageBuffer, nbObj, vkSS::eVertex | vkSS::eFragment | vkSS::eClosestHitKHR));
+ // Scene description (binding = 2)
+ m_descSetLayoutBind.addBinding( //
+ vkDS(2, vkDT::eStorageBuffer, 1, vkSS::eVertex | vkSS::eFragment | vkSS::eClosestHitKHR));
+ // Textures (binding = 3)
+ m_descSetLayoutBind.addBinding(
+ vkDS(3, vkDT::eCombinedImageSampler, nbTxt, vkSS::eFragment | vkSS::eClosestHitKHR));
+ // Materials (binding = 4)
+ m_descSetLayoutBind.addBinding(
+ vkDS(4, vkDT::eStorageBuffer, nbObj, vkSS::eFragment | vkSS::eClosestHitKHR));
+ // Storing vertices (binding = 5)
+ m_descSetLayoutBind.addBinding( //
+ vkDS(5, vkDT::eStorageBuffer, nbObj, vkSS::eClosestHitKHR));
+ // Storing indices (binding = 6)
+ m_descSetLayoutBind.addBinding( //
+ vkDS(6, vkDT::eStorageBuffer, nbObj, vkSS::eClosestHitKHR));
+
+
+ m_descSetLayout = m_descSetLayoutBind.createLayout(m_device);
+ m_descPool = m_descSetLayoutBind.createPool(m_device, 1);
+ m_descSet = nvvk::allocateDescriptorSet(m_device, m_descPool, m_descSetLayout);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Setting up the buffers in the descriptor set
+//
+void HelloVulkan::updateDescriptorSet()
+{
+ std::vector writes;
+
+ // Camera matrices and scene description
+ vk::DescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE};
+ writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif));
+ vk::DescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE};
+ writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 2, &dbiSceneDesc));
+
+ // All material buffers, 1 buffer per OBJ
+ std::vector dbiMat;
+ std::vector dbiMatIdx;
+ std::vector dbiVert;
+ std::vector dbiIdx;
+ for(auto& obj : m_objModel)
+ {
+ dbiMat.emplace_back(obj.matColorBuffer.buffer, 0, VK_WHOLE_SIZE);
+ dbiMatIdx.emplace_back(obj.matIndexBuffer.buffer, 0, VK_WHOLE_SIZE);
+ dbiVert.emplace_back(obj.vertexBuffer.buffer, 0, VK_WHOLE_SIZE);
+ dbiIdx.emplace_back(obj.indexBuffer.buffer, 0, VK_WHOLE_SIZE);
+ }
+ writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 1, dbiMat.data()));
+ writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 4, dbiMatIdx.data()));
+ writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 5, dbiVert.data()));
+ writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 6, dbiIdx.data()));
+
+ // All texture samplers
+ std::vector diit;
+ for(auto& texture : m_textures)
+ {
+ diit.emplace_back(texture.descriptor);
+ }
+ writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 3, diit.data()));
+
+ // Writing the information
+ m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Creating the pipeline layout
+//
+void HelloVulkan::createGraphicsPipeline()
+{
+ using vkSS = vk::ShaderStageFlagBits;
+
+ vk::PushConstantRange pushConstantRanges = {vkSS::eVertex | vkSS::eFragment, 0,
+ sizeof(ObjPushConstant)};
+
+ // Creating the Pipeline Layout
+ vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo;
+ vk::DescriptorSetLayout descSetLayout(m_descSetLayout);
+ pipelineLayoutCreateInfo.setSetLayoutCount(1);
+ pipelineLayoutCreateInfo.setPSetLayouts(&descSetLayout);
+ pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
+ pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges);
+ m_pipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo);
+
+ // Creating the Pipeline
+ std::vector paths = defaultSearchPaths;
+ nvvk::GraphicsPipelineGeneratorCombined gpb(m_device, m_pipelineLayout, m_offscreenRenderPass);
+ gpb.depthStencilState.depthTestEnable = true;
+ gpb.addShader(nvh::loadFile("shaders/vert_shader.vert.spv", true, paths, true), vkSS::eVertex);
+ gpb.addShader(nvh::loadFile("shaders/frag_shader.frag.spv", true, paths, true), vkSS::eFragment);
+ gpb.addBindingDescription({0, sizeof(VertexObj)});
+ gpb.addAttributeDescriptions(std::vector{
+ {0, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, pos)},
+ {1, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, nrm)},
+ {2, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, color)},
+ {3, 0, vk::Format::eR32G32Sfloat, offsetof(VertexObj, texCoord)}});
+
+ m_graphicsPipeline = gpb.createPipeline();
+ m_debug.setObjectName(m_graphicsPipeline, "Graphics");
+}
+
+//--------------------------------------------------------------------------------------------------
+// Loading the OBJ file and setting up all buffers
+//
+void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform)
+{
+ using vkBU = vk::BufferUsageFlagBits;
+
+ LOGI("Loading File: %s \n", filename.c_str());
+ ObjLoader loader;
+ loader.loadModel(filename);
+
+ // Converting from Srgb to linear
+ for(auto& m : loader.m_materials)
+ {
+ m.ambient = nvmath::pow(m.ambient, 2.2f);
+ m.diffuse = nvmath::pow(m.diffuse, 2.2f);
+ m.specular = nvmath::pow(m.specular, 2.2f);
+ }
+
+ ObjInstance instance;
+ instance.objIndex = static_cast(m_objModel.size());
+ instance.transform = transform;
+ instance.transformIT = nvmath::transpose(nvmath::invert(transform));
+ instance.txtOffset = static_cast(m_textures.size());
+
+ ObjModel model;
+ model.nbIndices = static_cast(loader.m_indices.size());
+ model.nbVertices = static_cast(loader.m_vertices.size());
+
+ // Create the buffers on Device and copy vertices, indices and materials
+ nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex);
+ vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();
+ model.vertexBuffer =
+ m_alloc.createBuffer(cmdBuf, loader.m_vertices,
+ vkBU::eVertexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress
+ | vkBU::eAccelerationStructureBuildInputReadOnlyKHR);
+ model.indexBuffer =
+ m_alloc.createBuffer(cmdBuf, loader.m_indices,
+ vkBU::eIndexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress
+ | vkBU::eAccelerationStructureBuildInputReadOnlyKHR);
+ model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, vkBU::eStorageBuffer);
+ model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, vkBU::eStorageBuffer);
+ // Creates all textures found
+ createTextureImages(cmdBuf, loader.m_textures);
+ cmdBufGet.submitAndWait(cmdBuf);
+ m_alloc.finalizeAndReleaseStaging();
+
+ std::string objNb = std::to_string(instance.objIndex);
+ m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb).c_str()));
+ m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb).c_str()));
+ m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb).c_str()));
+ m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb).c_str()));
+
+ m_objModel.emplace_back(model);
+ m_objInstance.emplace_back(instance);
+}
+
+// Add a light-emitting colored lantern to the scene. May only be called before TLAS build.
+void HelloVulkan::addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius)
+{
+ assert(m_lanternCount == 0); // Indicates TLAS build has not happened yet.
+
+ m_lanterns.push_back({pos, color, brightness, radius});
+}
+
+//--------------------------------------------------------------------------------------------------
+// Creating the uniform buffer holding the camera matrices
+// - Buffer is host visible
+//
+void HelloVulkan::createUniformBuffer()
+{
+ using vkBU = vk::BufferUsageFlagBits;
+ using vkMP = vk::MemoryPropertyFlagBits;
+
+ m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices),
+ vkBU::eUniformBuffer | vkBU::eTransferDst, vkMP::eDeviceLocal);
+ m_debug.setObjectName(m_cameraMat.buffer, "cameraMat");
+}
+
+//--------------------------------------------------------------------------------------------------
+// Create a storage buffer containing the description of the scene elements
+// - Which geometry is used by which instance
+// - Transformation
+// - Offset for texture
+//
+void HelloVulkan::createSceneDescriptionBuffer()
+{
+ using vkBU = vk::BufferUsageFlagBits;
+ nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex);
+
+ auto cmdBuf = cmdGen.createCommandBuffer();
+ m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, vkBU::eStorageBuffer);
+ cmdGen.submitAndWait(cmdBuf);
+ m_alloc.finalizeAndReleaseStaging();
+ m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc");
+}
+
+//--------------------------------------------------------------------------------------------------
+// Creating all textures and samplers
+//
+void HelloVulkan::createTextureImages(const vk::CommandBuffer& cmdBuf,
+ const std::vector& textures)
+{
+ using vkIU = vk::ImageUsageFlagBits;
+
+ vk::SamplerCreateInfo samplerCreateInfo{
+ {}, vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear};
+ samplerCreateInfo.setMaxLod(FLT_MAX);
+ vk::Format format = vk::Format::eR8G8B8A8Srgb;
+
+ // If no textures are present, create a dummy one to accommodate the pipeline layout
+ if(textures.empty() && m_textures.empty())
+ {
+ nvvk::Texture texture;
+
+ std::array color{255u, 255u, 255u, 255u};
+ vk::DeviceSize bufferSize = sizeof(color);
+ auto imgSize = vk::Extent2D(1, 1);
+ auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format);
+
+ // Creating the dummy texture
+ nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, color.data(), imageCreateInfo);
+ vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo);
+ texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo);
+
+ // The image format must be in VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+ nvvk::cmdBarrierImageLayout(cmdBuf, texture.image, vk::ImageLayout::eUndefined,
+ vk::ImageLayout::eShaderReadOnlyOptimal);
+ m_textures.push_back(texture);
+ }
+ else
+ {
+ // Uploading all images
+ for(const auto& texture : textures)
+ {
+ std::stringstream o;
+ int texWidth, texHeight, texChannels;
+ o << "media/textures/" << texture;
+ std::string txtFile = nvh::findFile(o.str(), defaultSearchPaths, true);
+
+ stbi_uc* stbi_pixels =
+ stbi_load(txtFile.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
+
+ std::array color{255u, 0u, 255u, 255u};
+
+ stbi_uc* pixels = stbi_pixels;
+ // Handle failure
+ if(!stbi_pixels)
+ {
+ texWidth = texHeight = 1;
+ texChannels = 4;
+ pixels = reinterpret_cast(color.data());
+ }
+
+ vk::DeviceSize bufferSize = static_cast(texWidth) * texHeight * sizeof(uint8_t) * 4;
+ auto imgSize = vk::Extent2D(texWidth, texHeight);
+ auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format, vkIU::eSampled, true);
+
+ {
+ nvvk::ImageDedicated image =
+ m_alloc.createImage(cmdBuf, bufferSize, pixels, imageCreateInfo);
+ nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels);
+ vk::ImageViewCreateInfo ivInfo =
+ nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo);
+ nvvk::Texture texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo);
+
+ m_textures.push_back(texture);
+ }
+
+ stbi_image_free(stbi_pixels);
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+// Destroying all allocations
+//
+void HelloVulkan::destroyResources()
+{
+ m_device.destroy(m_graphicsPipeline);
+ m_device.destroy(m_pipelineLayout);
+ m_device.destroy(m_descPool);
+ m_device.destroy(m_descSetLayout);
+ m_alloc.destroy(m_cameraMat);
+ m_alloc.destroy(m_sceneDesc);
+
+ for(auto& m : m_objModel)
+ {
+ m_alloc.destroy(m.vertexBuffer);
+ m_alloc.destroy(m.indexBuffer);
+ m_alloc.destroy(m.matColorBuffer);
+ m_alloc.destroy(m.matIndexBuffer);
+ }
+
+ for(auto& t : m_textures)
+ {
+ m_alloc.destroy(t);
+ }
+
+ //#Post
+ m_device.destroy(m_postPipeline);
+ m_device.destroy(m_postPipelineLayout);
+ m_device.destroy(m_postDescPool);
+ m_device.destroy(m_postDescSetLayout);
+ m_alloc.destroy(m_offscreenColor);
+ m_alloc.destroy(m_offscreenDepth);
+ m_device.destroy(m_offscreenRenderPass);
+ m_device.destroy(m_offscreenFramebuffer);
+
+ // #VKRay
+ m_rtBuilder.destroy();
+ m_device.destroy(m_rtDescPool);
+ m_device.destroy(m_rtDescSetLayout);
+ m_device.destroy(m_rtPipeline);
+ m_device.destroy(m_rtPipelineLayout);
+ m_alloc.destroy(m_rtSBTBuffer);
+
+ m_device.destroy(m_lanternIndirectDescPool);
+ m_device.destroy(m_lanternIndirectDescSetLayout);
+ m_device.destroy(m_lanternIndirectCompPipeline);
+ m_device.destroy(m_lanternIndirectCompPipelineLayout);
+ m_alloc.destroy(m_lanternIndirectBuffer);
+ m_alloc.destroy(m_lanternVertexBuffer);
+ m_alloc.destroy(m_lanternIndexBuffer);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Drawing the scene in raster mode
+//
+void HelloVulkan::rasterize(const vk::CommandBuffer& cmdBuf)
+{
+ using vkPBP = vk::PipelineBindPoint;
+ using vkSS = vk::ShaderStageFlagBits;
+ vk::DeviceSize offset{0};
+
+ m_debug.beginLabel(cmdBuf, "Rasterize");
+
+ // Dynamic Viewport
+ cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)});
+ cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}});
+
+ // Drawing all triangles
+ cmdBuf.bindPipeline(vkPBP::eGraphics, m_graphicsPipeline);
+ cmdBuf.bindDescriptorSets(vkPBP::eGraphics, m_pipelineLayout, 0, {m_descSet}, {});
+ for(int i = 0; i < m_objInstance.size(); ++i)
+ {
+ auto& inst = m_objInstance[i];
+ auto& model = m_objModel[inst.objIndex];
+ m_pushConstant.instanceId = i; // Telling which instance is drawn
+ cmdBuf.pushConstants(m_pipelineLayout, vkSS::eVertex | vkSS::eFragment, 0,
+ m_pushConstant);
+
+ cmdBuf.bindVertexBuffers(0, {model.vertexBuffer.buffer}, {offset});
+ cmdBuf.bindIndexBuffer(model.indexBuffer.buffer, 0, vk::IndexType::eUint32);
+ cmdBuf.drawIndexed(model.nbIndices, 1, 0, 0, 0);
+ }
+ m_debug.endLabel(cmdBuf);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Handling resize of the window
+//
+void HelloVulkan::onResize(int /*w*/, int /*h*/)
+{
+ createOffscreenRender();
+ updatePostDescriptorSet();
+ updateRtDescriptorSet();
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Post-processing
+//////////////////////////////////////////////////////////////////////////
+
+//--------------------------------------------------------------------------------------------------
+// Creating an offscreen frame buffer and the associated render pass
+//
+void HelloVulkan::createOffscreenRender()
+{
+ m_alloc.destroy(m_offscreenColor);
+ m_alloc.destroy(m_offscreenDepth);
+
+ // Creating the color image
+ {
+ auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenColorFormat,
+ vk::ImageUsageFlagBits::eColorAttachment
+ | vk::ImageUsageFlagBits::eSampled
+ | vk::ImageUsageFlagBits::eStorage);
+
+
+ nvvk::Image image = m_alloc.createImage(colorCreateInfo);
+ vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
+ m_offscreenColor = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
+ m_offscreenColor.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ }
+
+ // Creating the depth buffer
+ auto depthCreateInfo =
+ nvvk::makeImage2DCreateInfo(m_size, m_offscreenDepthFormat,
+ vk::ImageUsageFlagBits::eDepthStencilAttachment);
+ {
+ nvvk::Image image = m_alloc.createImage(depthCreateInfo);
+
+ vk::ImageViewCreateInfo depthStencilView;
+ depthStencilView.setViewType(vk::ImageViewType::e2D);
+ depthStencilView.setFormat(m_offscreenDepthFormat);
+ depthStencilView.setSubresourceRange({vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1});
+ depthStencilView.setImage(image.image);
+
+ m_offscreenDepth = m_alloc.createTexture(image, depthStencilView);
+ }
+
+ // Setting the image layout for both color and depth
+ {
+ nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex);
+ auto cmdBuf = genCmdBuf.createCommandBuffer();
+ nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenColor.image, vk::ImageLayout::eUndefined,
+ vk::ImageLayout::eGeneral);
+ nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenDepth.image, vk::ImageLayout::eUndefined,
+ vk::ImageLayout::eDepthStencilAttachmentOptimal,
+ vk::ImageAspectFlagBits::eDepth);
+
+ genCmdBuf.submitAndWait(cmdBuf);
+ }
+
+ // Creating a renderpass for the offscreen
+ if(!m_offscreenRenderPass)
+ {
+ m_offscreenRenderPass =
+ nvvk::createRenderPass(m_device, {m_offscreenColorFormat}, m_offscreenDepthFormat, 1, true,
+ true, vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral);
+ }
+
+ // Creating the frame buffer for offscreen
+ std::vector attachments = {m_offscreenColor.descriptor.imageView,
+ m_offscreenDepth.descriptor.imageView};
+
+ m_device.destroy(m_offscreenFramebuffer);
+ vk::FramebufferCreateInfo info;
+ info.setRenderPass(m_offscreenRenderPass);
+ info.setAttachmentCount(2);
+ info.setPAttachments(attachments.data());
+ info.setWidth(m_size.width);
+ info.setHeight(m_size.height);
+ info.setLayers(1);
+ m_offscreenFramebuffer = m_device.createFramebuffer(info);
+}
+
+//--------------------------------------------------------------------------------------------------
+// The pipeline is how things are rendered, which shaders, type of primitives, depth test and more
+//
+void HelloVulkan::createPostPipeline()
+{
+ // Push constants in the fragment shader
+ vk::PushConstantRange pushConstantRanges = {vk::ShaderStageFlagBits::eFragment, 0, sizeof(float)};
+
+ // Creating the pipeline layout
+ vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo;
+ pipelineLayoutCreateInfo.setSetLayoutCount(1);
+ pipelineLayoutCreateInfo.setPSetLayouts(&m_postDescSetLayout);
+ pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
+ pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges);
+ m_postPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo);
+
+ // Pipeline: completely generic, no vertices
+ std::vector paths = defaultSearchPaths;
+
+ nvvk::GraphicsPipelineGeneratorCombined pipelineGenerator(m_device, m_postPipelineLayout,
+ m_renderPass);
+ pipelineGenerator.addShader(nvh::loadFile("shaders/passthrough.vert.spv", true, paths, true),
+ vk::ShaderStageFlagBits::eVertex);
+ pipelineGenerator.addShader(nvh::loadFile("shaders/post.frag.spv", true, paths, true),
+ vk::ShaderStageFlagBits::eFragment);
+ pipelineGenerator.rasterizationState.setCullMode(vk::CullModeFlagBits::eNone);
+ m_postPipeline = pipelineGenerator.createPipeline();
+ m_debug.setObjectName(m_postPipeline, "post");
+}
+
+//--------------------------------------------------------------------------------------------------
+// The descriptor layout is the description of the data that is passed to the vertex or the
+// fragment program.
+//
+void HelloVulkan::createPostDescriptor()
+{
+ using vkDS = vk::DescriptorSetLayoutBinding;
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+
+ m_postDescSetLayoutBind.addBinding(vkDS(0, vkDT::eCombinedImageSampler, 1, vkSS::eFragment));
+ m_postDescSetLayout = m_postDescSetLayoutBind.createLayout(m_device);
+ m_postDescPool = m_postDescSetLayoutBind.createPool(m_device);
+ m_postDescSet = nvvk::allocateDescriptorSet(m_device, m_postDescPool, m_postDescSetLayout);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Update the output
+//
+void HelloVulkan::updatePostDescriptorSet()
+{
+ vk::WriteDescriptorSet writeDescriptorSets =
+ m_postDescSetLayoutBind.makeWrite(m_postDescSet, 0, &m_offscreenColor.descriptor);
+ m_device.updateDescriptorSets(writeDescriptorSets, nullptr);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Draw a full screen quad with the attached image
+//
+void HelloVulkan::drawPost(vk::CommandBuffer cmdBuf)
+{
+ m_debug.beginLabel(cmdBuf, "Post");
+
+ cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)});
+ cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}});
+
+ auto aspectRatio = static_cast(m_size.width) / static_cast(m_size.height);
+ cmdBuf.pushConstants(m_postPipelineLayout, vk::ShaderStageFlagBits::eFragment, 0,
+ aspectRatio);
+ cmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, m_postPipeline);
+ cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_postPipelineLayout, 0,
+ m_postDescSet, {});
+ cmdBuf.draw(3, 1, 0, 0);
+
+ m_debug.endLabel(cmdBuf);
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+//--------------------------------------------------------------------------------------------------
+// Initialize Vulkan ray tracing
+// #VKRay
+void HelloVulkan::initRayTracing()
+{
+ // Requesting ray tracing properties
+ auto properties =
+ m_physicalDevice.getProperties2();
+ m_rtProperties = properties.get();
+ m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Convert an OBJ model into the ray tracing geometry used to build the BLAS
+//
+nvvk::RaytracingBuilderKHR::BlasInput HelloVulkan::objectToVkGeometryKHR(const ObjModel& model)
+{
+ // BLAS builder requires raw device addresses.
+ vk::DeviceAddress vertexAddress = m_device.getBufferAddress({model.vertexBuffer.buffer});
+ vk::DeviceAddress indexAddress = m_device.getBufferAddress({model.indexBuffer.buffer});
+
+ uint32_t maxPrimitiveCount = model.nbIndices / 3;
+
+ // Describe buffer as array of VertexObj.
+ vk::AccelerationStructureGeometryTrianglesDataKHR triangles;
+ triangles.setVertexFormat(vk::Format::eR32G32B32Sfloat); // vec3 vertex position data.
+ triangles.setVertexData(vertexAddress);
+ triangles.setVertexStride(sizeof(VertexObj));
+ // Describe index data (32-bit unsigned int)
+ triangles.setIndexType(vk::IndexType::eUint32);
+ triangles.setIndexData(indexAddress);
+ // Indicate identity transform by setting transformData to null device pointer.
+ triangles.setTransformData({});
+ triangles.setMaxVertex(model.nbVertices);
+
+ // Identify the above data as containing opaque triangles.
+ vk::AccelerationStructureGeometryKHR asGeom;
+ asGeom.setGeometryType(vk::GeometryTypeKHR::eTriangles);
+ asGeom.setFlags(vk::GeometryFlagBitsKHR::eOpaque);
+ asGeom.geometry.setTriangles(triangles);
+
+ // The entire array will be used to build the BLAS.
+ vk::AccelerationStructureBuildRangeInfoKHR offset;
+ offset.setFirstVertex(0);
+ offset.setPrimitiveCount(maxPrimitiveCount);
+ offset.setPrimitiveOffset(0);
+ offset.setTransformOffset(0);
+
+ // Our blas is made from only one geometry, but could be made of many geometries
+ nvvk::RaytracingBuilderKHR::BlasInput input;
+ input.asGeometry.emplace_back(asGeom);
+ input.asBuildOffsetInfo.emplace_back(offset);
+
+ return input;
+}
+
+// Tesselate a sphere as a list of triangles; return its
+// vertices and indices as reference arguments.
+void HelloVulkan::fillLanternVerts(std::vector& vertices, std::vector& indices)
+{
+ // Create a spherical lantern model by recursively tesselating an octahedron.
+ struct VertexIndex
+ {
+ nvmath::vec3f vertex;
+ uint32_t index; // Keep track of this vert's _eventual_ index in vertices.
+ };
+ struct Triangle
+ {
+ VertexIndex vert0, vert1, vert2;
+ };
+
+ VertexIndex posX{{ m_lanternModelRadius, 0, 0}, 0};
+ VertexIndex negX{{-m_lanternModelRadius, 0, 0}, 1};
+ VertexIndex posY{{0, m_lanternModelRadius, 0}, 2};
+ VertexIndex negY{{0, -m_lanternModelRadius, 0}, 3};
+ VertexIndex posZ{{0, 0, m_lanternModelRadius}, 4};
+ VertexIndex negZ{{0, 0, -m_lanternModelRadius}, 5};
+ uint32_t vertexCount = 6;
+
+ // Initial triangle list is octahedron.
+ std::vector triangles {
+ { posX, posY, posZ },
+ { posX, posY, negZ },
+ { posX, negY, posZ },
+ { posX, negY, negZ },
+ { negX, posY, posZ },
+ { negX, posY, negZ },
+ { negX, negY, posZ },
+ { negX, negY, negZ } };
+
+ // Recursion: every iteration, convert the current model to a new
+ // model by breaking each triangle into 4 triangles.
+ for (int recursions = 0; recursions < 3; ++recursions)
+ {
+ std::vector new_triangles;
+ for (Triangle t : triangles) {
+ // Split each of three edges in half, then fixup the
+ // length of the midpoint to match m_lanternModelRadius.
+ // Record the index the new vertices will eventually have in vertices.
+ VertexIndex v01 {
+ m_lanternModelRadius * nvmath::normalize(t.vert0.vertex + t.vert1.vertex),
+ vertexCount++ };
+ VertexIndex v12 {
+ m_lanternModelRadius * nvmath::normalize(t.vert1.vertex + t.vert2.vertex),
+ vertexCount++ };
+ VertexIndex v02 {
+ m_lanternModelRadius * nvmath::normalize(t.vert0.vertex + t.vert2.vertex),
+ vertexCount++ };
+
+ // Old triangle becomes 4 new triangles.
+ new_triangles.push_back({t.vert0, v01, v02});
+ new_triangles.push_back({t.vert1, v01, v12});
+ new_triangles.push_back({t.vert2, v02, v12});
+ new_triangles.push_back({v01, v02, v12});
+ }
+ triangles = std::move(new_triangles);
+ }
+
+ vertices.resize(vertexCount);
+ indices.clear();
+ indices.reserve(triangles.size() * 3);
+
+ // Write out the vertices to the vertices vector, and
+ // connect the tesselated triangles with indices in the indices vector.
+ for (Triangle t : triangles)
+ {
+ vertices[t.vert0.index] = t.vert0.vertex;
+ vertices[t.vert1.index] = t.vert1.vertex;
+ vertices[t.vert2.index] = t.vert2.vertex;
+ indices.push_back(t.vert0.index);
+ indices.push_back(t.vert1.index);
+ indices.push_back(t.vert2.index);
+ }
+}
+
+// Create the BLAS storing triangles for the spherical lantern model. This fills in
+// m_lanternVertexBuffer: packed VkBuffer of vec3
+// m_lanternIndexBuffer: packed VkBuffer of 32-bit indices
+// m_lanternBlasInput: BLAS for spherical lantern
+//
+// NOTE: A more elegant way to do this is to use a procedular hit group instead,
+// then, this BLAS can just be one AABB. I wanted to avoid introducing the new
+// concept of intersection shaders here.
+void HelloVulkan::createLanternModel()
+{
+ std::vector vertices;
+ std::vector indices;
+ fillLanternVerts(vertices, indices);
+
+ // Upload vertex and index data to buffers.
+ auto usageFlags = vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR
+ | vk::BufferUsageFlagBits::eShaderDeviceAddress;
+ auto memFlags =
+ vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent;
+
+ auto vertexBytes = vertices.size() * sizeof vertices[0];
+ m_lanternVertexBuffer = m_alloc.createBuffer(vertexBytes, usageFlags, memFlags);
+ void* map = m_alloc.map(m_lanternVertexBuffer);
+ memcpy(map, vertices.data(), vertexBytes);
+ m_alloc.unmap(m_lanternVertexBuffer);
+
+ auto indexBytes = indices.size() * sizeof indices[0];
+ m_lanternIndexBuffer = m_alloc.createBuffer(indexBytes, usageFlags, memFlags);
+ map = m_alloc.map(m_lanternIndexBuffer);
+ memcpy(map, indices.data(), indexBytes);
+ m_alloc.unmap(m_lanternIndexBuffer);
+
+ // Package vertex and index buffers as BlasInput.
+ vk::DeviceAddress vertexAddress = m_device.getBufferAddress({m_lanternVertexBuffer.buffer});
+ vk::DeviceAddress indexAddress = m_device.getBufferAddress({m_lanternIndexBuffer.buffer});
+
+ uint32_t maxPrimitiveCount = uint32_t(indices.size() / 3);
+
+ // Describe buffer as packed array of float vec3.
+ vk::AccelerationStructureGeometryTrianglesDataKHR triangles;
+ triangles.setVertexFormat(vk::Format::eR32G32B32Sfloat); // vec3 vertex position data.
+ triangles.setVertexData(vertexAddress);
+ triangles.setVertexStride(sizeof(nvmath::vec3f));
+ // Describe index data (32-bit unsigned int)
+ triangles.setIndexType(vk::IndexType::eUint32);
+ triangles.setIndexData(indexAddress);
+ // Indicate identity transform by setting transformData to null device pointer.
+ triangles.setTransformData({});
+ triangles.setMaxVertex(uint32_t(vertices.size()));
+
+ // Identify the above data as containing opaque triangles.
+ vk::AccelerationStructureGeometryKHR asGeom;
+ asGeom.setGeometryType(vk::GeometryTypeKHR::eTriangles);
+ asGeom.setFlags(vk::GeometryFlagBitsKHR::eOpaque);
+ asGeom.geometry.setTriangles(triangles);
+
+ // The entire array will be used to build the BLAS.
+ vk::AccelerationStructureBuildRangeInfoKHR offset;
+ offset.setFirstVertex(0);
+ offset.setPrimitiveCount(maxPrimitiveCount);
+ offset.setPrimitiveOffset(0);
+ offset.setTransformOffset(0);
+
+ // Our blas is made from only one geometry, but could be made of many geometries
+ m_lanternBlasInput.asGeometry.emplace_back(asGeom);
+ m_lanternBlasInput.asBuildOffsetInfo.emplace_back(offset);
+}
+
+//--------------------------------------------------------------------------------------------------
+//
+// Build the array of BLAS in m_rtBuilder. There are `m_objModel.size() + 1`-many BLASes.
+// The first `m_objModel.size()` are used for OBJ model BLASes, and the last one
+// is used for the lanterns (model generated at runtime).
+void HelloVulkan::createBottomLevelAS()
+{
+ // BLAS - Storing each primitive in a geometry
+ std::vector allBlas;
+ allBlas.reserve(m_objModel.size() + 1);
+
+ // Add OBJ models.
+ for(const auto& obj : m_objModel)
+ {
+ auto blas = objectToVkGeometryKHR(obj);
+
+ // We could add more geometry in each BLAS, but we add only one for now
+ allBlas.emplace_back(blas);
+ }
+
+ // Add lantern model.
+ createLanternModel();
+ m_lanternBlasId = allBlas.size();
+ allBlas.emplace_back(m_lanternBlasInput);
+
+ m_rtBuilder.buildBlas(allBlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
+}
+
+// Build the TLAS in m_rtBuilder. Requires that the BLASes were already built and
+// that all ObjInstance and lanterns have been added. One instance with hitGroupId=0
+// is created for every OBJ instance, and one instance with hitGroupId=1 for each lantern.
+//
+// gl_InstanceCustomIndexEXT will be the index of the instance or lantern in m_objInstance or
+// m_lanterns respectively.
+void HelloVulkan::createTopLevelAS()
+{
+ assert(m_lanternCount == 0);
+ m_lanternCount = m_lanterns.size();
+
+ std::vector tlas;
+ tlas.reserve(m_objInstance.size() + m_lanternCount);
+
+ // Add the OBJ instances.
+ for(int i = 0; i < static_cast(m_objInstance.size()); i++)
+ {
+ nvvk::RaytracingBuilderKHR::Instance rayInst;
+ rayInst.transform = m_objInstance[i].transform; // Position of the instance
+ rayInst.instanceCustomId = i; // gl_InstanceCustomIndexEXT
+ rayInst.blasId = m_objInstance[i].objIndex;
+ rayInst.hitGroupId = 0; // We will use the same hit group for all OBJ
+ rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+ tlas.emplace_back(rayInst);
+ }
+
+ // Add lantern instances.
+ for(int i = 0; i < static_cast(m_lanterns.size()); ++i)
+ {
+ nvvk::RaytracingBuilderKHR::Instance lanternInstance;
+ lanternInstance.transform = nvmath::translation_mat4(m_lanterns[i].position);
+ lanternInstance.instanceCustomId = i;
+ lanternInstance.blasId = uint32_t(m_lanternBlasId);
+ lanternInstance.hitGroupId = 1; // Next hit group is for lanterns.
+ lanternInstance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+ tlas.emplace_back(lanternInstance);
+ }
+
+ m_rtBuilder.buildTlas(tlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
+}
+
+//--------------------------------------------------------------------------------------------------
+// This descriptor set holds the Acceleration structure, output image, and lanterns array buffer.
+//
+void HelloVulkan::createRtDescriptorSet()
+{
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+ using vkDSLB = vk::DescriptorSetLayoutBinding;
+
+ // TLAS (binding = 0)
+ m_rtDescSetLayoutBind.addBinding(vkDSLB(0, vkDT::eAccelerationStructureKHR, 1,
+ vkSS::eRaygenKHR | vkSS::eClosestHitKHR));
+ // Output image (binding = 1)
+ m_rtDescSetLayoutBind.addBinding(
+ vkDSLB(1, vkDT::eStorageImage, 1, vkSS::eRaygenKHR)); // Output image
+
+ // Lantern buffer (binding = 2)
+ m_rtDescSetLayoutBind.addBinding( //
+ vkDSLB(2, vkDT::eStorageBuffer, 1, vkSS::eRaygenKHR | vkSS::eClosestHitKHR));
+ assert(m_lanternCount > 0);
+
+ m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device);
+ m_rtDescSetLayout = m_rtDescSetLayoutBind.createLayout(m_device);
+ m_rtDescSet = m_device.allocateDescriptorSets({m_rtDescPool, 1, &m_rtDescSetLayout})[0];
+
+ vk::AccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure();
+ vk::WriteDescriptorSetAccelerationStructureKHR descASInfo;
+ descASInfo.setAccelerationStructureCount(1);
+ descASInfo.setPAccelerationStructures(&tlas);
+ vk::DescriptorImageInfo imageInfo{
+ {}, m_offscreenColor.descriptor.imageView, vk::ImageLayout::eGeneral};
+ vk::DescriptorBufferInfo lanternBufferInfo{
+ m_lanternIndirectBuffer.buffer, 0, m_lanternCount * sizeof(LanternIndirectEntry)};
+
+ std::vector writes;
+ writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 0, &descASInfo));
+ writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo));
+ writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &lanternBufferInfo));
+ m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr);
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// Writes the output image to the descriptor set
+// - Required when changing resolution
+//
+void HelloVulkan::updateRtDescriptorSet()
+{
+ using vkDT = vk::DescriptorType;
+
+ // (1) Output buffer
+ vk::DescriptorImageInfo imageInfo{
+ {}, m_offscreenColor.descriptor.imageView, vk::ImageLayout::eGeneral};
+ vk::WriteDescriptorSet wds{m_rtDescSet, 1, 0, 1, vkDT::eStorageImage, &imageInfo};
+ m_device.updateDescriptorSets(wds, nullptr);
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// Pipeline for the ray tracer: all shaders, raygen, chit, miss
+//
+// Shader list:
+//
+// 0 ====== Ray Generation Shaders =====================================================
+//
+// Raygen shader: Ray generation shader. Casts primary rays from camera to scene.
+//
+// 1 ====== Miss Shaders ===============================================================
+//
+// Miss shader 0: Miss shader when casting primary rays. Fill in clear color.
+//
+// 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Miss shader 1: Miss shader when casting shadow rays towards main light.
+// Reports no shadow.
+//
+// 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Miss shader 2: Miss shader when casting shadow rays towards a lantern.
+// Reports no lantern hit (-1).
+//
+// 4 ====== Hit Groups for Primary Rays (sbtRecordOffset=0) ============================
+//
+// chit shader 0: Closest hit shader for primary rays hitting OBJ instances
+// (hitGroupId=0). Casts shadow ray (to sky light or to lantern,
+// depending on pass number) and returns specular
+// and diffuse light to add to output image.
+//
+// 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// chit shader 1: Closest hit shader for primary rays hitting lantern instances
+// (hitGroupId=1). Returns color value to replace the current
+// image pixel color with (lanterns are self-illuminating).
+//
+// 6 - - - - Hit Groups for Lantern Shadow Rays (sbtRecordOffset=2) - - - - - - - - - - -
+//
+// chit shader 2: Closest hit shader for OBJ instances hit when casting shadow
+// rays to a lantern. Returns -1 to report that the shadow ray
+// failed to reach the targetted lantern.
+//
+// 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// chit shader 3: Closest hit shader for lantern instances hit when casting shadow
+// rays to a lantern. Returns the gl_CustomInstanceIndexEXT [lantern
+// number] of the lantern hit.
+//
+// 8 =====================================================================================
+void HelloVulkan::createRtPipeline()
+{
+ std::vector paths = defaultSearchPaths;
+
+ vk::ShaderModule raygenSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/raytrace.rgen.spv", true, paths, true));
+
+ // Miss shader 0 invoked when a primary ray doesn't hit geometry. Fills in clear color.
+ vk::ShaderModule missSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/raytrace.rmiss.spv", true, paths, true));
+
+ // Miss shader 1 is invoked when a shadow ray (for the main scene light)
+ // misses the geometry. It simply indicates that no occlusion has been found.
+ vk::ShaderModule shadowmissSM = nvvk::createShaderModule(
+ m_device, nvh::loadFile("shaders/raytraceShadow.rmiss.spv", true, paths, true));
+
+ // Miss shader 2 is invoked when a shadow ray for lantern lighting misses the
+ // lantern. It shouldn't be invoked, but I include it just in case.
+ vk::ShaderModule lanternmissSM = nvvk::createShaderModule(
+ m_device, nvh::loadFile("shaders/lanternShadow.rmiss.spv", true, paths, true));
+
+ std::vector stages;
+
+ // Raygen
+ vk::RayTracingShaderGroupCreateInfoKHR rg{vk::RayTracingShaderGroupTypeKHR::eGeneral,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eRaygenKHR, raygenSM, "main"});
+ rg.setGeneralShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(rg);
+ // Miss
+ vk::RayTracingShaderGroupCreateInfoKHR mg{vk::RayTracingShaderGroupTypeKHR::eGeneral,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, missSM, "main"});
+ mg.setGeneralShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(mg);
+
+ // Shadow Miss
+ stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, shadowmissSM, "main"});
+ mg.setGeneralShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(mg);
+
+ // Lantern Miss
+ stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, lanternmissSM, "main"});
+ mg.setGeneralShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(mg);
+
+ // OBJ Primary Ray Hit Group - Closest Hit + AnyHit (not used)
+ vk::ShaderModule chitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/raytrace.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR hg{vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, chitSM, "main"});
+ hg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(hg);
+
+ // Lantern Primary Ray Hit Group
+ vk::ShaderModule lanternChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lantern.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternChitSM, "main"});
+ lanternHg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(lanternHg);
+
+ // OBJ Lantern Shadow Ray Hit Group
+ vk::ShaderModule lanternShadowObjChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternShadowObj.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternShadowObjHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowObjChitSM, "main"});
+ lanternShadowObjHg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(lanternShadowObjHg);
+
+ // Lantern Lantern Shadow Ray Hit Group
+ vk::ShaderModule lanternShadowLanternChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternShadowLantern.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternShadowLanternHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowLanternChitSM, "main"});
+ lanternShadowLanternHg.setClosestHitShader(static_cast(stages.size() - 1));
+ m_rtShaderGroups.push_back(lanternShadowLanternHg);
+
+ vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo;
+
+ // Push constant: we want to be able to update constants used by the shaders
+ vk::PushConstantRange pushConstant{vk::ShaderStageFlagBits::eRaygenKHR
+ | vk::ShaderStageFlagBits::eClosestHitKHR
+ | vk::ShaderStageFlagBits::eMissKHR,
+ 0, sizeof(RtPushConstant)};
+ pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
+ pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstant);
+
+ // Descriptor sets: one specific to ray tracing, and one shared with the rasterization pipeline
+ std::vector rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout};
+ pipelineLayoutCreateInfo.setSetLayoutCount(static_cast(rtDescSetLayouts.size()));
+ pipelineLayoutCreateInfo.setPSetLayouts(rtDescSetLayouts.data());
+
+ m_rtPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo);
+
+ // Assemble the shader stages and recursion depth info into the ray tracing pipeline
+
+ vk::RayTracingPipelineCreateInfoKHR rayPipelineInfo;
+ rayPipelineInfo.setStageCount(static_cast(stages.size())); // Stages are shaders
+ rayPipelineInfo.setPStages(stages.data());
+
+ // In this case, m_rtShaderGroups.size() == 8: we have one raygen group,
+ // three miss shader groups, and four hit groups.
+ rayPipelineInfo.setGroupCount(static_cast(
+ m_rtShaderGroups.size()));
+ rayPipelineInfo.setPGroups(m_rtShaderGroups.data());
+
+ rayPipelineInfo.setMaxPipelineRayRecursionDepth(2); // Ray depth
+ rayPipelineInfo.setLayout(m_rtPipelineLayout);
+ m_rtPipeline = static_cast(
+ m_device.createRayTracingPipelineKHR({}, {}, rayPipelineInfo));
+
+ m_device.destroy(raygenSM);
+ m_device.destroy(missSM);
+ m_device.destroy(shadowmissSM);
+ m_device.destroy(lanternmissSM);
+ m_device.destroy(chitSM);
+ m_device.destroy(lanternChitSM);
+ m_device.destroy(lanternShadowObjChitSM);
+ m_device.destroy(lanternShadowLanternChitSM);
+}
+
+//--------------------------------------------------------------------------------------------------
+// The Shader Binding Table (SBT)
+// - getting all shader handles and write them in a SBT buffer
+// - Besides exception, this could be always done like this
+// See how the SBT buffer is used in run()
+//
+void HelloVulkan::createRtShaderBindingTable()
+{
+ auto groupCount = static_cast(m_rtShaderGroups.size());
+ assert(groupCount == 8 && "Update Comment"); // 8 shaders: raygen, 3 miss, 4 chit
+
+ uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier
+ // Compute the actual size needed per SBT entry (round-up to alignment needed).
+ uint32_t groupSizeAligned =
+ nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment);
+ // Bytes needed for the SBT.
+ uint32_t sbtSize = groupCount * groupSizeAligned;
+
+ // Fetch all the shader handles used in the pipeline. This is opaque data,
+ // so we store it in a vector of bytes.
+ std::vector shaderHandleStorage(sbtSize);
+ auto result = m_device.getRayTracingShaderGroupHandlesKHR(m_rtPipeline, 0, groupCount, sbtSize,
+ shaderHandleStorage.data());
+ assert(result == vk::Result::eSuccess);
+
+ // Allocate a buffer for storing the SBT. Give it a debug name for NSight.
+ m_rtSBTBuffer = m_alloc.createBuffer(
+ sbtSize,
+ vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eShaderDeviceAddress
+ | vk::BufferUsageFlagBits::eShaderBindingTableKHR,
+ vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
+ m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str());
+
+ // Map the SBT buffer and write in the handles.
+ void* mapped = m_alloc.map(m_rtSBTBuffer);
+ auto* pData = reinterpret_cast(mapped);
+ for(uint32_t g = 0; g < groupCount; g++)
+ {
+ memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize);
+ pData += groupSizeAligned;
+ }
+ m_alloc.unmap(m_rtSBTBuffer);
+ m_alloc.finalizeAndReleaseStaging();
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// The compute shader just needs read/write access to the buffer of LanternIndirectEntry.
+void HelloVulkan::createLanternIndirectDescriptorSet()
+{
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+ using vkDSLB = vk::DescriptorSetLayoutBinding;
+
+ // Lantern buffer (binding = 0)
+ m_lanternIndirectDescSetLayoutBind.addBinding( //
+ vkDSLB(0, vkDT::eStorageBuffer, 1, vkSS::eCompute));
+
+ m_lanternIndirectDescPool = m_lanternIndirectDescSetLayoutBind.createPool(m_device);
+ m_lanternIndirectDescSetLayout = m_lanternIndirectDescSetLayoutBind.createLayout(m_device);
+ m_lanternIndirectDescSet =
+ m_device.allocateDescriptorSets({m_lanternIndirectDescPool, 1, &m_lanternIndirectDescSetLayout})[0];
+
+ assert(m_lanternIndirectBuffer.buffer);
+ vk::DescriptorBufferInfo lanternBufferInfo{
+ m_lanternIndirectBuffer.buffer, 0, m_lanternCount * sizeof(LanternIndirectEntry)};
+
+ std::vector writes;
+ writes.emplace_back(m_lanternIndirectDescSetLayoutBind.makeWrite(m_lanternIndirectDescSet, 0, &lanternBufferInfo));
+ m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr);
+}
+
+// Create compute pipeline used to fill m_lanternIndirectBuffer with parameters
+// for dispatching the correct number of ray traces.
+void HelloVulkan::createLanternIndirectCompPipeline()
+{
+ // Compile compute shader and package as stage.
+ vk::ShaderModule computeShader =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternIndirect.comp.spv", true, defaultSearchPaths, true));
+ vk::PipelineShaderStageCreateInfo stageInfo;
+ stageInfo.setStage(vk::ShaderStageFlagBits::eCompute);
+ stageInfo.setModule(computeShader);
+ stageInfo.setPName("main");
+
+ // Set up push constant and pipeline layout.
+ constexpr auto pushSize = sizeof(m_lanternIndirectPushConstants);
+ vk::PushConstantRange pushCRange = {vk::ShaderStageFlagBits::eCompute, 0, pushSize};
+ static_assert(pushSize <= 128, "Spec guarantees only 128 byte push constant");
+ vk::PipelineLayoutCreateInfo layoutInfo;
+ layoutInfo.setSetLayoutCount(1);
+ layoutInfo.setPSetLayouts(&m_lanternIndirectDescSetLayout);
+ layoutInfo.setPushConstantRangeCount(1);
+ layoutInfo.setPPushConstantRanges(&pushCRange);
+ m_lanternIndirectCompPipelineLayout = m_device.createPipelineLayout(layoutInfo);
+
+ // Create compute pipeline.
+ vk::ComputePipelineCreateInfo pipelineInfo;
+ pipelineInfo.setStage(stageInfo);
+ pipelineInfo.setLayout(m_lanternIndirectCompPipelineLayout);
+ m_lanternIndirectCompPipeline = static_cast(m_device.createComputePipeline({}, pipelineInfo));
+
+ m_device.destroy(computeShader);
+}
+
+// Allocate the buffer used to pass lantern info + ray trace indirect parameters to ray tracer.
+// Fill in the lantern info from m_lanterns (indirect info is filled per-frame on device
+// using a compute shader). Must be called only after TLAS build.
+//
+// The buffer is an array of LanternIndirectEntry, entry i is for m_lanterns[i].
+void HelloVulkan::createLanternIndirectBuffer()
+{
+ assert(m_lanternCount > 0);
+ assert(m_lanternCount == m_lanterns.size());
+
+ // m_alloc behind the scenes uses cmdBuf to transfer data to the buffer.
+ nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex);
+ vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();
+
+ using Usage = vk::BufferUsageFlagBits;
+ m_lanternIndirectBuffer =
+ m_alloc.createBuffer(sizeof(LanternIndirectEntry) * m_lanternCount,
+ Usage::eIndirectBuffer | Usage::eTransferDst
+ | Usage::eShaderDeviceAddress | Usage::eStorageBuffer,
+ vk::MemoryPropertyFlagBits::eDeviceLocal);
+
+ std::vector entries(m_lanternCount);
+ for (size_t i = 0; i < m_lanternCount; ++i) entries[i].lantern = m_lanterns[i];
+ cmdBuf.updateBuffer(m_lanternIndirectBuffer.buffer, 0, entries.size() * sizeof entries[0], entries.data());
+
+ cmdBufGet.submitAndWait(cmdBuf);
+}
+
+//--------------------------------------------------------------------------------------------------
+// Ray Tracing the scene
+//
+// The raytracing is split into multiple passes:
+//
+// First pass fills in the initial values for every pixel in the output image.
+// Illumination and shadow rays come from the main light.
+//
+// Subsequently, one lantern pass is run for each lantern in the scene. We run
+// a compute shader to calculate a bounding scissor rectangle for each lantern's light
+// effect. This is stored in m_lanternIndirectBuffer. Then an indirect trace rays command
+// is run for every lantern within its scissor rectangle. The lanterns' light
+// contribution is additively blended into the output image.
+void HelloVulkan::raytrace(const vk::CommandBuffer& cmdBuf, const nvmath::vec4f& clearColor)
+{
+ // Before tracing rays, we need to dispatch the compute shaders that
+ // fill in the ray trace indirect parameters for each lantern pass.
+
+ // First, barrier before, ensure writes aren't visible to previous frame.
+ vk::BufferMemoryBarrier bufferBarrier;
+ bufferBarrier.setSrcAccessMask(vk::AccessFlagBits::eIndirectCommandRead);
+ bufferBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderWrite);
+ bufferBarrier.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ bufferBarrier.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ bufferBarrier.setBuffer(m_lanternIndirectBuffer.buffer);
+ bufferBarrier.offset = 0;
+ bufferBarrier.size = m_lanternCount * sizeof m_lanterns[0];
+ cmdBuf.pipelineBarrier( //
+ vk::PipelineStageFlagBits::eDrawIndirect, //
+ vk::PipelineStageFlagBits::eComputeShader,//
+ vk::DependencyFlags(0), //
+ {}, {bufferBarrier}, {});
+
+ // Bind compute shader, update push constant and descriptors, dispatch compute.
+ cmdBuf.bindPipeline(vk::PipelineBindPoint::eCompute, m_lanternIndirectCompPipeline);
+ nvmath::mat4 view = getViewMatrix();
+ m_lanternIndirectPushConstants.viewRowX = view.row(0);
+ m_lanternIndirectPushConstants.viewRowY = view.row(1);
+ m_lanternIndirectPushConstants.viewRowZ = view.row(2);
+ m_lanternIndirectPushConstants.proj = getProjMatrix();
+ m_lanternIndirectPushConstants.nearZ = nearZ;
+ m_lanternIndirectPushConstants.screenX = m_size.width;
+ m_lanternIndirectPushConstants.screenY = m_size.height;
+ m_lanternIndirectPushConstants.lanternCount = int32_t(m_lanternCount);
+ cmdBuf.pushConstants(
+ m_lanternIndirectCompPipelineLayout,
+ vk::ShaderStageFlagBits::eCompute,
+ 0, m_lanternIndirectPushConstants);
+ cmdBuf.bindDescriptorSets(
+ vk::PipelineBindPoint::eCompute, m_lanternIndirectCompPipelineLayout, 0, {m_lanternIndirectDescSet}, {});
+ cmdBuf.dispatch(1, 1, 1);
+
+ // Ensure compute results are visible when doing indirect ray trace.
+ bufferBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
+ bufferBarrier.setDstAccessMask(vk::AccessFlagBits::eIndirectCommandRead);
+ cmdBuf.pipelineBarrier( //
+ vk::PipelineStageFlagBits::eComputeShader, //
+ vk::PipelineStageFlagBits::eDrawIndirect, //
+ vk::DependencyFlags(0), //
+ {}, {bufferBarrier}, {});
+
+
+ // Now move on to the actual ray tracing.
+ m_debug.beginLabel(cmdBuf, "Ray trace");
+
+ // Initialize push constant values
+ m_rtPushConstants.clearColor = clearColor;
+ m_rtPushConstants.lightPosition = m_pushConstant.lightPosition;
+ m_rtPushConstants.lightIntensity = m_pushConstant.lightIntensity;
+ m_rtPushConstants.lightType = m_pushConstant.lightType;
+ m_rtPushConstants.lanternPassNumber = -1; // Global non-lantern pass
+ m_rtPushConstants.screenX = m_size.width;
+ m_rtPushConstants.screenY = m_size.height;
+ m_rtPushConstants.lanternDebug = m_lanternDebug;
+
+ cmdBuf.bindPipeline(vk::PipelineBindPoint::eRayTracingKHR, m_rtPipeline);
+ cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eRayTracingKHR, m_rtPipelineLayout, 0,
+ {m_rtDescSet, m_descSet}, {});
+ cmdBuf.pushConstants(m_rtPipelineLayout,
+ vk::ShaderStageFlagBits::eRaygenKHR
+ | vk::ShaderStageFlagBits::eClosestHitKHR
+ | vk::ShaderStageFlagBits::eMissKHR,
+ 0, m_rtPushConstants);
+
+ // Size of a program identifier
+ uint32_t groupSize =
+ nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment);
+ uint32_t groupStride = groupSize;
+ vk::DeviceAddress sbtAddress = m_device.getBufferAddress({m_rtSBTBuffer.buffer});
+
+ using Stride = vk::StridedDeviceAddressRegionKHR;
+ std::array strideAddresses{
+ Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen
+ Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 3}, // miss
+ Stride{sbtAddress + 4u * groupSize, groupStride, groupSize * 4}, // hit
+ Stride{0u, 0u, 0u}}; // callable
+
+ // First pass, illuminate scene with global light.
+ cmdBuf.traceRaysKHR(
+ &strideAddresses[0], &strideAddresses[1], //
+ &strideAddresses[2], &strideAddresses[3], //
+ m_size.width, m_size.height, 1);
+
+ // Lantern passes, ensure previous pass completed, then add light contribution from each lantern.
+ for (int i = 0; i < static_cast(m_lanternCount); ++i)
+ {
+ // Barrier to ensure previous pass finished.
+ vk::Image offscreenImage{m_offscreenColor.image};
+ vk::ImageSubresourceRange colorRange(
+ vk::ImageAspectFlagBits::eColor, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS
+ );
+ vk::ImageMemoryBarrier imageBarrier;
+ imageBarrier.setOldLayout(vk::ImageLayout::eGeneral);
+ imageBarrier.setNewLayout(vk::ImageLayout::eGeneral);
+ imageBarrier.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ imageBarrier.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ imageBarrier.setImage(offscreenImage);
+ imageBarrier.setSubresourceRange(colorRange);
+ imageBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
+ imageBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
+ cmdBuf.pipelineBarrier(
+ vk::PipelineStageFlagBits::eRayTracingShaderKHR, //
+ vk::PipelineStageFlagBits::eRayTracingShaderKHR, //
+ vk::DependencyFlags(0), //
+ {}, {}, {imageBarrier});
+
+ // Set lantern pass number.
+ m_rtPushConstants.lanternPassNumber = i;
+ cmdBuf.pushConstants(m_rtPipelineLayout,
+ vk::ShaderStageFlagBits::eRaygenKHR
+ | vk::ShaderStageFlagBits::eClosestHitKHR
+ | vk::ShaderStageFlagBits::eMissKHR,
+ 0, m_rtPushConstants);
+
+ // Execute lantern pass.
+ cmdBuf.traceRaysIndirectKHR(
+ &strideAddresses[0], &strideAddresses[1], //
+ &strideAddresses[2], &strideAddresses[3], //
+ m_device.getBufferAddress({m_lanternIndirectBuffer.buffer}) + i * sizeof(LanternIndirectEntry));
+ }
+
+ m_debug.endLabel(cmdBuf);
+}
diff --git a/ray_tracing_indirect_scissor/hello_vulkan.h b/ray_tracing_indirect_scissor/hello_vulkan.h
new file mode 100644
index 0000000..0a9a750
--- /dev/null
+++ b/ray_tracing_indirect_scissor/hello_vulkan.h
@@ -0,0 +1,278 @@
+/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of NVIDIA CORPORATION nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#pragma once
+#include
+
+#define NVVK_ALLOC_DEDICATED
+#include "nvvk/allocator_vk.hpp"
+#include "nvvk/appbase_vkpp.hpp"
+#include "nvvk/debug_util_vk.hpp"
+#include "nvvk/descriptorsets_vk.hpp"
+
+// #VKRay
+#include "nvvk/raytraceKHR_vk.hpp"
+
+//--------------------------------------------------------------------------------------------------
+// Simple rasterizer of OBJ objects
+// - Each OBJ loaded are stored in an `ObjModel` and referenced by a `ObjInstance`
+// - It is possible to have many `ObjInstance` referencing the same `ObjModel`
+// - Rendering is done in an offscreen framebuffer
+// - The image of the framebuffer is displayed in post-process in a full-screen quad
+//
+class HelloVulkan : public nvvk::AppBase
+{
+public:
+ void setup(const vk::Instance& instance,
+ const vk::Device& device,
+ const vk::PhysicalDevice& physicalDevice,
+ uint32_t queueFamily) override;
+ void createDescriptorSetLayout();
+ void createGraphicsPipeline();
+ void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1));
+ void addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius);
+ void updateDescriptorSet();
+ void createUniformBuffer();
+ void createSceneDescriptionBuffer();
+ void createTextureImages(const vk::CommandBuffer& cmdBuf,
+ const std::vector& textures);
+
+ nvmath::mat4 getViewMatrix()
+ {
+ return CameraManip.getMatrix();
+ }
+
+ static constexpr float nearZ = 0.1f;
+ nvmath::mat4 getProjMatrix()
+ {
+ const float aspectRatio = m_size.width / static_cast(m_size.height);
+ return nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, nearZ, 1000.0f);
+ }
+
+ void updateUniformBuffer(const vk::CommandBuffer&);
+ void onResize(int /*w*/, int /*h*/) override;
+ void destroyResources();
+ void rasterize(const vk::CommandBuffer& cmdBuff);
+
+ // The OBJ model
+ struct ObjModel
+ {
+ uint32_t nbIndices{0};
+ uint32_t nbVertices{0};
+ nvvk::Buffer vertexBuffer; // Device buffer of all 'Vertex'
+ nvvk::Buffer indexBuffer; // Device buffer of the indices forming triangles
+ nvvk::Buffer matColorBuffer; // Device buffer of array of 'Wavefront material'
+ nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material'
+ };
+
+ // Instance of the OBJ
+ struct ObjInstance
+ {
+ uint32_t objIndex{0}; // Reference to the `m_objModel`
+ uint32_t txtOffset{0}; // Offset in `m_textures`
+ nvmath::mat4f transform{1}; // Position of the instance
+ nvmath::mat4f transformIT{1}; // Inverse transpose
+ };
+
+ // Information pushed at each draw call
+ struct ObjPushConstant
+ {
+ nvmath::vec3f lightPosition{10.f, 15.f, 8.f};
+ int instanceId{0}; // To retrieve the transformation matrix
+ float lightIntensity{40.f};
+ int lightType{0}; // 0: point, 1: infinite
+ };
+ ObjPushConstant m_pushConstant;
+
+ // Information on each colored lantern illuminating the scene.
+ struct Lantern
+ {
+ nvmath::vec3f position;
+ nvmath::vec3f color;
+ float brightness;
+ float radius; // Max world-space distance that light illuminates.
+ };
+
+ // Information on each colored lantern, plus the info needed for dispatching the
+ // indirect ray trace command used to add its brightness effect.
+ // The dispatched ray trace covers pixels (offsetX, offsetY) to
+ // (offsetX + indirectCommand.width - 1, offsetY + indirectCommand.height - 1).
+ struct LanternIndirectEntry
+ {
+ // Filled in by the device using a compute shader.
+ // NOTE: I rely on indirectCommand being the first member.
+ VkTraceRaysIndirectCommandKHR indirectCommand;
+ int32_t offsetX;
+ int32_t offsetY;
+
+ // Filled in by the host.
+ Lantern lantern;
+ };
+
+ // Array of objects and instances in the scene. Not modifiable after acceleration structure build.
+ std::vector m_objModel;
+ std::vector m_objInstance;
+
+ // Array of lanterns in scene. Not modifiable after acceleration structure build.
+ std::vector m_lanterns;
+
+ // Graphic pipeline
+ vk::PipelineLayout m_pipelineLayout;
+ vk::Pipeline m_graphicsPipeline;
+ nvvk::DescriptorSetBindings m_descSetLayoutBind;
+ vk::DescriptorPool m_descPool;
+ vk::DescriptorSetLayout m_descSetLayout;
+ vk::DescriptorSet m_descSet;
+
+ nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices
+ nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances
+ std::vector m_textures; // vector of all textures of the scene
+
+ nvvk::AllocatorDedicated m_alloc; // Allocator for buffer, images, acceleration structures
+ nvvk::DebugUtil m_debug; // Utility to name objects
+
+ // #Post
+ void createOffscreenRender();
+ void createPostPipeline();
+ void createPostDescriptor();
+ void updatePostDescriptorSet();
+ void drawPost(vk::CommandBuffer cmdBuf);
+
+ nvvk::DescriptorSetBindings m_postDescSetLayoutBind;
+ vk::DescriptorPool m_postDescPool;
+ vk::DescriptorSetLayout m_postDescSetLayout;
+ vk::DescriptorSet m_postDescSet;
+ vk::Pipeline m_postPipeline;
+ vk::PipelineLayout m_postPipelineLayout;
+ vk::RenderPass m_offscreenRenderPass;
+ vk::Framebuffer m_offscreenFramebuffer;
+ nvvk::Texture m_offscreenColor;
+ vk::Format m_offscreenColorFormat{vk::Format::eR32G32B32A32Sfloat};
+ nvvk::Texture m_offscreenDepth;
+ vk::Format m_offscreenDepthFormat{vk::Format::eD32Sfloat};
+
+ // #VKRay
+ void initRayTracing();
+ nvvk::RaytracingBuilderKHR::BlasInput objectToVkGeometryKHR(const ObjModel& model);
+
+private:
+ void fillLanternVerts(std::vector& vertices, std::vector& indices);
+ void createLanternModel();
+
+public:
+ void createBottomLevelAS();
+ void createTopLevelAS();
+ void createRtDescriptorSet();
+ void updateRtDescriptorSet();
+ void createRtPipeline();
+ void createLanternIndirectDescriptorSet();
+ void createLanternIndirectCompPipeline();
+ void createRtShaderBindingTable();
+ void createLanternIndirectBuffer();
+
+ void raytrace(const vk::CommandBuffer& cmdBuf, const nvmath::vec4f& clearColor);
+
+ // Used to store lantern model, generated at runtime.
+ const float m_lanternModelRadius = 0.125;
+ nvvk::Buffer m_lanternVertexBuffer;
+ nvvk::Buffer m_lanternIndexBuffer;
+ nvvk::RaytracingBuilderKHR::BlasInput m_lanternBlasInput{};
+
+ // Index of lantern's BLAS in the BLAS array stored in m_rtBuilder.
+ size_t m_lanternBlasId;
+
+ vk::PhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties;
+ nvvk::RaytracingBuilderKHR m_rtBuilder;
+ nvvk::DescriptorSetBindings m_rtDescSetLayoutBind;
+ vk::DescriptorPool m_rtDescPool;
+ vk::DescriptorSetLayout m_rtDescSetLayout;
+ vk::DescriptorSet m_rtDescSet;
+ std::vector m_rtShaderGroups;
+ vk::PipelineLayout m_rtPipelineLayout;
+ vk::Pipeline m_rtPipeline;
+ nvvk::DescriptorSetBindings m_lanternIndirectDescSetLayoutBind;
+ vk::DescriptorPool m_lanternIndirectDescPool;
+ vk::DescriptorSetLayout m_lanternIndirectDescSetLayout;
+ vk::DescriptorSet m_lanternIndirectDescSet;
+ vk::PipelineLayout m_lanternIndirectCompPipelineLayout;
+ vk::Pipeline m_lanternIndirectCompPipeline;
+ nvvk::Buffer m_rtSBTBuffer;
+
+ // Buffer to source vkCmdTraceRaysIndirectKHR indirect parameters and lantern color,
+ // position, etc. from when doing lantern lighting passes.
+ nvvk::Buffer m_lanternIndirectBuffer;
+ VkDeviceSize m_lanternCount = 0; // Set to actual lantern count after TLAS build, as
+ // that is the point no more lanterns may be added.
+
+ // Push constant for ray trace pipeline.
+ struct RtPushConstant
+ {
+ // Background color
+ nvmath::vec4f clearColor;
+
+ // Information on the light in the sky used when lanternPassNumber = -1.
+ nvmath::vec3f lightPosition;
+ float lightIntensity;
+ int32_t lightType;
+
+ // -1 if this is the full-screen pass. Otherwise, this pass is to add light
+ // from lantern number lanternPassNumber. We use this to lookup trace indirect
+ // parameters in m_lanternIndirectBuffer.
+ int32_t lanternPassNumber;
+
+ // Pixel dimensions of the output image.
+ int32_t screenX;
+ int32_t screenY;
+
+ // See m_lanternDebug.
+ int32_t lanternDebug;
+ } m_rtPushConstants;
+
+ // Copied to RtPushConstant::lanternDebug. If true,
+ // make lantern produce constant light regardless of distance
+ // so that I can see the screen rectangle coverage.
+ bool m_lanternDebug = false;
+
+
+ // Push constant for compute shader filling lantern indirect buffer.
+ // Barely fits in 128-byte push constant limit guaranteed by spec.
+ struct LanternIndirectPushConstants
+ {
+ nvmath::vec4 viewRowX; // First 3 rows of view matrix.
+ nvmath::vec4 viewRowY; // Set w=1 implicitly in shader.
+ nvmath::vec4 viewRowZ;
+
+ nvmath::mat4 proj; // Perspective matrix
+ float nearZ; // Near plane used to create projection matrix.
+
+ // Pixel dimensions of output image (needed to scale NDC to screen coordinates).
+ int32_t screenX;
+ int32_t screenY;
+
+ // Length of the LanternIndirectEntry array.
+ int32_t lanternCount;
+ } m_lanternIndirectPushConstants;
+};
diff --git a/ray_tracing_indirect_scissor/main.cpp b/ray_tracing_indirect_scissor/main.cpp
new file mode 100644
index 0000000..814701b
--- /dev/null
+++ b/ray_tracing_indirect_scissor/main.cpp
@@ -0,0 +1,324 @@
+/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of NVIDIA CORPORATION nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// ImGui - standalone example application for Glfw + Vulkan, using programmable
+// pipeline If you are new to ImGui, see examples/README.txt and documentation
+// at the top of imgui.cpp.
+
+#include
+#include
+VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
+
+#include "imgui.h"
+#include "imgui_impl_glfw.h"
+
+#include "hello_vulkan.h"
+#include "imgui_camera_widget.h"
+#include "nvh/cameramanipulator.hpp"
+#include "nvh/fileoperations.hpp"
+#include "nvpsystem.hpp"
+#include "nvvk/appbase_vkpp.hpp"
+#include "nvvk/commands_vk.hpp"
+#include "nvvk/context_vk.hpp"
+
+
+//////////////////////////////////////////////////////////////////////////
+#define UNUSED(x) (void)(x)
+//////////////////////////////////////////////////////////////////////////
+
+// Default search path for shaders
+std::vector defaultSearchPaths;
+
+// GLFW Callback functions
+static void onErrorCallback(int error, const char* description)
+{
+ fprintf(stderr, "GLFW Error %d: %s\n", error, description);
+}
+
+// Extra UI
+void renderUI(HelloVulkan& helloVk)
+{
+ ImGuiH::CameraWidget();
+ if(ImGui::CollapsingHeader("Light"))
+ {
+ ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0);
+ ImGui::SameLine();
+ ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1);
+
+ ImGui::SliderFloat3("Position", &helloVk.m_pushConstant.lightPosition.x, -20.f, 20.f);
+ ImGui::SliderFloat("Intensity", &helloVk.m_pushConstant.lightIntensity, 0.f, 150.f);
+ ImGui::Checkbox("Lantern Debug", &helloVk.m_lanternDebug);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+static int const SAMPLE_WIDTH = 1280;
+static int const SAMPLE_HEIGHT = 720;
+
+//--------------------------------------------------------------------------------------------------
+// Application Entry
+//
+int main(int argc, char** argv)
+{
+ UNUSED(argc);
+
+ // Setup GLFW window
+ glfwSetErrorCallback(onErrorCallback);
+ if(!glfwInit())
+ {
+ return 1;
+ }
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+ GLFWwindow* window =
+ glfwCreateWindow(SAMPLE_WIDTH, SAMPLE_HEIGHT, PROJECT_NAME, nullptr, nullptr);
+
+ // Setup camera
+ CameraManip.setWindowSize(SAMPLE_WIDTH, SAMPLE_HEIGHT);
+ CameraManip.setLookat(nvmath::vec3f(5, 4, -4), nvmath::vec3f(0, 1, 0), nvmath::vec3f(0, 1, 0));
+
+ // Setup Vulkan
+ if(!glfwVulkanSupported())
+ {
+ printf("GLFW: Vulkan Not Supported\n");
+ return 1;
+ }
+
+ // setup some basic things for the sample, logging file for example
+ NVPSystem system(argv[0], PROJECT_NAME);
+
+ // Search path for shaders and other media
+ defaultSearchPaths = {
+ PROJECT_ABSDIRECTORY,
+ PROJECT_ABSDIRECTORY "..",
+ NVPSystem::exePath(),
+ NVPSystem::exePath() + "..",
+ NVPSystem::exePath() + std::string(PROJECT_NAME),
+ };
+
+ // Requesting Vulkan extensions and layers
+ nvvk::ContextCreateInfo contextInfo(true);
+ contextInfo.setVersion(1, 2);
+ contextInfo.addInstanceLayer("VK_LAYER_LUNARG_monitor", true);
+ contextInfo.addInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, true);
+ contextInfo.addInstanceExtension(VK_KHR_SURFACE_EXTENSION_NAME);
+#ifdef WIN32
+ contextInfo.addInstanceExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
+#else
+ contextInfo.addInstanceExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
+ contextInfo.addInstanceExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
+#endif
+ contextInfo.addInstanceExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+ contextInfo.addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+ contextInfo.addDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
+ contextInfo.addDeviceExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
+ // #VKRay: Activate the ray tracing extension
+ vk::PhysicalDeviceAccelerationStructureFeaturesKHR accelFeature;
+ contextInfo.addDeviceExtension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, false,
+ &accelFeature);
+ vk::PhysicalDeviceRayTracingPipelineFeaturesKHR rtPipelineFeature;
+ contextInfo.addDeviceExtension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, false,
+ &rtPipelineFeature);
+ contextInfo.addDeviceExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME);
+ contextInfo.addDeviceExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME);
+ contextInfo.addDeviceExtension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
+ contextInfo.addDeviceExtension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
+
+
+ // Creating Vulkan base application
+ nvvk::Context vkctx{};
+ vkctx.initInstance(contextInfo);
+ // Find all compatible devices
+ auto compatibleDevices = vkctx.getCompatibleDevices(contextInfo);
+ assert(!compatibleDevices.empty());
+ // Use a compatible device
+ vkctx.initDevice(compatibleDevices[0], contextInfo);
+
+
+ // Create example
+ HelloVulkan helloVk;
+
+ // Window need to be opened to get the surface on which to draw
+ const vk::SurfaceKHR surface = helloVk.getVkSurface(vkctx.m_instance, window);
+ vkctx.setGCTQueueWithPresent(surface);
+
+ helloVk.setup(vkctx.m_instance, vkctx.m_device, vkctx.m_physicalDevice,
+ vkctx.m_queueGCT.familyIndex);
+ helloVk.createSwapchain(surface, SAMPLE_WIDTH, SAMPLE_HEIGHT);
+ helloVk.createDepthBuffer();
+ helloVk.createRenderPass();
+ helloVk.createFrameBuffers();
+
+ // Setup Imgui
+ helloVk.initGUI(0); // Using sub-pass 0
+
+ // Creation of the example
+ helloVk.loadModel(nvh::findFile("media/scenes/Medieval_building.obj", defaultSearchPaths, true));
+ helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true));
+ helloVk.addLantern({ 8.000f, 1.100f, 3.600f}, {1.0f, 0.0f, 0.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 8.000f, 0.600f, 3.900f}, {0.0f, 1.0f, 0.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 8.000f, 1.100f, 4.400f}, {0.0f, 0.0f, 1.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 1.730f, 1.812f, -1.604f}, {0.0f, 0.4f, 0.4f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 1.730f, 1.862f, 1.916f}, {0.0f, 0.2f, 0.4f}, 0.3f, 3.0f);
+ helloVk.addLantern({-2.000f, 1.900f, -0.700f}, {0.8f, 0.8f, 0.6f}, 0.4f, 3.9f);
+ helloVk.addLantern({ 0.100f, 0.080f, -2.392f}, {1.0f, 0.0f, 1.0f}, 0.5f, 5.0f);
+ helloVk.addLantern({ 1.948f, 0.080f, 0.598f}, {1.0f, 1.0f, 1.0f}, 0.6f, 6.0f);
+ helloVk.addLantern({-2.300f, 0.080f, 2.100f}, {0.0f, 0.7f, 0.0f}, 0.6f, 6.0f);
+ helloVk.addLantern({-1.400f, 4.300f, 0.150f}, {1.0f, 1.0f, 0.0f}, 0.7f, 7.0f);
+
+ helloVk.createOffscreenRender();
+ helloVk.createDescriptorSetLayout();
+ helloVk.createGraphicsPipeline();
+ helloVk.createUniformBuffer();
+ helloVk.createSceneDescriptionBuffer();
+ helloVk.updateDescriptorSet();
+
+ // #VKRay
+ helloVk.initRayTracing();
+ helloVk.createBottomLevelAS();
+ helloVk.createTopLevelAS();
+ helloVk.createLanternIndirectBuffer();
+ helloVk.createRtDescriptorSet();
+ helloVk.createRtPipeline();
+ helloVk.createLanternIndirectDescriptorSet();
+ helloVk.createLanternIndirectCompPipeline();
+ helloVk.createRtShaderBindingTable();
+
+ helloVk.createPostDescriptor();
+ helloVk.createPostPipeline();
+ helloVk.updatePostDescriptorSet();
+
+
+ nvmath::vec4f clearColor = nvmath::vec4f(1, 1, 1, 1.00f);
+ bool useRaytracer = true;
+
+
+ helloVk.setupGlfwCallbacks(window);
+ ImGui_ImplGlfw_InitForVulkan(window, true);
+
+ // Main loop
+ while(!glfwWindowShouldClose(window))
+ {
+ glfwPollEvents();
+ if(helloVk.isMinimized())
+ continue;
+
+ // Start the Dear ImGui frame
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ // Show UI window.
+ if(helloVk.showGui())
+ {
+ ImGuiH::Panel::Begin();
+ ImGui::ColorEdit3("Clear color", reinterpret_cast(&clearColor));
+ ImGui::Checkbox("Ray Tracer mode", &useRaytracer); // Switch between raster and ray tracing
+
+ renderUI(helloVk);
+ ImGui::Text("Application average %.3f ms/frame (%.1f FPS)",
+ 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
+
+ ImGuiH::Control::Info("", "", "(F10) Toggle Pane", ImGuiH::Control::Flags::Disabled);
+ ImGuiH::Panel::End();
+ }
+
+ // Start rendering the scene
+ helloVk.prepareFrame();
+
+ // Start command buffer of this frame
+ auto curFrame = helloVk.getCurFrame();
+ const vk::CommandBuffer& cmdBuf = helloVk.getCommandBuffers()[curFrame];
+
+ cmdBuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
+
+ // Updating camera buffer
+ helloVk.updateUniformBuffer(cmdBuf);
+
+ // Clearing screen
+ vk::ClearValue clearValues[2];
+ clearValues[0].setColor(
+ std::array({clearColor[0], clearColor[1], clearColor[2], clearColor[3]}));
+ clearValues[1].setDepthStencil({1.0f, 0});
+
+ // Offscreen render pass
+ {
+ vk::RenderPassBeginInfo offscreenRenderPassBeginInfo;
+ offscreenRenderPassBeginInfo.setClearValueCount(2);
+ offscreenRenderPassBeginInfo.setPClearValues(clearValues);
+ offscreenRenderPassBeginInfo.setRenderPass(helloVk.m_offscreenRenderPass);
+ offscreenRenderPassBeginInfo.setFramebuffer(helloVk.m_offscreenFramebuffer);
+ offscreenRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()});
+
+ // Rendering Scene
+ if(useRaytracer)
+ {
+ helloVk.raytrace(cmdBuf, clearColor);
+ }
+ else
+ {
+ cmdBuf.beginRenderPass(offscreenRenderPassBeginInfo, vk::SubpassContents::eInline);
+ helloVk.rasterize(cmdBuf);
+ cmdBuf.endRenderPass();
+ }
+ }
+
+ // 2nd rendering pass: tone mapper, UI
+ {
+ vk::RenderPassBeginInfo postRenderPassBeginInfo;
+ postRenderPassBeginInfo.setClearValueCount(2);
+ postRenderPassBeginInfo.setPClearValues(clearValues);
+ postRenderPassBeginInfo.setRenderPass(helloVk.getRenderPass());
+ postRenderPassBeginInfo.setFramebuffer(helloVk.getFramebuffers()[curFrame]);
+ postRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()});
+
+ cmdBuf.beginRenderPass(postRenderPassBeginInfo, vk::SubpassContents::eInline);
+ // Rendering tonemapper
+ helloVk.drawPost(cmdBuf);
+ // Rendering UI
+ ImGui::Render();
+ ImGui::RenderDrawDataVK(cmdBuf, ImGui::GetDrawData());
+ cmdBuf.endRenderPass();
+ }
+
+ // Submit for display
+ cmdBuf.end();
+ helloVk.submitFrame();
+ }
+
+ // Cleanup
+ helloVk.getDevice().waitIdle();
+ helloVk.destroyResources();
+ helloVk.destroy();
+
+ vkctx.deinit();
+
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/LanternIndirectEntry.glsl b/ray_tracing_indirect_scissor/shaders/LanternIndirectEntry.glsl
new file mode 100644
index 0000000..a711cc3
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/LanternIndirectEntry.glsl
@@ -0,0 +1,18 @@
+struct LanternIndirectEntry
+{
+ // VkTraceRaysIndirectCommandKHR
+ int indirectWidth;
+ int indirectHeight;
+ int indirectDepth;
+
+ // Pixel coordinate of scissor rect upper-left.
+ int offsetX;
+ int offsetY;
+
+ // Lantern starts here:
+ // Can't use vec3 due to alignment.
+ float x, y, z;
+ float red, green, blue;
+ float brightness;
+ float radius;
+};
diff --git a/ray_tracing_indirect_scissor/shaders/frag_shader.frag b/ray_tracing_indirect_scissor/shaders/frag_shader.frag
new file mode 100644
index 0000000..d1c55f7
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/frag_shader.frag
@@ -0,0 +1,79 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_EXT_nonuniform_qualifier : enable
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_EXT_scalar_block_layout : enable
+
+#include "wavefront.glsl"
+
+
+layout(push_constant) uniform shaderInformation
+{
+ vec3 lightPosition;
+ uint instanceId;
+ float lightIntensity;
+ int lightType;
+}
+pushC;
+
+// clang-format off
+// Incoming
+//layout(location = 0) flat in int matIndex;
+layout(location = 1) in vec2 fragTexCoord;
+layout(location = 2) in vec3 fragNormal;
+layout(location = 3) in vec3 viewDir;
+layout(location = 4) in vec3 worldPos;
+// Outgoing
+layout(location = 0) out vec4 outColor;
+// Buffers
+layout(binding = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[];
+layout(binding = 2, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc;
+layout(binding = 3) uniform sampler2D[] textureSamplers;
+layout(binding = 4, scalar) buffer MatIndex { int i[]; } matIdx[];
+
+// clang-format on
+
+
+void main()
+{
+ // Object of this instance
+ int objId = scnDesc.i[pushC.instanceId].objId;
+
+ // Material of the object
+ int matIndex = matIdx[nonuniformEXT(objId)].i[gl_PrimitiveID];
+ WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIndex];
+
+ vec3 N = normalize(fragNormal);
+
+ // Vector toward light
+ vec3 L;
+ float lightIntensity = pushC.lightIntensity;
+ if(pushC.lightType == 0)
+ {
+ vec3 lDir = pushC.lightPosition - worldPos;
+ float d = length(lDir);
+ lightIntensity = pushC.lightIntensity / (d * d);
+ L = normalize(lDir);
+ }
+ else
+ {
+ L = normalize(pushC.lightPosition - vec3(0));
+ }
+
+
+ // Diffuse
+ vec3 diffuse = computeDiffuse(mat, L, N);
+ if(mat.textureId >= 0)
+ {
+ int txtOffset = scnDesc.i[pushC.instanceId].txtOffset;
+ uint txtId = txtOffset + mat.textureId;
+ vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz;
+ diffuse *= diffuseTxt;
+ }
+
+ // Specular
+ vec3 specular = computeSpecular(mat, viewDir, L, N);
+
+ // Result
+ outColor = vec4(lightIntensity * (diffuse + specular), 1);
+}
diff --git a/ray_tracing_indirect_scissor/shaders/lantern.rchit b/ray_tracing_indirect_scissor/shaders/lantern.rchit
new file mode 100644
index 0000000..cd80a0a
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/lantern.rchit
@@ -0,0 +1,23 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_EXT_nonuniform_qualifier : enable
+#extension GL_EXT_scalar_block_layout : enable
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// Closest hit shader invoked when a primary ray hits a lantern.
+
+// clang-format off
+layout(location = 0) rayPayloadInEXT hitPayload prd;
+
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+// clang-format on
+
+void main()
+{
+ // Just look up this lantern's color. Self-illuminating, so no lighting calculations.
+ LanternIndirectEntry lantern = lanterns.lanterns[nonuniformEXT(gl_InstanceCustomIndexEXT)];
+ prd.hitValue = vec3(lantern.red, lantern.green, lantern.blue);
+ prd.additiveBlending = false;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/lanternIndirect.comp b/ray_tracing_indirect_scissor/shaders/lanternIndirect.comp
new file mode 100644
index 0000000..c606b0f
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/lanternIndirect.comp
@@ -0,0 +1,163 @@
+#version 460
+#extension GL_GOOGLE_include_directive : enable
+
+// Compute shader for filling in raytrace indirect parameters for each lantern
+// based on the current camera position (passed as view and proj matrix in
+// push constant).
+//
+// Designed to be dispatched with only one work group; it alone fills in
+// the entire lantern array (of length lanternCount, in also push constant).
+
+#define LOCAL_SIZE 128
+layout(local_size_x = LOCAL_SIZE, local_size_y = 1, local_size_z = 1) in;
+
+#include "LanternIndirectEntry.glsl"
+
+layout(binding = 0, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+layout(push_constant) uniform Constants
+{
+ vec4 viewRowX;
+ vec4 viewRowY;
+ vec4 viewRowZ;
+ mat4 proj;
+ float nearZ;
+ int screenX;
+ int screenY;
+ int lanternCount;
+}
+pushC;
+
+// Copy the technique of "2D Polyhedral Bounds of a Clipped,
+// Perspective-Projected 3D Sphere" M. Mara M. McGuire
+// http://jcgt.org/published/0002/02/05/paper.pdf
+// to compute a screen-space rectangle covering the given Lantern's
+// light radius-of-effect. Result is in screen (pixel) coordinates.
+void getScreenCoordBox(in LanternIndirectEntry lantern, out ivec2 lower, out ivec2 upper);
+
+// Use the xyz and radius of lanterns[i] plus the transformation matrices
+// in pushC to fill in the offset and indirect parameters of lanterns[i]
+// (defines the screen rectangle that this lantern's light is bounded in).
+void fillIndirectEntry(int i)
+{
+ LanternIndirectEntry lantern = lanterns.lanterns[i];
+ ivec2 lower, upper;
+ getScreenCoordBox(lantern, lower, upper);
+
+ lanterns.lanterns[i].indirectWidth = max(0, upper.x - lower.x);
+ lanterns.lanterns[i].indirectHeight = max(0, upper.y - lower.y);
+ lanterns.lanterns[i].indirectDepth = 1;
+ lanterns.lanterns[i].offsetX = lower.x;
+ lanterns.lanterns[i].offsetY = lower.y;
+}
+
+void main()
+{
+ for (int i = int(gl_LocalInvocationID.x); i < pushC.lanternCount; i += LOCAL_SIZE)
+ {
+ fillIndirectEntry(i);
+ }
+}
+
+// Functions below modified from the paper.
+float square(float a) { return a*a; }
+
+void getBoundsForAxis(
+ in bool xAxis,
+ in vec3 center,
+ in float radius,
+ in float nearZ,
+ in mat4 projMatrix,
+ out vec3 U,
+ out vec3 L) {
+ bool trivialAccept = (center.z + radius) < nearZ; // Entirely in back of nearPlane (Trivial Accept)
+ vec3 a = xAxis ? vec3(1, 0, 0) : vec3(0, 1, 0);
+
+ // given in coordinates (a,z), where a is in the direction of the vector a, and z is in the standard z direction
+ vec2 projectedCenter = vec2(dot(a, center), center.z);
+ vec2 bounds_az[2];
+ float tSquared = dot(projectedCenter, projectedCenter) - square(radius);
+ float t, cLength, costheta = 0, sintheta = 0;
+
+ if(tSquared > 0) { // Camera is outside sphere
+ // Distance to the tangent points of the sphere (points where a vector from the camera are tangent to the sphere) (calculated a-z space)
+ t = sqrt(tSquared);
+ cLength = length(projectedCenter);
+
+ // Theta is the angle between the vector from the camera to the center of the sphere and the vectors from the camera to the tangent points
+ costheta = t / cLength;
+ sintheta = radius / cLength;
+ }
+ float sqrtPart = 0.0f;
+ if(!trivialAccept) sqrtPart = sqrt(square(radius) - square(nearZ - projectedCenter.y));
+
+ for(int i = 0; i < 2; ++i){
+ if(tSquared > 0) {
+ float x = costheta * projectedCenter.x + -sintheta * projectedCenter.y;
+ float y = sintheta * projectedCenter.x + costheta * projectedCenter.y;
+ bounds_az[i] = costheta * vec2(x, y);
+ }
+
+ if(!trivialAccept && (tSquared <= 0 || bounds_az[i].y > nearZ)) {
+ bounds_az[i].x = projectedCenter.x + sqrtPart;
+ bounds_az[i].y = nearZ;
+ }
+ sintheta *= -1; // negate theta for B
+ sqrtPart *= -1; // negate sqrtPart for B
+ }
+ U = bounds_az[0].x * a;
+ U.z = bounds_az[0].y;
+ L = bounds_az[1].x * a;
+ L.z = bounds_az[1].y;
+}
+
+/** Center is in camera space */
+void getBoundingBox(
+ in vec3 center,
+ in float radius,
+ in float nearZ,
+ in mat4 projMatrix,
+ out vec2 ndc_low,
+ out vec2 ndc_high) {
+ vec3 maxXHomogenous, minXHomogenous, maxYHomogenous, minYHomogenous;
+ getBoundsForAxis(true, center, radius, nearZ, projMatrix, maxXHomogenous, minXHomogenous);
+ getBoundsForAxis(false, center, radius, nearZ, projMatrix, maxYHomogenous, minYHomogenous);
+
+ vec4 projRow0 = vec4(projMatrix[0][0], projMatrix[1][0], projMatrix[2][0], projMatrix[3][0]);
+ vec4 projRow1 = vec4(projMatrix[0][1], projMatrix[1][1], projMatrix[2][1], projMatrix[3][1]);
+ vec4 projRow3 = vec4(projMatrix[0][3], projMatrix[1][3], projMatrix[2][3], projMatrix[3][3]);
+
+ // We only need one coordinate for each point, so we save computation by only calculating x(or y) and w
+ float maxX_w = dot(vec4(maxXHomogenous, 1.0f), projRow3);
+ float minX_w = dot(vec4(minXHomogenous, 1.0f), projRow3);
+ float maxY_w = dot(vec4(maxYHomogenous, 1.0f), projRow3);
+ float minY_w = dot(vec4(minYHomogenous, 1.0f), projRow3);
+
+ float maxX = dot(vec4(maxXHomogenous, 1.0f), projRow0) / maxX_w;
+ float minX = dot(vec4(minXHomogenous, 1.0f), projRow0) / minX_w;
+ float maxY = dot(vec4(maxYHomogenous, 1.0f), projRow1) / maxY_w;
+ float minY = dot(vec4(minYHomogenous, 1.0f), projRow1) / minY_w;
+
+ // Paper minX, etc. names are misleading, not necessarily min. Fix here.
+ ndc_low = vec2(min(minX, maxX), min(minY, maxY));
+ ndc_high = vec2(max(minX, maxX), max(minY, maxY));
+}
+
+void getScreenCoordBox(in LanternIndirectEntry lantern, out ivec2 lower, out ivec2 upper)
+{
+ vec4 lanternWorldCenter = vec4(lantern.x, lantern.y, lantern.z, 1);
+ vec3 center = vec3(
+ dot(pushC.viewRowX, lanternWorldCenter),
+ dot(pushC.viewRowY, lanternWorldCenter),
+ dot(pushC.viewRowZ, lanternWorldCenter));
+ vec2 ndc_low, ndc_high;
+ float paperNearZ = -abs(pushC.nearZ); // Paper expected negative nearZ, took 2 days to figure out!
+ getBoundingBox(center, lantern.radius, paperNearZ, pushC.proj, ndc_low, ndc_high);
+
+ // Convert NDC [-1,+1]^2 coordinates to screen coordinates, and clamp to stay in bounds.
+
+ lower.x = clamp(int((ndc_low.x * 0.5 + 0.5) * pushC.screenX), 0, pushC.screenX);
+ lower.y = clamp(int((ndc_low.y * 0.5 + 0.5) * pushC.screenY), 0, pushC.screenY);
+ upper.x = clamp(int((ndc_high.x * 0.5 + 0.5) * pushC.screenX), 0, pushC.screenX);
+ upper.y = clamp(int((ndc_high.y * 0.5 + 0.5) * pushC.screenY), 0, pushC.screenY);
+}
diff --git a/ray_tracing_indirect_scissor/shaders/lanternShadow.rmiss b/ray_tracing_indirect_scissor/shaders/lanternShadow.rmiss
new file mode 100644
index 0000000..f230893
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/lanternShadow.rmiss
@@ -0,0 +1,12 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+// Miss shader invoked when tracing shadow rays (rays towards lantern)
+// in lantern passes. Misses shouldn't really happen, but if they do,
+// report we did not hit any lantern by setting hitLanternInstance = -1.
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+void main()
+{
+ hitLanternInstance = -1;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/lanternShadowLantern.rchit b/ray_tracing_indirect_scissor/shaders/lanternShadowLantern.rchit
new file mode 100644
index 0000000..62b9afc
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/lanternShadowLantern.rchit
@@ -0,0 +1,18 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// During a lantern pass, this closest hit shader is invoked when
+// shadow rays (rays towards lantern) hit a lantern. Report back
+// which lantern was hit.
+
+// clang-format off
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+// clang-format on
+
+void main()
+{
+ hitLanternInstance = gl_InstanceCustomIndexEXT;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/lanternShadowObj.rchit b/ray_tracing_indirect_scissor/shaders/lanternShadowObj.rchit
new file mode 100644
index 0000000..9ce2b22
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/lanternShadowObj.rchit
@@ -0,0 +1,18 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// During a lantern pass, this closest hit shader is invoked when
+// shadow rays (rays towards lantern) hit a regular OBJ. Report back
+// that no lantern was hit (-1).
+
+// clang-format off
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+// clang-format on
+
+void main()
+{
+ hitLanternInstance = -1;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/passthrough.vert b/ray_tracing_indirect_scissor/shaders/passthrough.vert
new file mode 100644
index 0000000..3e15d82
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/passthrough.vert
@@ -0,0 +1,15 @@
+#version 450
+layout (location = 0) out vec2 outUV;
+
+
+out gl_PerVertex
+{
+ vec4 gl_Position;
+};
+
+
+void main()
+{
+ outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+ gl_Position = vec4(outUV * 2.0f - 1.0f, 1.0f, 1.0f);
+}
diff --git a/ray_tracing_indirect_scissor/shaders/post.frag b/ray_tracing_indirect_scissor/shaders/post.frag
new file mode 100644
index 0000000..b8f30f1
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/post.frag
@@ -0,0 +1,18 @@
+#version 450
+layout(location = 0) in vec2 outUV;
+layout(location = 0) out vec4 fragColor;
+
+layout(set = 0, binding = 0) uniform sampler2D noisyTxt;
+
+layout(push_constant) uniform shaderInformation
+{
+ float aspectRatio;
+}
+pushc;
+
+void main()
+{
+ vec2 uv = outUV;
+ float gamma = 1. / 2.2;
+ fragColor = pow(texture(noisyTxt, uv).rgba, vec4(gamma));
+}
diff --git a/ray_tracing_indirect_scissor/shaders/raycommon.glsl b/ray_tracing_indirect_scissor/shaders/raycommon.glsl
new file mode 100644
index 0000000..4ce2b6d
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/raycommon.glsl
@@ -0,0 +1,20 @@
+#include "LanternIndirectEntry.glsl"
+
+struct hitPayload
+{
+ vec3 hitValue;
+ bool additiveBlending;
+};
+
+layout(push_constant) uniform Constants
+{
+ vec4 clearColor;
+ vec3 lightPosition;
+ float lightIntensity;
+ int lightType; // 0: point, 1: infinite
+ int lanternPassNumber; // -1 if this is the full-screen pass. Otherwise, used to lookup trace indirect parameters.
+ int screenX;
+ int screenY;
+ int lanternDebug;
+}
+pushC;
\ No newline at end of file
diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rchit b/ray_tracing_indirect_scissor/shaders/raytrace.rchit
new file mode 100644
index 0000000..43902de
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/raytrace.rchit
@@ -0,0 +1,175 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_EXT_nonuniform_qualifier : enable
+#extension GL_EXT_scalar_block_layout : enable
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+#include "wavefront.glsl"
+
+hitAttributeEXT vec2 attribs;
+
+// clang-format off
+layout(location = 0) rayPayloadInEXT hitPayload prd;
+layout(location = 1) rayPayloadEXT bool isShadowed;
+layout(location = 2) rayPayloadEXT int hitLanternInstance;
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+layout(binding = 1, set = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[];
+layout(binding = 2, set = 1, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc;
+layout(binding = 3, set = 1) uniform sampler2D textureSamplers[];
+layout(binding = 4, set = 1) buffer MatIndexColorBuffer { int i[]; } matIndex[];
+layout(binding = 5, set = 1, scalar) buffer Vertices { Vertex v[]; } vertices[];
+layout(binding = 6, set = 1) buffer Indices { uint i[]; } indices[];
+
+// clang-format on
+
+void main()
+{
+ // Object of this instance
+ uint objId = scnDesc.i[gl_InstanceCustomIndexEXT].objId;
+
+ // Indices of the triangle
+ ivec3 ind = ivec3(indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 0], //
+ indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 1], //
+ indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 2]); //
+ // Vertex of the triangle
+ Vertex v0 = vertices[nonuniformEXT(objId)].v[ind.x];
+ Vertex v1 = vertices[nonuniformEXT(objId)].v[ind.y];
+ Vertex v2 = vertices[nonuniformEXT(objId)].v[ind.z];
+
+ const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y);
+
+ // Computing the normal at hit position
+ vec3 normal = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z;
+ // Transforming the normal to world space
+ normal = normalize(vec3(scnDesc.i[gl_InstanceCustomIndexEXT].transfoIT * vec4(normal, 0.0)));
+
+
+ // Computing the coordinates of the hit position
+ vec3 worldPos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z;
+ // Transforming the position to world space
+ worldPos = vec3(scnDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0));
+
+ // Vector toward the light
+ vec3 L;
+ vec3 colorIntensity = vec3(pushC.lightIntensity);
+ float lightDistance = 100000.0;
+
+ // ray direction is towards lantern, if in lantern pass.
+ if (pushC.lanternPassNumber >= 0)
+ {
+ LanternIndirectEntry lantern = lanterns.lanterns[pushC.lanternPassNumber];
+ vec3 lDir = vec3(lantern.x, lantern.y, lantern.z) - worldPos;
+ lightDistance = length(lDir);
+ vec3 color = vec3(lantern.red, lantern.green, lantern.blue);
+ // Lantern light decreases linearly. Not physically accurate, but looks good
+ // and avoids a hard "edge" at the radius limit. Use a constant value
+ // if lantern debug is enabled to clearly see the covered screen rectangle.
+ float distanceFade =
+ pushC.lanternDebug != 0
+ ? 0.3
+ : max(0, (lantern.radius - lightDistance) / lantern.radius);
+ colorIntensity = color * lantern.brightness * distanceFade;
+ L = normalize(lDir);
+ }
+ // Non-lantern pass may have point light...
+ else if(pushC.lightType == 0)
+ {
+ vec3 lDir = pushC.lightPosition - worldPos;
+ lightDistance = length(lDir);
+ colorIntensity = vec3(pushC.lightIntensity / (lightDistance * lightDistance));
+ L = normalize(lDir);
+ }
+ else // or directional light.
+ {
+ L = normalize(pushC.lightPosition - vec3(0));
+ }
+
+ // Material of the object
+ int matIdx = matIndex[nonuniformEXT(objId)].i[gl_PrimitiveID];
+ WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIdx];
+
+
+ // Diffuse
+ vec3 diffuse = computeDiffuse(mat, L, normal);
+ if(mat.textureId >= 0)
+ {
+ uint txtId = mat.textureId + scnDesc.i[gl_InstanceCustomIndexEXT].txtOffset;
+ vec2 texCoord =
+ v0.texCoord * barycentrics.x + v1.texCoord * barycentrics.y + v2.texCoord * barycentrics.z;
+ diffuse *= texture(textureSamplers[nonuniformEXT(txtId)], texCoord).xyz;
+ }
+
+ vec3 specular = vec3(0);
+ float attenuation = 1;
+
+ // Tracing shadow ray only if the light is visible from the surface
+ if(dot(normal, L) > 0)
+ {
+ float tMin = 0.001;
+ float tMax = lightDistance;
+ vec3 origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT;
+ vec3 rayDir = L;
+
+ // Ordinary shadow from the simple tutorial.
+ if (pushC.lanternPassNumber < 0) {
+ isShadowed = true;
+ uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT
+ | gl_RayFlagsSkipClosestHitShaderEXT;
+ traceRayEXT(topLevelAS, // acceleration structure
+ flags, // rayFlags
+ 0xFF, // cullMask
+ 0, // sbtRecordOffset
+ 0, // sbtRecordStride
+ 1, // missIndex
+ origin, // ray origin
+ tMin, // ray min range
+ rayDir, // ray direction
+ tMax, // ray max range
+ 1 // payload (location = 1)
+ );
+ }
+ // Lantern shadow ray. Cast a ray towards the lantern whose lighting is being
+ // added this pass. Only the closest hit shader for lanterns will set
+ // hitLanternInstance (payload 2) to non-negative value.
+ else {
+ // Skip ray if no light would be added anyway.
+ if (colorIntensity == vec3(0)) {
+ isShadowed = true;
+ }
+ else {
+ uint flags = gl_RayFlagsOpaqueEXT;
+ hitLanternInstance = -1;
+ traceRayEXT(topLevelAS, // acceleration structure
+ flags, // rayFlags
+ 0xFF, // cullMask
+ 2, // sbtRecordOffset : lantern shadow hit groups start at index 2.
+ 0, // sbtRecordStride
+ 2, // missIndex : lantern shadow miss shader is number 2.
+ origin, // ray origin
+ tMin, // ray min range
+ rayDir, // ray direction
+ tMax, // ray max range
+ 2 // payload (location = 2)
+ );
+ // Did we hit the lantern we expected?
+ isShadowed = (hitLanternInstance != pushC.lanternPassNumber);
+ }
+ }
+
+ if(isShadowed)
+ {
+ attenuation = 0.1;
+ }
+ else
+ {
+ // Specular
+ specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal);
+ }
+ }
+
+ prd.hitValue = colorIntensity * (attenuation * (diffuse + specular));
+ prd.additiveBlending = true;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rgen b/ray_tracing_indirect_scissor/shaders/raytrace.rgen
new file mode 100644
index 0000000..645544a
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/raytrace.rgen
@@ -0,0 +1,71 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 1, set = 0, rgba32f) uniform image2D image;
+
+layout(location = 0) rayPayloadEXT hitPayload prd;
+
+layout(binding = 0, set = 1) uniform CameraProperties
+{
+ mat4 view;
+ mat4 proj;
+ mat4 viewInverse;
+ mat4 projInverse;
+}
+cam;
+
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+void main()
+{
+ // Global light pass is a full screen rectangle (lower corner 0,0), but
+ // lantern passes are only run within rectangles that may be offset.
+ ivec2 pixelOffset = ivec2(0);
+ if (pushC.lanternPassNumber >= 0)
+ {
+ pixelOffset.x = lanterns.lanterns[pushC.lanternPassNumber].offsetX;
+ pixelOffset.y = lanterns.lanterns[pushC.lanternPassNumber].offsetY;
+ }
+
+ const ivec2 pixelIntCoord = ivec2(gl_LaunchIDEXT.xy) + pixelOffset;
+ const vec2 pixelCenter = vec2(pixelIntCoord) + vec2(0.5);
+ const vec2 inUV = pixelCenter / vec2(pushC.screenX, pushC.screenY);
+ vec2 d = inUV * 2.0 - 1.0;
+
+ vec4 origin = cam.viewInverse * vec4(0, 0, 0, 1);
+ vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1);
+ vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0);
+
+ uint rayFlags = gl_RayFlagsOpaqueEXT;
+ float tMin = 0.001;
+ float tMax = 10000.0;
+
+ // Lanterns (self-illuminating) and miss shader (constant background color)
+ // do not use additive blending. Only normal OBJ geometry is additive,
+ // OBJ closest hit sets this to true.
+ prd.additiveBlending = false;
+
+ traceRayEXT(topLevelAS, // acceleration structure
+ rayFlags, // rayFlags
+ 0xFF, // cullMask
+ 0, // sbtRecordOffset
+ 0, // sbtRecordStride
+ 0, // missIndex
+ origin.xyz, // ray origin
+ tMin, // ray min range
+ direction.xyz, // ray direction
+ tMax, // ray max range
+ 0 // payload (location = 0)
+ );
+
+ // Either add to or replace output image color based on prd.additiveBlending.
+ // Global pass always replaces color as it is the first pass.
+ vec3 oldColor = vec3(0);
+ if (prd.additiveBlending && pushC.lanternPassNumber >= 0) {
+ oldColor = imageLoad(image, pixelIntCoord).rgb;
+ }
+ imageStore(image, pixelIntCoord, vec4(prd.hitValue + oldColor, 1.0));
+}
diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rmiss b/ray_tracing_indirect_scissor/shaders/raytrace.rmiss
new file mode 100644
index 0000000..3202aa3
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/raytrace.rmiss
@@ -0,0 +1,12 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+layout(location = 0) rayPayloadInEXT hitPayload prd;
+
+void main()
+{
+ prd.hitValue = pushC.clearColor.xyz * 0.8;
+ prd.additiveBlending = false;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/raytraceShadow.rmiss b/ray_tracing_indirect_scissor/shaders/raytraceShadow.rmiss
new file mode 100644
index 0000000..57be266
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/raytraceShadow.rmiss
@@ -0,0 +1,9 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(location = 1) rayPayloadInEXT bool isShadowed;
+
+void main()
+{
+ isShadowed = false;
+}
diff --git a/ray_tracing_indirect_scissor/shaders/vert_shader.vert b/ray_tracing_indirect_scissor/shaders/vert_shader.vert
new file mode 100644
index 0000000..e358821
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/vert_shader.vert
@@ -0,0 +1,61 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_EXT_scalar_block_layout : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "wavefront.glsl"
+
+// clang-format off
+layout(binding = 2, set = 0, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc;
+// clang-format on
+
+layout(binding = 0) uniform UniformBufferObject
+{
+ mat4 view;
+ mat4 proj;
+ mat4 viewI;
+}
+ubo;
+
+layout(push_constant) uniform shaderInformation
+{
+ vec3 lightPosition;
+ uint instanceId;
+ float lightIntensity;
+ int lightType;
+}
+pushC;
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec3 inColor;
+layout(location = 3) in vec2 inTexCoord;
+
+
+//layout(location = 0) flat out int matIndex;
+layout(location = 1) out vec2 fragTexCoord;
+layout(location = 2) out vec3 fragNormal;
+layout(location = 3) out vec3 viewDir;
+layout(location = 4) out vec3 worldPos;
+
+out gl_PerVertex
+{
+ vec4 gl_Position;
+};
+
+
+void main()
+{
+ mat4 objMatrix = scnDesc.i[pushC.instanceId].transfo;
+ mat4 objMatrixIT = scnDesc.i[pushC.instanceId].transfoIT;
+
+ vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1));
+
+ worldPos = vec3(objMatrix * vec4(inPosition, 1.0));
+ viewDir = vec3(worldPos - origin);
+ fragTexCoord = inTexCoord;
+ fragNormal = vec3(objMatrixIT * vec4(inNormal, 0.0));
+ // matIndex = inMatID;
+
+ gl_Position = ubo.proj * ubo.view * vec4(worldPos, 1.0);
+}
diff --git a/ray_tracing_indirect_scissor/shaders/wavefront.glsl b/ray_tracing_indirect_scissor/shaders/wavefront.glsl
new file mode 100644
index 0000000..d0810fb
--- /dev/null
+++ b/ray_tracing_indirect_scissor/shaders/wavefront.glsl
@@ -0,0 +1,58 @@
+struct Vertex
+{
+ vec3 pos;
+ vec3 nrm;
+ vec3 color;
+ vec2 texCoord;
+};
+
+struct WaveFrontMaterial
+{
+ vec3 ambient;
+ vec3 diffuse;
+ vec3 specular;
+ vec3 transmittance;
+ vec3 emission;
+ float shininess;
+ float ior; // index of refraction
+ float dissolve; // 1 == opaque; 0 == fully transparent
+ int illum; // illumination model (see http://www.fileformat.info/format/material/)
+ int textureId;
+};
+
+struct sceneDesc
+{
+ int objId;
+ int txtOffset;
+ mat4 transfo;
+ mat4 transfoIT;
+};
+
+
+vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal)
+{
+ // Lambertian
+ float dotNL = max(dot(normal, lightDir), 0.0);
+ vec3 c = mat.diffuse * dotNL;
+ if(mat.illum >= 1)
+ c += mat.ambient;
+ return c;
+}
+
+vec3 computeSpecular(WaveFrontMaterial mat, vec3 viewDir, vec3 lightDir, vec3 normal)
+{
+ if(mat.illum < 2)
+ return vec3(0);
+
+ // Compute specular only if not in shadow
+ const float kPi = 3.14159265;
+ const float kShininess = max(mat.shininess, 4.0);
+
+ // Specular
+ const float kEnergyConservation = (2.0 + kShininess) / (2.0 * kPi);
+ vec3 V = normalize(-viewDir);
+ vec3 R = reflect(-lightDir, normal);
+ float specular = kEnergyConservation * pow(max(dot(V, R), 0.0), kShininess);
+
+ return vec3(mat.specular * specular);
+}