cleanup and refactoring

This commit is contained in:
CDaut 2024-05-25 11:53:25 +02:00
parent 2302158928
commit 76f6bf62a4
Signed by: clara
GPG key ID: 223391B52FAD4463
1285 changed files with 757994 additions and 8 deletions

View file

@ -0,0 +1,896 @@
## Table of Contents
- [alloc_vma.hpp](#alloc_vmahpp)
- [appbase_vk.hpp](#appbase_vkhpp)
- [appbase_vkpp.hpp](#appbase_vkpphpp)
- [application.hpp](#applicationhpp)
- [element_benchmark_parameters.hpp](#element_benchmark_parametershpp)
- [element_camera.hpp](#element_camerahpp)
- [element_dbgprintf.hpp](#element_dbgprintfhpp)
- [element_gui.hpp](#element_guihpp)
- [element_inspector.hpp](#element_inspectorhpp)
- [element_logger.hpp](#element_loggerhpp)
- [element_nvml.hpp](#element_nvmlhpp)
- [element_profiler.hpp](#element_profilerhpp)
- [element_testing.hpp](#element_testinghpp)
- [gbuffer.hpp](#gbufferhpp)
- [glsl_compiler.hpp](#glsl_compilerhpp)
- [gltf_scene.hpp](#gltf_scenehpp)
- [gltf_scene_rtx.hpp](#gltf_scene_rtxhpp)
- [gltf_scene_vk.hpp](#gltf_scene_vkhpp)
- [hdr_env.hpp](#hdr_envhpp)
- [hdr_env_dome.hpp](#hdr_env_domehpp)
- [pipeline_container.hpp](#pipeline_containerhpp)
- [scene_camera.hpp](#scene_camerahpp)
- [sky.hpp](#skyhpp)
- [tonemap_postprocess.hpp](#tonemap_postprocesshpp)
## alloc_vma.hpp
### class nvvkhl::AllocVma
> This class is an element of the application that is responsible for the resource allocation. It is using the `VMA` library to allocate buffers, images and acceleration structures.
This allocator uses VMA (Vulkan Memory Allocator) to allocate buffers, images and acceleration structures. It is using the `nvvk::ResourceAllocator` to manage the allocation and deallocation of the resources.
## appbase_vk.hpp
### class nvvkhl::AppBaseVk
nvvkhl::AppBaseVk is used in a few samples, can serve as base class for various needs.
They might differ a bit in setup and functionality, but in principle aid the setup of context and window,
as well as some common event processing.
The nvvkhl::AppBaseVk serves as the base class for many ray tracing examples and makes use of the Vulkan C API.
It does the basics for Vulkan, by holding a reference to the instance and device, but also comes with optional default setups
for the render passes and the swapchain.
## Usage
An example will derive from this class:
```cpp
class VkSample : public AppBaseVk
{
};
```
## Setup
In the `main()` of an application, call `setup()` which is taking a Vulkan instance, device, physical device,
and a queue family index. Setup copies the given Vulkan handles into AppBase, and query the 0th VkQueue of the
specified family, which must support graphics operations and drawing to the surface passed to createSurface.
Furthermore, it is creating a VkCommandPool.
Prior to calling setup, if you are using the `nvvk::Context` class to create and initialize Vulkan instances,
you may want to create a VkSurfaceKHR from the window (glfw for example) and call `setGCTQueueWithPresent()`.
This will make sure the m_queueGCT queue of nvvk::Context can draw to the surface, and m_queueGCT.familyIndex
will meet the requirements of setup().
Creating the swapchain for displaying. Arguments are
width and height, color and depth format, and vsync on/off. Defaults will create the best format for the surface.
Creating framebuffers has a dependency on the renderPass and depth buffer. All those are virtual and can be overridden
in a sample, but default implementation exist.
- createDepthBuffer: creates a 2D depth/stencil image
- createRenderPass : creates a color/depth pass and clear both buffers.
Here is the dependency order:
```cpp
vkSample.createDepthBuffer();
vkSample.createRenderPass();
vkSample.createFrameBuffers();
```cpp
The nvvk::Swapchain will create n images, typically 3. With this information, AppBase is also creating 3 VkFence,
3 VkCommandBuffer and 3 VkFrameBuffer.
### Frame Buffers
The created frame buffers are *display* frame buffers, made to be presented on screen. The frame buffers will be created
using one of the images from swapchain, and a depth buffer. There is only one depth buffer because that resource is not
used simultaneously. For example, when we clear the depth buffer, it is not done immediately, but done through a command
buffer, which will be executed later.
**Note**: the imageView(s) are part of the swapchain.
### Command Buffers
AppBase works with 3 *frame command buffers*. Each frame is filling a command buffer and gets submitted, one after the
other. This is a design choice that can be debated, but makes it simple. I think it is still possible to submit other
command buffers in a frame, but those command buffers will have to be submitted before the *frame* one. The *frame*
command buffer when submitted with submitFrame, will use the current fence.
### Fences
There are as many fences as there are images in the swapchain. At the beginning of a frame, we call prepareFrame().
This is calling the acquire() from nvvk::SwapChain and wait until the image is available. The very first time, the
fence will not stop, but later it will wait until the submit is completed on the GPU.
## ImGui
If the application is using Dear ImGui, there are convenient functions for initializing it and
setting the callbacks (glfw). The first one to call is `initGUI(0)`, where the argument is the subpass
it will be using. Default is 0, but if the application creates a renderpass with multi-sampling and
resolves in the second subpass, this makes it possible.
## Glfw Callbacks
Call `setupGlfwCallbacks(window)` to have all the window callback: key, mouse, window resizing.
By default AppBase will handle resizing of the window and will recreate the images and framebuffers.
If a sample needs to be aware of the resize, it can implement `onResize(width, height)`.
To handle the callbacks in Imgui, call `ImGui_ImplGlfw_InitForVulkan(window, true)`, where true
will handle the default ImGui callback .
**Note**: All the methods are virtual and can be overloaded if they are not doing the typical setup.
```cpp
// Create example
VulkanSample vkSample;
// Window need to be opened to get the surface on which to draw
const VkSurfaceKHR surface = vkSample.getVkSurface(vkctx.m_instance, window);
vkctx.setGCTQueueWithPresent(surface);
vkSample.setup(vkctx.m_instance, vkctx.m_device, vkctx.m_physicalDevice, vkctx.m_queueGCT.familyIndex);
vkSample.createSwapchain(surface, SAMPLE_WIDTH, SAMPLE_HEIGHT);
vkSample.createDepthBuffer();
vkSample.createRenderPass();
vkSample.createFrameBuffers();
vkSample.initGUI(0);
vkSample.setupGlfwCallbacks(window);
ImGui_ImplGlfw_InitForVulkan(window, true);
```
## Drawing loop
The drawing loop in the main() is the typicall loop you will find in glfw examples. Note that
AppBase has a convenient function to tell if the window is minimize, therefore not doing any
work and contain a sleep(), so the CPU is not going crazy.
```cpp
// Window system loop
while(!glfwWindowShouldClose(window))
{
glfwPollEvents();
if(vkSample.isMinimized())
continue;
vkSample.display(); // infinitely drawing
}
```
## Display
A typical display() function will need the following:
* Acquiring the next image: `prepareFrame()`
* Get the command buffer for the frame. There are n command buffers equal to the number of in-flight frames.
* Clearing values
* Start rendering pass
* Drawing
* End rendering
* Submitting frame to display
```cpp
void VkSample::display()
{
// Acquire
prepareFrame();
// Command buffer for current frame
auto curFrame = getCurFrame();
const VkCommandBuffer& cmdBuf = getCommandBuffers()[curFrame];
VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(cmdBuf, &beginInfo);
// Clearing values
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {{1.f, 1.f, 1.f, 1.f}};
clearValues[1].depthStencil = {1.0f, 0};
// Begin rendering
VkRenderPassBeginInfo renderPassBeginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues.data();
renderPassBeginInfo.renderPass = m_renderPass;
renderPassBeginInfo.framebuffer = m_framebuffers[curFram];
renderPassBeginInfo.renderArea = {{0, 0}, m_size};
vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
// .. draw scene ...
// Draw UI
ImGui_ImplVulkan_RenderDrawData( ImGui::GetDrawData(),cmdBuff)
// End rendering
vkCmdEndRenderPass(cmdBuf);
// End of the frame and present the one which is ready
vkEndCommandBuffer(cmdBuf);
submitFrame();
}
```
## Closing
Finally, all resources can be destroyed by calling `destroy()` at the end of main().
```cpp
vkSample.destroy();
```
## appbase_vkpp.hpp
### class nvvkhl::AppBase
nvvkhl::AppBaseVk is the same as nvvkhl::AppBaseVk but makes use of the Vulkan C++ API (`vulkan.hpp`).
## application.hpp
### class nvvk::Application
The Application is basically a small modification of the ImGui example for Vulkan.
Because we support multiple viewports, duplicating the code would be not necessary
and the code is very well explained.
To use the application,
* Fill the ApplicationCreateInfo with all the information, including the Vulkan creation information (nvvk::ContextCreateInfo).
* Attach elements to the application, such as rendering, camera, etc.
* Call run() to start the application.
*
* The application will create the window, the Vulkan context, and the ImGui context.
Worth notice
* ::init() : will create the GLFW window, call nvvk::context for the creation of the
Vulkan context, initialize ImGui , create the surface and window (::setupVulkanWindow)
* ::shutdown() : the oposite of init
* ::run() : while running, render the frame and present the frame. Check for resize, minimize window
and other changes. In the loop, it will call some functions for each 'element' that is connected.
onUIRender, onUIMenu, onRender. See IApplication for details.
* The Application is a singleton, and the main loop is inside the run() function.
* The Application is the owner of the elements, and it will call the onRender, onUIRender, onUIMenu
for each element that is connected to it.
* The Application is the owner of the Vulkan context, and it will create the surface and window.
* The Application is the owner of the ImGui context, and it will create the dockspace and the main menu.
* The Application is the owner of the GLFW window, and it will create the window and handle the events.
The application itself does not render per se. It contains control buffers for the images in flight,
it calls ImGui rendering for Vulkan, but that's it. Note that none of the samples render
directly into the swapchain. Instead, they render into an image, and the image is displayed in the ImGui window
window called "Viewport".
Application elements must be created to render scenes or add "elements" to the application. Several elements
can be added to an application, and each of them will be called during the frame. This allows the application
to be divided into smaller parts, or to reuse elements in various samples. For example, there is an element
that adds a default menu (File/Tools), another that changes the window title with FPS, the resolution, and there
is also an element for our automatic tests.
Each added element will be called in a frame, see the IAppElement interface for information on virtual functions.
Basically there is a call to create and destroy, a call to render the user interface and a call to render the
frame with the command buffer.
Note: order of Elements can be important if one depends on the other. For example, the ElementCamera should
be added before the rendering sample, such that its matrices are updated before pulled by the renderer.
## element_benchmark_parameters.hpp
### class nvvkhl::ElementBenchmarkParameters
This element allows you to control an application with command line parameters. There are default
parameters, but others can be added using the parameterLists().add(..) function.
It can also use a file containing several sets of parameters, separated by "benchmark" and
which can be used to benchmark an application.
If a profiler is set, the measured performance at the end of each benchmark group is logged.
There are default parameters that can be used:
-logfile Set a logfile.txt. If string contains $DEVICE$ it will be replaced by the GPU device name
-winsize Set window size (width and height)
-winpos Set window position (x and y)
-vsync Enable or disable vsync
-screenshot Save a screenshot into this file
-benchmarkframes Set number of benchmarkframes
-benchmark Set benchmark filename
-test Enabling Testing
-test-frames If test is on, number of frames to run
-test-time If test is on, time that test will run
Example of Setup:
```cpp
std::shared_ptr<nvvkhl::ElementBenchmarkParameters> g_benchmark;
std::shared_ptr<nvvkhl::ElementProfiler> g_profiler;
main() {
...
g_benchmark = std::make_shared<nvvkhl::ElementBenchmarkParameters>(argc, argv);
g_profiler = std::make_shared<nvvkhl::ElementProfiler>(false);
g_benchmark->setProfiler(g_profiler);
app->addElement(g_profiler);
app->addElement(g_benchmark);
...
}
```
Applications can also get their parameters modified:
```cpp
void MySample::MySample() {
g_benchmark->parameterLists().add("speed|The speed", &m_speed);
g_benchmark->parameterLists().add("color", &m_color, nullptr, 3);
g_benchmark->parameterLists().add("complex", &m_complex, [&](int p){ doSomething(); });
```cpp
Example of a benchmark.txt could look like
\code{.bat}
#how many frames to average
-benchmarkframes 12
-winpos 10 10
-winsize 500 500
benchmark "No vsync"
-vsync 0
-benchmarkframes 100
-winpos 500 500
-winsize 100 100
benchmark "Image only"
-screenshot "temporal_mdi.jpg"
```
## element_camera.hpp
### class nvvkhl::ElementCamera
This class is an element of the application that is responsible for the camera manipulation. It is using the `nvh::CameraManipulator` to handle the camera movement and interaction.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
## element_dbgprintf.hpp
### class nvvkhl::ElementDbgPrintf
> This class is an element of the application that is responsible for the debug printf in the shader. It is using the `VK_EXT_debug_printf` extension to print information from the shader.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
Create the element such that it will be available to the target application
- Example:
```cpp
std::shared_ptr<nvvkhl::ElementDbgPrintf> g_dbgPrintf = std::make_shared<nvvkhl::ElementDbgPrintf>();
```
Add to main
- Before creating the nvvkhl::Application, set:
` spec.vkSetup.instanceCreateInfoExt = g_dbgPrintf->getFeatures(); `
- Add the Element to the Application
` app->addElement(g_dbgPrintf); `
- In the target application, push the mouse coordinated
` m_pushConst.mouseCoord = g_dbgPrintf->getMouseCoord(); `
In the Shader, do:
- Add the extension
` #extension GL_EXT_debug_printf : enable `
- Where to get the information
```cpp
ivec2 fragCoord = ivec2(floor(gl_FragCoord.xy));
if(fragCoord == ivec2(pushC.mouseCoord))
debugPrintfEXT("Value: %f\n", myVal);
```
## element_gui.hpp
### class nvvkhl::ElementDefaultMenu
> This class is an element of the application that is responsible for the default menu of the application. It is using the `ImGui` library to create a menu with File/Exit and View/V-Sync.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
### class nvvkhl::ElementDefaultWindowTitle
> This class is an element of the application that is responsible for the default window title of the application. It is using the `GLFW` library to set the window title with the application name, the size of the window and the frame rate.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
## element_inspector.hpp
### class nvvkhl::ElementInspector
--------------------------------------------------------------------------------------------------
This element is used to facilitate GPU debugging by inspection of:
- Image contents
- Buffer contents
- Variables in compute shaders
- Variables in fragment shaders
IMPORTANT NOTE: if used in a multi threaded environment synchronization needs to be performed
externally by the application.
Basic usage:
------------------------------------------------------------------------------------------------
### INITIALIZATION
------------------------------------------------------------------------------------------------
Create the element as a global variable, and add it to the applications
```cpp
std::shared_ptr<ElementInspector> g_inspectorElement = std::make_shared<ElementInspector>();
void main(...)
{
...
app->addElement(g_inspectorElement);
...
}
```
Upon attachment of the main app element, initialize the Inspector and specify the number of
buffers, images, compute shader variables and fragment shader variables that it will need to
inspect
```cpp
void onAttach(nvvkhl::Application* app) override
{
...
ElementInspector::InitInfo initInfo{};
initInfo.allocator = m_alloc.get();
initInfo.imageCount = imageInspectionCount;
initInfo.bufferCount = bufferInspectionCount;
initInfo.computeCount = computeInspectionCount;
initInfo.fragmentCount = fragmentInspectionCount;
initInfo.customCount = customInspectionCount;
initInfo.device = m_app->getDevice();
initInfo.graphicsQueueFamilyIndex = m_app->getQueueGCT().familyIndex;
g_inspectorElement->init(initInfo);
...
}
```
------------------------------------------------------------------------------------------------
### BUFFER INSPECTION
------------------------------------------------------------------------------------------------
Each inspection needs to be initialized before use:
Inspect a buffer of size bufferSize, where each entry contains 5 values. The buffer format specifies the data
structure of the entries. The following format is the equivalent of
```cpp
// struct
// {
// uint32_t counterU32;
// float counterF32;
// int16_t anI16Value;
// uint16_t myU16;
// int32_t anI32;
// };
```
```cpp
bufferFormat = std::vector<ElementInspector::ValueFormat>(5);
bufferFormat[0] = {ElementInspector::eUint32, "counterU32"};
bufferFormat[1] = {ElementInspector::eFloat32, "counterF32"};
bufferFormat[2] = {ElementInspector::eInt16, "anI16Value"};
bufferFormat[3] = {ElementInspector::eUint16, "myU16"};
bufferFormat[4] = {ElementInspector::eInt32, "anI32"};
ElementInspector::BufferInspectionInfo info{};
info.entryCount = bufferSize;
info.format = bufferFormat;
info.name = "myBuffer";
info.sourceBuffer = m_buffer.buffer;
g_inspectorElement->initBufferInspection(0, info);
```
When the inspection is desired, simply add it to the current command buffer. Required barriers are added internally.
IMPORTANT: the buffer MUST have been created with the VK_BUFFER_USAGE_TRANSFER_SRC_BIT flag
```cpp
g_inspectorElement->inspectBuffer(cmd, 0);
```
------------------------------------------------------------------------------------------------
### IMAGE INSPECTION
------------------------------------------------------------------------------------------------
Inspection of the image stored in m_texture, with format RGBA32F. Other formats can be specified using the syntax
above
```cpp
ElementInspector::ImageInspectionInfo info{};
info.createInfo = create_info;
info.format = g_inspectorElement->formatRGBA32();
info.name = "MyImageInspection";
info.sourceImage = m_texture.image;
g_inspectorElement->initImageInspection(0, info);
```
When the inspection is desired, simply add it to the current command buffer. Required barriers are added internally.
```cpp
g_inspectorElement->inspectImage(cmd, 0, imageCurrentLayout);
```
------------------------------------------------------------------------------------------------
### COMPUTE SHADER VARIABLE INSPECTION
------------------------------------------------------------------------------------------------
Inspect a compute shader variable for a given 3D grid and block size (use 1 for unused dimensions). This mode applies
to shaders where invocation IDs (e.g. gl_LocalInvocationID) are defined, such as compute, mesh and task shaders.
Since grids may contain many threads capturing a variable for all threads
may incur large memory consumption and performance loss. The blocks to inspect, and the warps within those blocks can
be specified using inspectedMin/MaxBlocks and inspectedMin/MaxWarp.
```cpp
computeInspectionFormat = std::vector<ElementInspector::ValueFormat>(...);
ElementInspector::ComputeInspectionInfo info{}; info.blockSize = blockSize;
// Create a 4-component vector format where each component is a uint32_t. The components will be labeled myVec4u.x,
// myVec4u.y, myVec4u.z, myVec4u.w in the UI
info.format = ElementInspector::formatVector4(eUint32, "myVec4u");
info.gridSizeInBlocks = gridSize;
info.minBlock = inspectedMinBlock;
info.maxBlock = inspectedMaxBlock;
info.minWarp = inspectedMinWarp;
info.maxWarp = inspectedMaxWarp;
info.name = "My Compute Inspection";
g_inspectorElement->initComputeInspection(0, info);
```
To allow variable inspection two buffers need to be made available to the target shader:
m_computeShader.updateBufferBinding(eThreadInspection, g_inspectorElement->getComputeInspectionBuffer(0));
m_computeShader.updateBufferBinding(eThreadMetadata, g_inspectorElement->getComputeMetadataBuffer(0));
The shader code needs to indicate include the Inspector header along with preprocessor variables to set the
inspection mode to Compute, and indicate the binding points for the buffers:
```cpp
#define INSPECTOR_MODE_COMPUTE
#define INSPECTOR_DESCRIPTOR_SET 0
#define INSPECTOR_INSPECTION_DATA_BINDING 1
#define INSPECTOR_METADATA_BINDING 2
#include "dh_inspector.h"
```
The inspection of a variable is then done as follows. For alignment purposes the inspection is done with a 32-bit
granularity. The shader is responsible for packing the inspected variables in 32-bit uint words. Those will be
unpacked within the Inspector for display according to the specified format.
```cpp
uint32_t myVariable = myFunction(...);
inspect32BitValue(0, myVariable);
```
The inspection is triggered on the host side right after the compute shader invocation:
```cpp
m_computeShader.dispatchBlocks(cmd, computGridSize, &constants);
g_inspectorElement->inspectComputeVariables(cmd, 0);
```
------------------------------------------------------------------------------------------------
### FRAGMENT SHADER VARIABLE INSPECTION
------------------------------------------------------------------------------------------------
Inspect a fragment shader variable for a given output image resolution. Since the image may have high resolution
capturing a variable for all threads may incur large memory consumption and performance loss. The bounding box of the
fragments to inspect can be specified using inspectedMin/MaxCoord.
IMPORTANT: Overlapping geometry may trigger
several fragment shader invocations for a given pixel. The inspection will only store the value of the foremost
fragment (with the lowest gl_FragCoord.z).
```cpp
fragmentInspectionFormat = std::vector<ElementInspector::ValueFormat>(...);
FragmentInspectionInfo info{};
info.name = "My Fragment Inspection";
info.format = fragmentInspectionFormat;
info.renderSize = imageSize;
info.minFragment = inspectedMinCoord;
info.maxFragment = inspectedMaxCoord;
g_inspectorElement->initFragmentInspection(0, info);
```
To allow variable inspection two storage buffers need to be declared in the pipeline layout and made available
as follows:
```cpp
std::vector<VkWriteDescriptorSet> writes;
const VkDescriptorBufferInfo inspectorInspection{
g_inspectorElement->getFragmentInspectionBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 1, &inspectorInspection));
const VkDescriptorBufferInfo inspectorMetadata{
g_inspectorElement->getFragmentMetadataBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 2, &inspectorMetadata));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
```
The shader code needs to indicate include the Inspector header along with preprocessor variables to set the
inspection mode to Fragment, and indicate the binding points for the buffers:
```cpp
#define INSPECTOR_MODE_FRAGMENT
#define INSPECTOR_DESCRIPTOR_SET 0
#define INSPECTOR_INSPECTION_DATA_BINDING 1
#define INSPECTOR_METADATA_BINDING 2
#include "dh_inspector.h"
```
The inspection of a variable is then done as follows. For alignment purposes the inspection is done with a 32-bit
granularity. The shader is responsible for packing the inspected variables in 32-bit uint words. Those will be
unpacked within the Inspector for display according to the specified format.
```cpp
uint32_t myVariable = myFunction(...);
inspect32BitValue(0, myVariable);
```
The inspection data for a pixel will only be written if a fragment actually covers that pixel. To avoid ghosting
where no fragments are rendered it is useful to clear the inspection data before rendering:
```cpp
g_inspectorElement->clearFragmentVariables(cmd, 0);
vkCmdBeginRendering(...);
```
The inspection is triggered on the host side right after rendering:
```cpp
vkCmdEndRendering(cmd);
g_inspectorElement->inspectFragmentVariables(cmd, 0);
```
------------------------------------------------------------------------------------------------
### CUSTOM SHADER VARIABLE INSPECTION
------------------------------------------------------------------------------------------------
In case some in-shader data has to be inspected in other shader types, or not on a once-per-thread basis, the custom
inspection mode can be used. This mode allows the user to specify the overall size of the generated data as well as
an effective inspection window. This mode may be used in conjunction with the COMPUTE and FRAGMENT modes.
std::vector<ElementInspector::ValueFormat> customCaptureFormat;
```cpp
...
ElementInspector::CustomInspectionInfo info{};
info.extent = totalInspectionSize;
info.format = customCaptureFormat;
info.minCoord = inspectionWindowMin;
info.maxCoord = inspectionWindowMax;
info.name = "My Custom Capture";
g_inspectorElement->initCustomInspection(0, info);
```
To allow variable inspection two buffers need to be made available to the target pipeline:
```cpp
std::vector<VkWriteDescriptorSet> writes;
const VkDescriptorBufferInfo inspectorInspection{
g_inspectorElement->getCustomInspectionBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 1, &inspectorInspection));
const VkDescriptorBufferInfo inspectorMetadata{
g_inspectorElement->getCustomMetadataBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 2, &inspectorMetadata));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
```
The shader code needs to indicate include the Inspector header along with preprocessor variables to set the
inspection mode to Fragment, and indicate the binding points for the buffers:
```cpp
#define INSPECTOR_MODE_CUSTOM
#define INSPECTOR_DESCRIPTOR_SET 0
#define INSPECTOR_CUSTOM_INSPECTION_DATA_BINDING 1
#define INSPECTOR_CUSTOM_METADATA_BINDING 2
#include "dh_inspector.h"
```
The inspection of a variable is then done as follows. For alignment purposes the inspection is done with a 32-bit
granularity. The shader is responsible for packing the inspected variables in 32-bit uint words. Those will be
unpacked within the Inspector for display according to the specified format.
```cpp
uint32_t myVariable = myFunction(...);
inspectCustom32BitValue(0, myCoordinates, myVariable);
```
The inspection is triggered on the host side right after running the pipeline:
```cpp
g_inspectorElement->inspectCustomVariables(cmd, 0);
```
## element_logger.hpp
### class nvvkhl::ElementLogger
> This class is an element of the application that can redirect all logs to a ImGui window in the application
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
Create the element such that it will be available to the target application
Example:
```cpp
static nvvkhl::SampleAppLog g_logger;
nvprintSetCallback([](int level, const char* fmt)
{
g_logger.addLog("%s", fmt);
});
app->addElement(std::make_unique<nvvkhl::ElementLogger>(&g_logger, true)); // Add logger window
```
## element_nvml.hpp
### class nvvkhl::ElementNvml
> This class is an element of the application that is responsible for the NVML monitoring. It is using the `NVML` library to get information about the GPU and display it in the application.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
## element_profiler.hpp
### class nvvkhl::ElementProfiler
> This class is an element of the application that is responsible for the profiling of the application. It is using the `nvvk::ProfilerVK` to profile the time parts of the computation done on the GPU.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
The profiler element, is there to help profiling the time parts of the
computation is done on the GPU. To use it, follow those simple steps
In the main() program, create an instance of the profiler and add it to the
nvvkhl::Application
```cpp
std::shared_ptr<nvvkhl::ElementProfiler> profiler = std::make_shared<nvvkhl::ElementProfiler>();
app->addElement(profiler);
```
In the application where profiling needs to be done, add profiling sections
```cpp
void mySample::onRender(VkCommandBuffer cmd)
{
auto sec = m_profiler->timeRecurring(__FUNCTION__, cmd);
...
// Subsection
{
auto sec = m_profiler->timeRecurring("Dispatch", cmd);
vkCmdDispatch(cmd, (size.width + (GROUP_SIZE - 1)) / GROUP_SIZE, (size.height + (GROUP_SIZE - 1)) / GROUP_SIZE, 1);
}
...
```
This is it and the execution time on the GPU for each part will be showing in the Profiler window.
## element_testing.hpp
> Todo: Add documentation
## gbuffer.hpp
### class nvvkhl::GBuffer
> This class is an help for creating GBuffers.
This can be use to create a GBuffer with multiple color images and a depth image. The GBuffer can be used to render the scene in multiple passes, such as deferred rendering.
To use this class, you need to create it and call the `create` method to create the GBuffer. The `create` method will create the images and the descriptor set for the GBuffer. The `destroy` method will destroy the images and the descriptor set.
Note: the `getDescriptorSet` method can be use to display the image in ImGui. Ex: `ImGui::Image((ImTextureID)gbuffer.getDescriptorSet(), ImVec2(128, 128));`
## glsl_compiler.hpp
### class nvvkhl::GlslCompiler
> This class is a wrapper around the shaderc compiler to help compiling GLSL to Spir-V using shaderC
## gltf_scene.hpp
### class nvvkhl::Scene
> This class is responsible for loading and managing a glTF scene.
It is using the `nvh::GltfScene` to load and manage the scene and `tinygltf` to parse the glTF file.
## gltf_scene_rtx.hpp
### class nvvkhl::SceneRtx
> This class is responsible for the ray tracing acceleration structure.
It is using the `nvvkhl::Scene` and `nvvkhl::SceneVk` information to create the acceleration structure.
## gltf_scene_vk.hpp
### class nvvkhl::SceneVk
> This class is responsible for the Vulkan version of the scene.
It is using `nvvkhl::Scene` to create the Vulkan buffers and images.
## hdr_env.hpp
### class nvvkhl::HdrEnv
> Load an environment image (HDR) and create an acceleration structure for important light sampling.
## hdr_env_dome.hpp
### class nvvkhl::HdrEnvDome
> Use an environment image (HDR) and create the cubic textures for glossy reflection and diffuse illumination. It also has the ability to render the HDR environment, in the background of an image.
Using 4 compute shaders
- hdr_dome: to make the HDR as background
- hdr_integrate_brdf : generate the BRDF lookup table
- hdr_prefilter_diffuse : integrate the diffuse contribution in a cubemap
- hdr_prefilter_glossy : integrate the glossy reflection in a cubemap
## pipeline_container.hpp
### struct nvvkhl::PipelineContainer
> Small multi-pipeline container
## scene_camera.hpp
### Function nvvkhl::setCameraFromScene
> Set the camera from the scene, if no camera is found, it will fit the camera to the scene.
## sky.hpp
### class nvvkhl::SkyDome
> This class is responsible for the sky dome.
This class can render a sky dome with a sun, for both the rasterizer and the ray tracer.
The `draw` method is responsible for rendering the sky dome for the rasterizer. For ray tracing, there is no need to call this method, as the sky dome is part of the ray tracing shaders (see shaders/dh_sky.h).
## tonemap_postprocess.hpp
### class nvvkhl::TonemapperPostProcess
> This class is meant to be use for displaying the final image rendered in linear space (sRGB).
There are two ways to use it, one which is graphic, the other is compute.
- The graphic will render a full screen quad with the input image. It is to the
application to set the rendering target ( -> G-Buffer0 )
- The compute takes an image as input and write to an another one using a compute shader
- It is either one or the other, both rendering aren't needed to post-process. If both are provided
it is for convenience.
Note: It is important in any cases to place a barrier if there is a transition from
fragment to compute and compute to fragment to avoid missing results.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <memory>
#include "vk_mem_alloc.h"
#include "nvvk/resourceallocator_vk.hpp"
#include "nvvk/context_vk.hpp"
#include "nvvk/memallocator_vma_vk.hpp"
namespace nvvkhl {
/** @DOC_START
# class nvvkhl::AllocVma
> This class is an element of the application that is responsible for the resource allocation. It is using the `VMA` library to allocate buffers, images and acceleration structures.
This allocator uses VMA (Vulkan Memory Allocator) to allocate buffers, images and acceleration structures. It is using the `nvvk::ResourceAllocator` to manage the allocation and deallocation of the resources.
@DOC_END */
class AllocVma : public nvvk::ResourceAllocator
{
public:
explicit AllocVma(const nvvk::Context* context)
{
VmaAllocatorCreateInfo allocator_info = {};
allocator_info.physicalDevice = context->m_physicalDevice;
allocator_info.device = context->m_device;
allocator_info.instance = context->m_instance;
allocator_info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
init(allocator_info);
}
explicit AllocVma(const VmaAllocatorCreateInfo& allocatorInfo) { init(allocatorInfo); }
~AllocVma() override { deinit(); }
// Use the following to trace
// #define VMA_DEBUG_LOG(format, ...) do { printf(format, __VA_ARGS__); printf("\n"); } while(false)
void findLeak(uint64_t leak) { m_vma->findLeak(leak); }
VmaAllocator vma() { return m_vmaAlloc; }
private:
void init(const VmaAllocatorCreateInfo& allocatorInfo)
{
vmaCreateAllocator(&allocatorInfo, &m_vmaAlloc);
m_vma = std::make_unique<nvvk::VMAMemoryAllocator>(allocatorInfo.device, allocatorInfo.physicalDevice, m_vmaAlloc);
nvvk::ResourceAllocator::init(allocatorInfo.device, allocatorInfo.physicalDevice, m_vma.get());
}
void deinit()
{
releaseStaging();
vmaDestroyAllocator(m_vmaAlloc);
m_vmaAlloc = nullptr;
m_vma->deinit();
nvvk::ResourceAllocator::deinit();
}
std::unique_ptr<nvvk::VMAMemoryAllocator> m_vma; // The memory allocator
VmaAllocator m_vmaAlloc{};
};
} // namespace nvvkhl

View file

@ -0,0 +1,852 @@
/*
* Copyright (c) 2021-2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "nvvkhl/appbase_vk.hpp"
#include "nvp/perproject_globals.hpp"
// Imgui
#include <imgui.h>
#include <backends/imgui_impl_glfw.h>
#include <backends/imgui_impl_vulkan.h>
#include "imgui/imgui_camera_widget.h"
#include "imgui/imgui_helper.h"
//--------------------------------------------------------------------------------------------------
// Creation order of all elements for the application
// First keep the Vulkan instance, device, ... in class members
// Then create the swapchain, a depth buffer, a default render pass and the
// framebuffers for the swapchain (all sharing the depth image)
// Initialize Imgui and setup callback functions for windows operations (mouse, key, ...)
void nvvkhl::AppBaseVk::create(const AppBaseVkCreateInfo& info)
{
m_useDynamicRendering = info.useDynamicRendering;
setup(info.instance, info.device, info.physicalDevice, info.queueIndices[0]);
createSwapchain(info.surface, info.size.width, info.size.height, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_UNDEFINED, info.useVsync);
createDepthBuffer();
createRenderPass();
createFrameBuffers();
initGUI();
setupGlfwCallbacks(info.window);
ImGui_ImplGlfw_InitForVulkan(info.window, true);
}
//--------------------------------------------------------------------------------------------------
// Setup the low level Vulkan for various operations
//
void nvvkhl::AppBaseVk::setup(const VkInstance& instance, const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t graphicsQueueIndex)
{
m_instance = instance;
m_device = device;
m_physicalDevice = physicalDevice;
m_graphicsQueueIndex = graphicsQueueIndex;
vkGetDeviceQueue(m_device, m_graphicsQueueIndex, 0, &m_queue);
VkCommandPoolCreateInfo poolCreateInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
vkCreateCommandPool(m_device, &poolCreateInfo, nullptr, &m_cmdPool);
VkPipelineCacheCreateInfo pipelineCacheInfo{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
vkCreatePipelineCache(m_device, &pipelineCacheInfo, nullptr, &m_pipelineCache);
ImGuiH::SetCameraJsonFile(getProjectName());
}
//--------------------------------------------------------------------------------------------------
// To call on exit
//
void nvvkhl::AppBaseVk::destroy()
{
vkDeviceWaitIdle(m_device);
if(ImGui::GetCurrentContext() != nullptr)
{
// In case multiple ImGUI contexts are used in the same application, the VK side may not own ImGui resources
if(ImGui::GetIO().BackendRendererUserData)
ImGui_ImplVulkan_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
if(!m_useDynamicRendering)
vkDestroyRenderPass(m_device, m_renderPass, nullptr);
vkDestroyImageView(m_device, m_depthView, nullptr);
vkDestroyImage(m_device, m_depthImage, nullptr);
vkFreeMemory(m_device, m_depthMemory, nullptr);
vkDestroyPipelineCache(m_device, m_pipelineCache, nullptr);
for(uint32_t i = 0; i < m_swapChain.getImageCount(); i++)
{
vkDestroyFence(m_device, m_waitFences[i], nullptr);
if(!m_useDynamicRendering)
vkDestroyFramebuffer(m_device, m_framebuffers[i], nullptr);
vkFreeCommandBuffers(m_device, m_cmdPool, 1, &m_commandBuffers[i]);
}
m_swapChain.deinit();
vkDestroyDescriptorPool(m_device, m_imguiDescPool, nullptr);
vkDestroyCommandPool(m_device, m_cmdPool, nullptr);
if(m_surface)
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Return the surface "screen" for the display
//
VkSurfaceKHR nvvkhl::AppBaseVk::getVkSurface(const VkInstance& instance, GLFWwindow* window)
{
assert(instance);
m_window = window;
VkSurfaceKHR surface{};
VkResult err = glfwCreateWindowSurface(instance, window, nullptr, &surface);
if(err != VK_SUCCESS)
assert(!"Failed to create a Window surface");
m_surface = surface;
return surface;
}
//--------------------------------------------------------------------------------------------------
// Creating the surface for rendering
//
void nvvkhl::AppBaseVk::createSwapchain(const VkSurfaceKHR& surface,
uint32_t width,
uint32_t height,
VkFormat colorFormat /*= VK_FORMAT_B8G8R8A8_UNORM*/,
VkFormat depthFormat /*= VK_FORMAT_UNDEFINED*/,
bool vsync /*= false*/)
{
m_size = VkExtent2D{width, height};
m_colorFormat = colorFormat;
m_depthFormat = depthFormat;
m_vsync = vsync;
// Find the most suitable depth format
if(m_depthFormat == VK_FORMAT_UNDEFINED)
{
auto feature = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
for(const auto& f : {VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT})
{
VkFormatProperties formatProp{VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, f, &formatProp);
if((formatProp.optimalTilingFeatures & feature) == feature)
{
m_depthFormat = f;
break;
}
}
}
m_swapChain.init(m_device, m_physicalDevice, m_queue, m_graphicsQueueIndex, surface, static_cast<VkFormat>(colorFormat));
m_size = m_swapChain.update(m_size.width, m_size.height, vsync);
m_colorFormat = static_cast<VkFormat>(m_swapChain.getFormat());
// Create Synchronization Primitives
m_waitFences.resize(m_swapChain.getImageCount());
for(auto& fence : m_waitFences)
{
VkFenceCreateInfo fenceCreateInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
vkCreateFence(m_device, &fenceCreateInfo, nullptr, &fence);
}
// Command buffers store a reference to the frame buffer inside their render pass info
// so for static usage without having to rebuild them each frame, we use one per frame buffer
VkCommandBufferAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
allocateInfo.commandPool = m_cmdPool;
allocateInfo.commandBufferCount = m_swapChain.getImageCount();
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
m_commandBuffers.resize(m_swapChain.getImageCount());
vkAllocateCommandBuffers(m_device, &allocateInfo, m_commandBuffers.data());
auto cmdBuffer = createTempCmdBuffer();
m_swapChain.cmdUpdateBarriers(cmdBuffer);
submitTempCmdBuffer(cmdBuffer);
#ifndef NDEBUG
for(size_t i = 0; i < m_commandBuffers.size(); i++)
{
std::string name = std::string("AppBase") + std::to_string(i);
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
nameInfo.objectHandle = (uint64_t)m_commandBuffers[i];
nameInfo.objectType = VK_OBJECT_TYPE_COMMAND_BUFFER;
nameInfo.pObjectName = name.c_str();
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
}
#endif // !NDEBUG
// Setup camera
CameraManip.setWindowSize(m_size.width, m_size.height);
}
//--------------------------------------------------------------------------------------------------
// Create all the framebuffers in which the image will be rendered
// - Swapchain need to be created before calling this
//
void nvvkhl::AppBaseVk::createFrameBuffers()
{
if(m_useDynamicRendering)
return;
// Recreate the frame buffers
for(auto framebuffer : m_framebuffers)
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
// Array of attachment (color, depth)
std::array<VkImageView, 2> attachments{};
// Create frame buffers for every swap chain image
VkFramebufferCreateInfo framebufferCreateInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
framebufferCreateInfo.renderPass = m_renderPass;
framebufferCreateInfo.attachmentCount = 2;
framebufferCreateInfo.width = m_size.width;
framebufferCreateInfo.height = m_size.height;
framebufferCreateInfo.layers = 1;
framebufferCreateInfo.pAttachments = attachments.data();
// Create frame buffers for every swap chain image
m_framebuffers.resize(m_swapChain.getImageCount());
for(uint32_t i = 0; i < m_swapChain.getImageCount(); i++)
{
attachments[0] = m_swapChain.getImageView(i);
attachments[1] = m_depthView;
vkCreateFramebuffer(m_device, &framebufferCreateInfo, nullptr, &m_framebuffers[i]);
}
#ifndef NDEBUG
for(size_t i = 0; i < m_framebuffers.size(); i++)
{
std::string name = std::string("AppBase") + std::to_string(i);
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
nameInfo.objectHandle = (uint64_t)m_framebuffers[i];
nameInfo.objectType = VK_OBJECT_TYPE_FRAMEBUFFER;
nameInfo.pObjectName = name.c_str();
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
}
#endif // !NDEBUG
}
//--------------------------------------------------------------------------------------------------
// Creating a default render pass, very simple one.
// Other examples will mostly override this one.
//
void nvvkhl::AppBaseVk::createRenderPass()
{
if(m_useDynamicRendering)
return;
if(m_renderPass)
vkDestroyRenderPass(m_device, m_renderPass, nullptr);
std::array<VkAttachmentDescription, 2> attachments{};
// Color attachment
attachments[0].format = m_colorFormat;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
// Depth attachment
attachments[1].format = m_depthFormat;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
// One color, one depth
const VkAttachmentReference colorReference{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
const VkAttachmentReference depthReference{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
std::array<VkSubpassDependency, 1> subpassDependencies{};
// Transition from final to initial (VK_SUBPASS_EXTERNAL refers to all commands executed outside of the actual renderpass)
subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
subpassDependencies[0].dstSubpass = 0;
subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpassDependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
subpassDependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
VkSubpassDescription subpassDescription{};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = 1;
subpassDescription.pColorAttachments = &colorReference;
subpassDescription.pDepthStencilAttachment = &depthReference;
VkRenderPassCreateInfo renderPassInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpassDescription;
renderPassInfo.dependencyCount = static_cast<uint32_t>(subpassDependencies.size());
renderPassInfo.pDependencies = subpassDependencies.data();
vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass);
#ifndef NDEBUG
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
nameInfo.objectHandle = (uint64_t)m_renderPass;
nameInfo.objectType = VK_OBJECT_TYPE_RENDER_PASS;
nameInfo.pObjectName = R"(AppBaseVk)";
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
#endif // !NDEBUG
}
//--------------------------------------------------------------------------------------------------
// Creating an image to be used as depth buffer
//
void nvvkhl::AppBaseVk::createDepthBuffer()
{
if(m_depthView)
vkDestroyImageView(m_device, m_depthView, nullptr);
if(m_depthImage)
vkDestroyImage(m_device, m_depthImage, nullptr);
if(m_depthMemory)
vkFreeMemory(m_device, m_depthMemory, nullptr);
// Depth information
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
VkImageCreateInfo depthStencilCreateInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
depthStencilCreateInfo.imageType = VK_IMAGE_TYPE_2D;
depthStencilCreateInfo.extent = VkExtent3D{m_size.width, m_size.height, 1};
depthStencilCreateInfo.format = m_depthFormat;
depthStencilCreateInfo.mipLevels = 1;
depthStencilCreateInfo.arrayLayers = 1;
depthStencilCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
depthStencilCreateInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
// Create the depth image
vkCreateImage(m_device, &depthStencilCreateInfo, nullptr, &m_depthImage);
#ifndef NDEBUG
std::string name = std::string("AppBaseDepth");
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
nameInfo.objectHandle = (uint64_t)m_depthImage;
nameInfo.objectType = VK_OBJECT_TYPE_IMAGE;
nameInfo.pObjectName = R"(AppBase)";
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
#endif // !NDEBUG
// Allocate the memory
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(m_device, m_depthImage, &memReqs);
VkMemoryAllocateInfo memAllocInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
vkAllocateMemory(m_device, &memAllocInfo, nullptr, &m_depthMemory);
// Bind image and memory
vkBindImageMemory(m_device, m_depthImage, m_depthMemory, 0);
auto cmdBuffer = createTempCmdBuffer();
// Put barrier on top, Put barrier inside setup command buffer
VkImageSubresourceRange subresourceRange{};
subresourceRange.aspectMask = aspect;
subresourceRange.levelCount = 1;
subresourceRange.layerCount = 1;
VkImageMemoryBarrier imageMemoryBarrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
imageMemoryBarrier.image = m_depthImage;
imageMemoryBarrier.subresourceRange = subresourceRange;
imageMemoryBarrier.srcAccessMask = VkAccessFlags();
imageMemoryBarrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
const VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
const VkPipelineStageFlags destStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
vkCmdPipelineBarrier(cmdBuffer, srcStageMask, destStageMask, VK_FALSE, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
submitTempCmdBuffer(cmdBuffer);
// Setting up the view
VkImageViewCreateInfo depthStencilView{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
depthStencilView.format = m_depthFormat;
depthStencilView.subresourceRange = subresourceRange;
depthStencilView.image = m_depthImage;
vkCreateImageView(m_device, &depthStencilView, nullptr, &m_depthView);
}
//--------------------------------------------------------------------------------------------------
// Convenient function to call before rendering.
// - Waits for a framebuffer to be available
// - Update camera matrix if in movement
void nvvkhl::AppBaseVk::prepareFrame()
{
// Resize protection - should be cached by the glFW callback
int w, h;
glfwGetFramebufferSize(m_window, &w, &h);
if(w != (int)m_size.width || h != (int)m_size.height)
onFramebufferSize(w, h);
// Acquire the next image from the swap chain
if(!m_swapChain.acquire())
assert(!"This shouldn't happen");
// Use a fence to wait until the command buffer has finished execution before using it again
uint32_t imageIndex = m_swapChain.getActiveImageIndex();
VkResult result{VK_SUCCESS};
do
{
result = vkWaitForFences(m_device, 1, &m_waitFences[imageIndex], VK_TRUE, 1'000'000);
} while(result == VK_TIMEOUT);
if(result != VK_SUCCESS)
{ // This allows Aftermath to do things and later assert below
#ifdef _WIN32
Sleep(1000);
#else
usleep(1000);
#endif
}
assert(result == VK_SUCCESS);
// start new frame with updated camera
updateCamera();
updateInputs();
}
//--------------------------------------------------------------------------------------------------
// Convenient function to call for submitting the rendering command
// Sending the command buffer of the current frame and add a fence to know when it will be free to use
//
void nvvkhl::AppBaseVk::submitFrame()
{
uint32_t imageIndex = m_swapChain.getActiveImageIndex();
vkResetFences(m_device, 1, &m_waitFences[imageIndex]);
// In case of using NVLINK
const uint32_t deviceMask = m_useNvlink ? 0b0000'0011 : 0b0000'0001;
const std::array<uint32_t, 2> deviceIndex = {0, 1};
VkDeviceGroupSubmitInfo deviceGroupSubmitInfo{VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO_KHR};
deviceGroupSubmitInfo.waitSemaphoreCount = 1;
deviceGroupSubmitInfo.commandBufferCount = 1;
deviceGroupSubmitInfo.pCommandBufferDeviceMasks = &deviceMask;
deviceGroupSubmitInfo.signalSemaphoreCount = m_useNvlink ? 2 : 1;
deviceGroupSubmitInfo.pSignalSemaphoreDeviceIndices = deviceIndex.data();
deviceGroupSubmitInfo.pWaitSemaphoreDeviceIndices = deviceIndex.data();
VkSemaphore semaphoreRead = m_swapChain.getActiveReadSemaphore();
VkSemaphore semaphoreWrite = m_swapChain.getActiveWrittenSemaphore();
// Pipeline stage at which the queue submission will wait (via pWaitSemaphores)
const VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
// The submit info structure specifies a command buffer queue submission batch
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
submitInfo.pWaitDstStageMask = &waitStageMask; // Pointer to the list of pipeline stages that the semaphore waits will occur at
submitInfo.pWaitSemaphores = &semaphoreRead; // Semaphore(s) to wait upon before the submitted command buffer starts executing
submitInfo.waitSemaphoreCount = 1; // One wait semaphore
submitInfo.pSignalSemaphores = &semaphoreWrite; // Semaphore(s) to be signaled when command buffers have completed
submitInfo.signalSemaphoreCount = 1; // One signal semaphore
submitInfo.pCommandBuffers = &m_commandBuffers[imageIndex]; // Command buffers(s) to execute in this batch (submission)
submitInfo.commandBufferCount = 1; // One command buffer
submitInfo.pNext = &deviceGroupSubmitInfo;
// Submit to the graphics queue passing a wait fence
vkQueueSubmit(m_queue, 1, &submitInfo, m_waitFences[imageIndex]);
// Presenting frame
m_swapChain.present(m_queue);
}
//--------------------------------------------------------------------------------------------------
// When the pipeline is set for using dynamic, this becomes useful
//
void nvvkhl::AppBaseVk::setViewport(const VkCommandBuffer& cmdBuf)
{
VkViewport viewport{0.0f, 0.0f, static_cast<float>(m_size.width), static_cast<float>(m_size.height), 0.0f, 1.0f};
vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
VkRect2D scissor{{0, 0}, {m_size.width, m_size.height}};
vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
}
//--------------------------------------------------------------------------------------------------
// Window callback when the it is resized
// - Destroy allocated frames, then rebuild them with the new size
// - Call onResize() of the derived class
//
void nvvkhl::AppBaseVk::onFramebufferSize(int w, int h)
{
if(w == 0 || h == 0)
return;
// Update imgui
if(ImGui::GetCurrentContext() != nullptr)
{
auto& imgui_io = ImGui::GetIO();
imgui_io.DisplaySize = ImVec2(static_cast<float>(w), static_cast<float>(h));
}
// Wait to finish what is currently drawing
vkDeviceWaitIdle(m_device);
vkQueueWaitIdle(m_queue);
// Request new swapchain image size
m_size = m_swapChain.update(w, h, m_vsync);
auto cmdBuffer = createTempCmdBuffer();
m_swapChain.cmdUpdateBarriers(cmdBuffer); // Make them presentable
submitTempCmdBuffer(cmdBuffer);
if(m_size.width != w || m_size.height != h)
LOGW("Requested size (%d, %d) is different from created size (%u, %u) ", w, h, m_size.width, m_size.height);
CameraManip.setWindowSize(m_size.width, m_size.height);
// Invoking Sample callback
onResize(m_size.width, m_size.height); // <-- to implement on derived class
// Recreating other resources
createDepthBuffer();
createFrameBuffers();
}
//--------------------------------------------------------------------------------------------------
// Window callback when the mouse move
// - Handling ImGui and a default camera
//
void nvvkhl::AppBaseVk::onMouseMotion(int x, int y)
{
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
return;
if(m_inputs.lmb || m_inputs.rmb || m_inputs.mmb)
CameraManip.mouseMove(x, y, m_inputs);
}
//--------------------------------------------------------------------------------------------------
// Window callback when a special key gets hit
// - Handling ImGui and a default camera
//
void nvvkhl::AppBaseVk::onKeyboard(int key, int /*scancode*/, int action, int mods)
{
const bool pressed = action != GLFW_RELEASE;
if(pressed && key == GLFW_KEY_F11)
m_show_gui = !m_show_gui;
else if(pressed && key == GLFW_KEY_ESCAPE)
glfwSetWindowShouldClose(m_window, 1);
}
//--------------------------------------------------------------------------------------------------
// Window callback when a key gets hit
//
void nvvkhl::AppBaseVk::onKeyboardChar(unsigned char key)
{
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureKeyboard)
return;
// Toggling vsync
if(key == 'v')
{
m_vsync = !m_vsync;
vkDeviceWaitIdle(m_device);
vkQueueWaitIdle(m_queue);
m_swapChain.update(m_size.width, m_size.height, m_vsync);
auto cmdBuffer = createTempCmdBuffer();
m_swapChain.cmdUpdateBarriers(cmdBuffer); // Make them presentable
submitTempCmdBuffer(cmdBuffer);
createFrameBuffers();
}
if(key == 'h' || key == '?')
m_showHelp = !m_showHelp;
}
//--------------------------------------------------------------------------------------------------
// Window callback when the mouse button is pressed
// - Handling ImGui and a default camera
//
void nvvkhl::AppBaseVk::onMouseButton(int button, int action, int mods)
{
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
return;
double x, y;
glfwGetCursorPos(m_window, &x, &y);
CameraManip.setMousePosition(static_cast<int>(x), static_cast<int>(y));
}
//--------------------------------------------------------------------------------------------------
// Window callback when the mouse wheel is modified
// - Handling ImGui and a default camera
//
void nvvkhl::AppBaseVk::onMouseWheel(int delta)
{
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
return;
if(delta != 0)
CameraManip.wheel(delta > 0 ? 1 : -1, m_inputs);
}
//--------------------------------------------------------------------------------------------------
// Called every frame to translate currently pressed keys into camera movement
//
void nvvkhl::AppBaseVk::updateCamera()
{
// measure one frame at a time
float factor = ImGui::GetIO().DeltaTime * 1000 * m_sceneRadius;
m_inputs.lmb = ImGui::IsMouseDown(ImGuiMouseButton_Left);
m_inputs.rmb = ImGui::IsMouseDown(ImGuiMouseButton_Right);
m_inputs.mmb = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
m_inputs.ctrl = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl);
m_inputs.shift = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
m_inputs.alt = ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt);
// Allow camera movement only when not editing
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureKeyboard)
return;
// For all pressed keys - apply the action
CameraManip.keyMotion(0, 0, nvh::CameraManipulator::NoAction);
if(!(ImGui::IsKeyDown(ImGuiKey_ModAlt) || ImGui::IsKeyDown(ImGuiKey_ModCtrl) || ImGui::IsKeyDown(ImGuiKey_ModShift)))
{
if(ImGui::IsKeyDown(ImGuiKey_W))
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Dolly);
if(ImGui::IsKeyDown(ImGuiKey_S))
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Dolly);
if(ImGui::IsKeyDown(ImGuiKey_D) || ImGui::IsKeyDown(ImGuiKey_RightArrow))
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Pan);
if(ImGui::IsKeyDown(ImGuiKey_A) || ImGui::IsKeyDown(ImGuiKey_LeftArrow))
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Pan);
if(ImGui::IsKeyDown(ImGuiKey_UpArrow))
CameraManip.keyMotion(0, factor, nvh::CameraManipulator::Pan);
if(ImGui::IsKeyDown(ImGuiKey_DownArrow))
CameraManip.keyMotion(0, -factor, nvh::CameraManipulator::Pan);
}
// This makes the camera to transition smoothly to the new position
CameraManip.updateAnim();
}
//--------------------------------------------------------------------------------------------------
// Initialization of the GUI
// - Need to be call after the device creation
//
void nvvkhl::AppBaseVk::initGUI(uint32_t subpassID /*= 0*/)
{
//assert(m_renderPass && "Render Pass must be set");
// UI
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr; // Avoiding the INI file
io.LogFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
ImGuiH::setStyle();
ImGuiH::setFonts();
std::vector<VkDescriptorPoolSize> poolSize{{VK_DESCRIPTOR_TYPE_SAMPLER, 1}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1}};
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
poolInfo.maxSets = 1000;
poolInfo.poolSizeCount = 2;
poolInfo.pPoolSizes = poolSize.data();
vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_imguiDescPool);
// Setup Platform/Renderer back ends
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = m_instance;
init_info.PhysicalDevice = m_physicalDevice;
init_info.Device = m_device;
init_info.QueueFamily = m_graphicsQueueIndex;
init_info.Queue = m_queue;
init_info.PipelineCache = VK_NULL_HANDLE;
init_info.DescriptorPool = m_imguiDescPool;
init_info.RenderPass = m_renderPass;
init_info.Subpass = subpassID;
init_info.MinImageCount = 2;
init_info.ImageCount = static_cast<int>(m_swapChain.getImageCount());
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; // <--- need argument?
init_info.CheckVkResultFn = nullptr;
init_info.Allocator = nullptr;
init_info.UseDynamicRendering = m_useDynamicRendering;
init_info.PipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
init_info.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
init_info.PipelineRenderingCreateInfo.pColorAttachmentFormats = &m_colorFormat;
init_info.PipelineRenderingCreateInfo.depthAttachmentFormat = m_depthFormat;
ImGui_ImplVulkan_Init(&init_info);
// Upload Fonts
ImGui_ImplVulkan_CreateFontsTexture();
}
//--------------------------------------------------------------------------------------------------
// Fit the camera to the Bounding box
//
void nvvkhl::AppBaseVk::fitCamera(const glm::vec3& boxMin, const glm::vec3& boxMax, bool instantFit /*= true*/)
{
CameraManip.fit(boxMin, boxMax, instantFit, false, m_size.width / static_cast<float>(m_size.height));
}
//--------------------------------------------------------------------------------------------------
// Return true if the window is minimized
//
bool nvvkhl::AppBaseVk::isMinimized(bool doSleeping /*= true*/)
{
int w, h;
glfwGetWindowSize(m_window, &w, &h);
bool minimized(w == 0 || h == 0);
if(minimized && doSleeping)
{
#ifdef _WIN32
Sleep(50);
#else
usleep(50);
#endif
}
return minimized;
}
void nvvkhl::AppBaseVk::setupGlfwCallbacks(GLFWwindow* window)
{
m_window = window;
glfwSetWindowUserPointer(window, this);
glfwSetKeyCallback(window, &key_cb);
glfwSetCharCallback(window, &char_cb);
glfwSetCursorPosCallback(window, &cursorpos_cb);
glfwSetMouseButtonCallback(window, &mousebutton_cb);
glfwSetScrollCallback(window, &scroll_cb);
glfwSetFramebufferSizeCallback(window, &framebuffersize_cb);
glfwSetDropCallback(window, &drop_cb);
}
void nvvkhl::AppBaseVk::framebuffersize_cb(GLFWwindow* window, int w, int h)
{
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
app->onFramebufferSize(w, h);
}
void nvvkhl::AppBaseVk::mousebutton_cb(GLFWwindow* window, int button, int action, int mods)
{
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
app->onMouseButton(button, action, mods);
}
void nvvkhl::AppBaseVk::cursorpos_cb(GLFWwindow* window, double x, double y)
{
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
app->onMouseMotion(static_cast<int>(x), static_cast<int>(y));
}
void nvvkhl::AppBaseVk::scroll_cb(GLFWwindow* window, double x, double y)
{
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
app->onMouseWheel(static_cast<int>(y));
}
void nvvkhl::AppBaseVk::key_cb(GLFWwindow* window, int key, int scancode, int action, int mods)
{
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
app->onKeyboard(key, scancode, action, mods);
}
void nvvkhl::AppBaseVk::char_cb(GLFWwindow* window, unsigned int key)
{
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
app->onKeyboardChar(key);
}
void nvvkhl::AppBaseVk::drop_cb(GLFWwindow* window, int count, const char** paths)
{
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
int i;
for(i = 0; i < count; i++)
app->onFileDrop(paths[i]);
}
uint32_t nvvkhl::AppBaseVk::getMemoryType(uint32_t typeBits, const VkMemoryPropertyFlags& properties) const
{
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
for(uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++)
{
if(((typeBits & (1 << i)) > 0) && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
return i;
}
LOGE("Unable to find memory type %u\n", static_cast<unsigned int>(properties));
assert(0);
return ~0u;
}
// Showing help
void nvvkhl::AppBaseVk::uiDisplayHelp()
{
if(m_showHelp)
{
ImGui::BeginChild("Help", ImVec2(370, 120), true);
ImGui::Text("%s", CameraManip.getHelp().c_str());
ImGui::EndChild();
}
}
VkCommandBuffer nvvkhl::AppBaseVk::createTempCmdBuffer()
{
// Create an image barrier to change the layout from undefined to DepthStencilAttachmentOptimal
VkCommandBufferAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
allocateInfo.commandBufferCount = 1;
allocateInfo.commandPool = m_cmdPool;
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
VkCommandBuffer cmdBuffer;
vkAllocateCommandBuffers(m_device, &allocateInfo, &cmdBuffer);
VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(cmdBuffer, &beginInfo);
return cmdBuffer;
}
void nvvkhl::AppBaseVk::submitTempCmdBuffer(VkCommandBuffer cmdBuffer)
{
vkEndCommandBuffer(cmdBuffer);
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmdBuffer;
vkQueueSubmit(m_queue, 1, &submitInfo, {});
vkQueueWaitIdle(m_queue);
vkFreeCommandBuffers(m_device, m_cmdPool, 1, &cmdBuffer);
}

View file

@ -0,0 +1,374 @@
/*
* Copyright (c) 2014-2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "vulkan/vulkan_core.h"
// Utilities
#include "nvh/cameramanipulator.hpp"
#include "nvvk/swapchain_vk.hpp"
#ifdef LINUX
#include <unistd.h>
#endif
// GLFW
#ifdef _WIN32
#define GLFW_EXPOSE_NATIVE_WIN32
#endif
#include "GLFW/glfw3.h"
#include "GLFW/glfw3native.h"
#include <vector>
namespace nvvkhl {
//--------------------------------------------------------------------------------------------------
/** @DOC_START
# class nvvkhl::AppBaseVk
nvvkhl::AppBaseVk is used in a few samples, can serve as base class for various needs.
They might differ a bit in setup and functionality, but in principle aid the setup of context and window,
as well as some common event processing.
The nvvkhl::AppBaseVk serves as the base class for many ray tracing examples and makes use of the Vulkan C API.
It does the basics for Vulkan, by holding a reference to the instance and device, but also comes with optional default setups
for the render passes and the swapchain.
## Usage
An example will derive from this class:
```cpp
class VkSample : public AppBaseVk
{
};
```
## Setup
In the `main()` of an application, call `setup()` which is taking a Vulkan instance, device, physical device,
and a queue family index. Setup copies the given Vulkan handles into AppBase, and query the 0th VkQueue of the
specified family, which must support graphics operations and drawing to the surface passed to createSurface.
Furthermore, it is creating a VkCommandPool.
Prior to calling setup, if you are using the `nvvk::Context` class to create and initialize Vulkan instances,
you may want to create a VkSurfaceKHR from the window (glfw for example) and call `setGCTQueueWithPresent()`.
This will make sure the m_queueGCT queue of nvvk::Context can draw to the surface, and m_queueGCT.familyIndex
will meet the requirements of setup().
Creating the swapchain for displaying. Arguments are
width and height, color and depth format, and vsync on/off. Defaults will create the best format for the surface.
Creating framebuffers has a dependency on the renderPass and depth buffer. All those are virtual and can be overridden
in a sample, but default implementation exist.
- createDepthBuffer: creates a 2D depth/stencil image
- createRenderPass : creates a color/depth pass and clear both buffers.
Here is the dependency order:
```cpp
vkSample.createDepthBuffer();
vkSample.createRenderPass();
vkSample.createFrameBuffers();
```cpp
The nvvk::Swapchain will create n images, typically 3. With this information, AppBase is also creating 3 VkFence,
3 VkCommandBuffer and 3 VkFrameBuffer.
### Frame Buffers
The created frame buffers are *display* frame buffers, made to be presented on screen. The frame buffers will be created
using one of the images from swapchain, and a depth buffer. There is only one depth buffer because that resource is not
used simultaneously. For example, when we clear the depth buffer, it is not done immediately, but done through a command
buffer, which will be executed later.
**Note**: the imageView(s) are part of the swapchain.
### Command Buffers
AppBase works with 3 *frame command buffers*. Each frame is filling a command buffer and gets submitted, one after the
other. This is a design choice that can be debated, but makes it simple. I think it is still possible to submit other
command buffers in a frame, but those command buffers will have to be submitted before the *frame* one. The *frame*
command buffer when submitted with submitFrame, will use the current fence.
### Fences
There are as many fences as there are images in the swapchain. At the beginning of a frame, we call prepareFrame().
This is calling the acquire() from nvvk::SwapChain and wait until the image is available. The very first time, the
fence will not stop, but later it will wait until the submit is completed on the GPU.
## ImGui
If the application is using Dear ImGui, there are convenient functions for initializing it and
setting the callbacks (glfw). The first one to call is `initGUI(0)`, where the argument is the subpass
it will be using. Default is 0, but if the application creates a renderpass with multi-sampling and
resolves in the second subpass, this makes it possible.
## Glfw Callbacks
Call `setupGlfwCallbacks(window)` to have all the window callback: key, mouse, window resizing.
By default AppBase will handle resizing of the window and will recreate the images and framebuffers.
If a sample needs to be aware of the resize, it can implement `onResize(width, height)`.
To handle the callbacks in Imgui, call `ImGui_ImplGlfw_InitForVulkan(window, true)`, where true
will handle the default ImGui callback .
**Note**: All the methods are virtual and can be overloaded if they are not doing the typical setup.
```cpp
// Create example
VulkanSample vkSample;
// Window need to be opened to get the surface on which to draw
const VkSurfaceKHR surface = vkSample.getVkSurface(vkctx.m_instance, window);
vkctx.setGCTQueueWithPresent(surface);
vkSample.setup(vkctx.m_instance, vkctx.m_device, vkctx.m_physicalDevice, vkctx.m_queueGCT.familyIndex);
vkSample.createSwapchain(surface, SAMPLE_WIDTH, SAMPLE_HEIGHT);
vkSample.createDepthBuffer();
vkSample.createRenderPass();
vkSample.createFrameBuffers();
vkSample.initGUI(0);
vkSample.setupGlfwCallbacks(window);
ImGui_ImplGlfw_InitForVulkan(window, true);
```
## Drawing loop
The drawing loop in the main() is the typicall loop you will find in glfw examples. Note that
AppBase has a convenient function to tell if the window is minimize, therefore not doing any
work and contain a sleep(), so the CPU is not going crazy.
```cpp
// Window system loop
while(!glfwWindowShouldClose(window))
{
glfwPollEvents();
if(vkSample.isMinimized())
continue;
vkSample.display(); // infinitely drawing
}
```
## Display
A typical display() function will need the following:
* Acquiring the next image: `prepareFrame()`
* Get the command buffer for the frame. There are n command buffers equal to the number of in-flight frames.
* Clearing values
* Start rendering pass
* Drawing
* End rendering
* Submitting frame to display
```cpp
void VkSample::display()
{
// Acquire
prepareFrame();
// Command buffer for current frame
auto curFrame = getCurFrame();
const VkCommandBuffer& cmdBuf = getCommandBuffers()[curFrame];
VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(cmdBuf, &beginInfo);
// Clearing values
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {{1.f, 1.f, 1.f, 1.f}};
clearValues[1].depthStencil = {1.0f, 0};
// Begin rendering
VkRenderPassBeginInfo renderPassBeginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues.data();
renderPassBeginInfo.renderPass = m_renderPass;
renderPassBeginInfo.framebuffer = m_framebuffers[curFram];
renderPassBeginInfo.renderArea = {{0, 0}, m_size};
vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
// .. draw scene ...
// Draw UI
ImGui_ImplVulkan_RenderDrawData( ImGui::GetDrawData(),cmdBuff)
// End rendering
vkCmdEndRenderPass(cmdBuf);
// End of the frame and present the one which is ready
vkEndCommandBuffer(cmdBuf);
submitFrame();
}
```
## Closing
Finally, all resources can be destroyed by calling `destroy()` at the end of main().
```cpp
vkSample.destroy();
```
@DOC_END */
struct AppBaseVkCreateInfo
{
VkInstance instance{};
VkDevice device{};
VkPhysicalDevice physicalDevice{};
std::vector<uint32_t> queueIndices{};
VkSurfaceKHR surface{};
VkExtent2D size{};
GLFWwindow* window{nullptr};
bool useDynamicRendering{false}; // VK_KHR_dynamic_rendering
bool useVsync{false};
};
class AppBaseVk
{
public:
AppBaseVk() = default;
virtual ~AppBaseVk() = default;
virtual void create(const AppBaseVkCreateInfo& info);
virtual void onResize(int /*w*/, int /*h*/){}; // To implement when the size of the window change
virtual void setup(const VkInstance& instance, const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t graphicsQueueIndex);
virtual void destroy();
VkSurfaceKHR getVkSurface(const VkInstance& instance, GLFWwindow* window);
virtual void createSwapchain(const VkSurfaceKHR& surface,
uint32_t width,
uint32_t height,
VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM,
VkFormat depthFormat = VK_FORMAT_UNDEFINED,
bool vsync = false);
virtual void createFrameBuffers();
virtual void createRenderPass();
virtual void createDepthBuffer();
virtual void prepareFrame();
virtual void submitFrame();
virtual void updateInputs(){};
virtual void onFramebufferSize(int w, int h);
virtual void onMouseMotion(int x, int y);
virtual void onKeyboard(int key, int scancode, int action, int mods);
virtual void onKeyboardChar(unsigned char key);
virtual void onMouseButton(int button, int action, int mods);
virtual void onMouseWheel(int delta);
virtual void onFileDrop(const char* filename) {}
void setViewport(const VkCommandBuffer& cmdBuf);
void initGUI(uint32_t subpassID = 0);
void fitCamera(const glm::vec3& boxMin, const glm::vec3& boxMax, bool instantFit = true);
bool isMinimized(bool doSleeping = true);
void setTitle(const std::string& title) { glfwSetWindowTitle(m_window, title.c_str()); }
void useNvlink(bool useNvlink) { m_useNvlink = useNvlink; }
// GLFW Callback setup
void setupGlfwCallbacks(GLFWwindow* window);
static void framebuffersize_cb(GLFWwindow* window, int w, int h);
static void mousebutton_cb(GLFWwindow* window, int button, int action, int mods);
static void cursorpos_cb(GLFWwindow* window, double x, double y);
static void scroll_cb(GLFWwindow* window, double x, double y);
static void key_cb(GLFWwindow* window, int key, int scancode, int action, int mods);
static void char_cb(GLFWwindow* window, unsigned int key);
static void drop_cb(GLFWwindow* window, int count, const char** paths);
// Getters
VkInstance getInstance() { return m_instance; }
VkDevice getDevice() { return m_device; }
VkPhysicalDevice getPhysicalDevice() { return m_physicalDevice; }
VkQueue getQueue() { return m_queue; }
uint32_t getQueueFamily() { return m_graphicsQueueIndex; }
VkCommandPool getCommandPool() { return m_cmdPool; }
VkRenderPass getRenderPass() { return m_renderPass; }
VkExtent2D getSize() { return m_size; }
VkPipelineCache getPipelineCache() { return m_pipelineCache; }
VkSurfaceKHR getSurface() { return m_surface; }
const std::vector<VkFramebuffer>& getFramebuffers() { return m_framebuffers; }
const std::vector<VkCommandBuffer>& getCommandBuffers() { return m_commandBuffers; }
uint32_t getCurFrame() const { return m_swapChain.getActiveImageIndex(); }
VkFormat getColorFormat() const { return m_colorFormat; }
VkFormat getDepthFormat() const { return m_depthFormat; }
bool showGui() { return m_show_gui; }
const nvvk::SwapChain& getSwapChain() { return m_swapChain; }
VkImageView getDepthView() { return m_depthView; }
protected:
uint32_t getMemoryType(uint32_t typeBits, const VkMemoryPropertyFlags& properties) const;
void uiDisplayHelp();
void updateCamera();
VkCommandBuffer createTempCmdBuffer();
void submitTempCmdBuffer(VkCommandBuffer cmdBuffer);
// Vulkan low level
VkInstance m_instance{};
VkDevice m_device{};
VkSurfaceKHR m_surface{};
VkPhysicalDevice m_physicalDevice{};
VkQueue m_queue{VK_NULL_HANDLE};
uint32_t m_graphicsQueueIndex{VK_QUEUE_FAMILY_IGNORED};
VkCommandPool m_cmdPool{VK_NULL_HANDLE};
VkDescriptorPool m_imguiDescPool{VK_NULL_HANDLE};
// Drawing/Surface
nvvk::SwapChain m_swapChain;
std::vector<VkFramebuffer> m_framebuffers; // All framebuffers, correspond to the Swapchain
std::vector<VkCommandBuffer> m_commandBuffers; // Command buffer per nb element in Swapchain
std::vector<VkFence> m_waitFences; // Fences per nb element in Swapchain
VkImage m_depthImage{VK_NULL_HANDLE}; // Depth/Stencil
VkDeviceMemory m_depthMemory{VK_NULL_HANDLE}; // Depth/Stencil
VkImageView m_depthView{VK_NULL_HANDLE}; // Depth/Stencil
VkRenderPass m_renderPass{VK_NULL_HANDLE}; // Base render pass
VkExtent2D m_size{0, 0}; // Size of the window
VkPipelineCache m_pipelineCache{VK_NULL_HANDLE}; // Cache for pipeline/shaders
bool m_vsync{false}; // Swapchain with vsync
bool m_useNvlink{false}; // NVLINK usage
GLFWwindow* m_window{nullptr}; // GLFW Window
// Surface buffer formats
VkFormat m_colorFormat{VK_FORMAT_B8G8R8A8_UNORM};
VkFormat m_depthFormat{VK_FORMAT_UNDEFINED};
// Camera manipulators
nvh::CameraManipulator::Inputs m_inputs; // Mouse button pressed
// Other
bool m_showHelp{false}; // Show help, pressing
bool m_show_gui{true};
bool m_useDynamicRendering{false}; // Using VK_KHR_dynamic_rendering
float m_sceneRadius{1.f};
};
} // namespace nvvkhl

View file

@ -0,0 +1,786 @@
/*
* Copyright (c) 2021-2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "nvvkhl/appbase_vkpp.hpp"
#include <imgui.h>
#include <backends/imgui_impl_vulkan.h>
#include "imgui/imgui_helper.h"
#include "imgui/imgui_camera_widget.h"
#include "nvp/perproject_globals.hpp"
#ifdef LINUX
#include <unistd.h>
#endif
#include "GLFW/glfw3.h"
#include <cmath>
void nvvkhl::AppBase::onResize(int, int) {}
void nvvkhl::AppBase::setup(const vk::Instance& instance, const vk::Device& device, const vk::PhysicalDevice& physicalDevice, uint32_t graphicsQueueIndex)
{
// Initialize function pointers
vk::DynamicLoader dl;
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance, device);
m_instance = instance;
m_device = device;
m_physicalDevice = physicalDevice;
m_graphicsQueueIndex = graphicsQueueIndex;
m_queue = m_device.getQueue(m_graphicsQueueIndex, 0);
m_cmdPool = m_device.createCommandPool({vk::CommandPoolCreateFlagBits::eResetCommandBuffer, graphicsQueueIndex});
m_pipelineCache = device.createPipelineCache(vk::PipelineCacheCreateInfo());
ImGuiH::SetCameraJsonFile(getProjectName());
}
void nvvkhl::AppBase::destroy()
{
m_device.waitIdle();
if(ImGui::GetCurrentContext() != nullptr)
{
//ImGui::ShutdownVK();
if(ImGui::GetIO().BackendRendererUserData != nullptr)
ImGui_ImplVulkan_Shutdown();
ImGui::DestroyContext();
}
m_device.destroy(m_renderPass);
m_device.destroy(m_depthView);
m_device.destroy(m_depthImage);
m_device.freeMemory(m_depthMemory);
m_device.destroy(m_pipelineCache);
for(uint32_t i = 0; i < m_swapChain.getImageCount(); i++)
{
m_device.destroy(m_waitFences[i]);
m_device.destroy(m_framebuffers[i]);
m_device.freeCommandBuffers(m_cmdPool, m_commandBuffers[i]);
}
m_swapChain.deinit();
m_device.destroy(m_imguiDescPool);
m_device.destroy(m_cmdPool);
if(m_surface)
m_instance.destroySurfaceKHR(m_surface);
}
VkSurfaceKHR nvvkhl::AppBase::getVkSurface(const vk::Instance& instance, GLFWwindow* window)
{
assert(instance);
m_window = window;
VkSurfaceKHR surface{};
VkResult err = glfwCreateWindowSurface(instance, window, nullptr, &surface);
if(err != VK_SUCCESS)
{
assert(!"Failed to create a Window surface");
}
m_surface = surface;
return surface;
}
void nvvkhl::AppBase::createSwapchain(const vk::SurfaceKHR& surface, uint32_t width, uint32_t height, vk::Format colorFormat, vk::Format depthFormat, bool vsync)
{
m_size = vk::Extent2D(width, height);
m_colorFormat = colorFormat;
m_depthFormat = depthFormat;
m_vsync = vsync;
// Find the most suitable depth format
if(m_depthFormat == vk::Format::eUndefined)
{
auto feature = vk::FormatFeatureFlagBits::eDepthStencilAttachment;
for(const auto& f : {vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint, vk::Format::eD16UnormS8Uint})
{
if((m_physicalDevice.getFormatProperties(f).optimalTilingFeatures & feature) == feature)
{
m_depthFormat = f;
break;
}
}
}
m_swapChain.init(m_device, m_physicalDevice, m_queue, m_graphicsQueueIndex, surface, static_cast<VkFormat>(colorFormat));
m_size = m_swapChain.update(m_size.width, m_size.height, vsync);
m_colorFormat = static_cast<vk::Format>(m_swapChain.getFormat());
// Create Synchronization Primitives
m_waitFences.resize(m_swapChain.getImageCount());
for(auto& fence : m_waitFences)
{
fence = m_device.createFence({vk::FenceCreateFlagBits::eSignaled});
}
// Command buffers store a reference to the frame buffer inside their render pass info
// so for static usage without having to rebuild them each frame, we use one per frame buffer
m_commandBuffers =
m_device.allocateCommandBuffers({m_cmdPool, vk::CommandBufferLevel::ePrimary, m_swapChain.getImageCount()});
#ifndef NDEBUG
for(size_t i = 0; i < m_commandBuffers.size(); i++)
{
std::string name = std::string("AppBase") + std::to_string(i);
m_device.setDebugUtilsObjectNameEXT(
{vk::ObjectType::eCommandBuffer, reinterpret_cast<const uint64_t&>(m_commandBuffers[i]), name.c_str()});
}
#endif // !NDEBUG
// Setup camera
CameraManip.setWindowSize(m_size.width, m_size.height);
}
void nvvkhl::AppBase::createFrameBuffers()
{
// Recreate the frame buffers
for(auto framebuffer : m_framebuffers)
{
m_device.destroy(framebuffer);
}
// Array of attachment (color, depth)
std::array<vk::ImageView, 2> attachments;
// Create frame buffers for every swap chain image
vk::FramebufferCreateInfo framebufferCreateInfo;
framebufferCreateInfo.renderPass = m_renderPass;
framebufferCreateInfo.attachmentCount = 2;
framebufferCreateInfo.width = m_size.width;
framebufferCreateInfo.height = m_size.height;
framebufferCreateInfo.layers = 1;
framebufferCreateInfo.pAttachments = attachments.data();
// Create frame buffers for every swap chain image
m_framebuffers.resize(m_swapChain.getImageCount());
for(uint32_t i = 0; i < m_swapChain.getImageCount(); i++)
{
attachments[0] = m_swapChain.getImageView(i);
attachments[1] = m_depthView;
m_framebuffers[i] = m_device.createFramebuffer(framebufferCreateInfo);
}
#ifndef NDEBUG
for(size_t i = 0; i < m_framebuffers.size(); i++)
{
std::string name = std::string("AppBase") + std::to_string(i);
m_device.setDebugUtilsObjectNameEXT(
{vk::ObjectType::eFramebuffer, reinterpret_cast<const uint64_t&>(m_framebuffers[i]), name.c_str()});
}
#endif // !NDEBUG
}
void nvvkhl::AppBase::createRenderPass()
{
if(m_renderPass)
{
m_device.destroy(m_renderPass);
}
std::array<vk::AttachmentDescription, 2> attachments;
// Color attachment
attachments[0].setFormat(m_colorFormat);
attachments[0].setLoadOp(vk::AttachmentLoadOp::eClear);
attachments[0].setFinalLayout(vk::ImageLayout::ePresentSrcKHR);
// Depth attachment
attachments[1].setFormat(m_depthFormat);
attachments[1].setLoadOp(vk::AttachmentLoadOp::eClear);
attachments[1].setStencilLoadOp(vk::AttachmentLoadOp::eClear);
attachments[1].setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
// One color, one depth
const vk::AttachmentReference colorReference{0, vk::ImageLayout::eColorAttachmentOptimal};
const vk::AttachmentReference depthReference{1, vk::ImageLayout::eDepthStencilAttachmentOptimal};
std::array<vk::SubpassDependency, 1> subpassDependencies;
// Transition from final to initial (VK_SUBPASS_EXTERNAL refers to all commands executed outside of the actual renderpass)
subpassDependencies[0].setSrcSubpass(VK_SUBPASS_EXTERNAL);
subpassDependencies[0].setDstSubpass(0);
subpassDependencies[0].setSrcStageMask(vk::PipelineStageFlagBits::eBottomOfPipe);
subpassDependencies[0].setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput);
subpassDependencies[0].setSrcAccessMask(vk::AccessFlagBits::eMemoryRead);
subpassDependencies[0].setDstAccessMask(vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
subpassDependencies[0].setDependencyFlags(vk::DependencyFlagBits::eByRegion);
vk::SubpassDescription subpassDescription;
subpassDescription.setPipelineBindPoint(vk::PipelineBindPoint::eGraphics);
subpassDescription.setColorAttachmentCount(1);
subpassDescription.setPColorAttachments(&colorReference);
subpassDescription.setPDepthStencilAttachment(&depthReference);
vk::RenderPassCreateInfo renderPassInfo;
renderPassInfo.setAttachmentCount(static_cast<uint32_t>(attachments.size()));
renderPassInfo.setPAttachments(attachments.data());
renderPassInfo.setSubpassCount(1);
renderPassInfo.setPSubpasses(&subpassDescription);
renderPassInfo.setDependencyCount(static_cast<uint32_t>(subpassDependencies.size()));
renderPassInfo.setPDependencies(subpassDependencies.data());
m_renderPass = m_device.createRenderPass(renderPassInfo);
#ifndef NDEBUG
m_device.setDebugUtilsObjectNameEXT({vk::ObjectType::eRenderPass, reinterpret_cast<const uint64_t&>(m_renderPass), "AppBase"});
#endif // !NDEBUG
}
void nvvkhl::AppBase::createDepthBuffer()
{
if(m_depthView)
m_device.destroy(m_depthView);
if(m_depthImage)
m_device.destroy(m_depthImage);
if(m_depthMemory)
m_device.freeMemory(m_depthMemory);
// Depth information
const vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
vk::ImageCreateInfo depthStencilCreateInfo;
depthStencilCreateInfo.setImageType(vk::ImageType::e2D);
depthStencilCreateInfo.setExtent(vk::Extent3D{m_size.width, m_size.height, 1});
depthStencilCreateInfo.setFormat(m_depthFormat);
depthStencilCreateInfo.setMipLevels(1);
depthStencilCreateInfo.setArrayLayers(1);
depthStencilCreateInfo.setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransferSrc);
// Create the depth image
m_depthImage = m_device.createImage(depthStencilCreateInfo);
// Allocate the memory
const vk::MemoryRequirements memReqs = m_device.getImageMemoryRequirements(m_depthImage);
vk::MemoryAllocateInfo memAllocInfo;
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
m_depthMemory = m_device.allocateMemory(memAllocInfo);
// Bind image and memory
m_device.bindImageMemory(m_depthImage, m_depthMemory, 0);
// Create an image barrier to change the layout from undefined to DepthStencilAttachmentOptimal
vk::CommandBuffer cmdBuffer = m_device.allocateCommandBuffers({m_cmdPool, vk::CommandBufferLevel::ePrimary, 1})[0];
cmdBuffer.begin(vk::CommandBufferBeginInfo{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
// Put barrier on top, Put barrier inside setup command buffer
vk::ImageSubresourceRange subresourceRange;
subresourceRange.aspectMask = aspect;
subresourceRange.levelCount = 1;
subresourceRange.layerCount = 1;
vk::ImageMemoryBarrier imageMemoryBarrier;
imageMemoryBarrier.oldLayout = vk::ImageLayout::eUndefined;
imageMemoryBarrier.newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
imageMemoryBarrier.image = m_depthImage;
imageMemoryBarrier.subresourceRange = subresourceRange;
imageMemoryBarrier.srcAccessMask = vk::AccessFlags();
imageMemoryBarrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite;
const vk::PipelineStageFlags srcStageMask = vk::PipelineStageFlagBits::eTopOfPipe;
const vk::PipelineStageFlags destStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests;
cmdBuffer.pipelineBarrier(srcStageMask, destStageMask, vk::DependencyFlags(), nullptr, nullptr, imageMemoryBarrier);
cmdBuffer.end();
m_queue.submit(vk::SubmitInfo{0, nullptr, nullptr, 1, &cmdBuffer}, vk::Fence());
m_queue.waitIdle();
m_device.freeCommandBuffers(m_cmdPool, cmdBuffer);
// Setting up the view
vk::ImageViewCreateInfo depthStencilView;
depthStencilView.setViewType(vk::ImageViewType::e2D);
depthStencilView.setFormat(m_depthFormat);
depthStencilView.setSubresourceRange(subresourceRange);
depthStencilView.setImage(m_depthImage);
m_depthView = m_device.createImageView(depthStencilView);
}
void nvvkhl::AppBase::prepareFrame()
{
// Resize protection - should be cached by the glFW callback
int w, h;
glfwGetFramebufferSize(m_window, &w, &h);
if(w != (int)m_size.width || h != (int)m_size.height)
{
onFramebufferSize(w, h);
}
// Acquire the next image from the swap chain
if(!m_swapChain.acquire())
{
assert(!"This shouldn't happen");
}
// Use a fence to wait until the command buffer has finished execution before using it again
uint32_t imageIndex = m_swapChain.getActiveImageIndex();
while(m_device.waitForFences(m_waitFences[imageIndex], VK_TRUE, 10000) == vk::Result::eTimeout)
{
}
// start new frame with updated camera
updateCamera();
}
void nvvkhl::AppBase::submitFrame()
{
uint32_t imageIndex = m_swapChain.getActiveImageIndex();
m_device.resetFences(m_waitFences[imageIndex]);
// In case of using NVLINK
const uint32_t deviceMask = m_useNvlink ? 0b0000'0011 : 0b0000'0001;
const std::array<uint32_t, 2> deviceIndex = {0, 1};
vk::DeviceGroupSubmitInfo deviceGroupSubmitInfo;
deviceGroupSubmitInfo.setWaitSemaphoreCount(1);
deviceGroupSubmitInfo.setCommandBufferCount(1);
deviceGroupSubmitInfo.setPCommandBufferDeviceMasks(&deviceMask);
deviceGroupSubmitInfo.setSignalSemaphoreCount(m_useNvlink ? 2 : 1);
deviceGroupSubmitInfo.setPSignalSemaphoreDeviceIndices(deviceIndex.data());
deviceGroupSubmitInfo.setPWaitSemaphoreDeviceIndices(deviceIndex.data());
vk::Semaphore semaphoreRead = m_swapChain.getActiveReadSemaphore();
vk::Semaphore semaphoreWrite = m_swapChain.getActiveWrittenSemaphore();
// Pipeline stage at which the queue submission will wait (via pWaitSemaphores)
const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
// The submit info structure specifies a command buffer queue submission batch
vk::SubmitInfo submitInfo;
submitInfo.setPWaitDstStageMask(&waitStageMask); // Pointer to the list of pipeline stages that the semaphore waits will occur at
submitInfo.setPWaitSemaphores(&semaphoreRead); // Semaphore(s) to wait upon before the submitted command buffer starts executing
submitInfo.setWaitSemaphoreCount(1); // One wait semaphore
submitInfo.setPSignalSemaphores(&semaphoreWrite); // Semaphore(s) to be signaled when command buffers have completed
submitInfo.setSignalSemaphoreCount(1); // One signal semaphore
submitInfo.setPCommandBuffers(&m_commandBuffers[imageIndex]); // Command buffers(s) to execute in this batch (submission)
submitInfo.setCommandBufferCount(1); // One command buffer
submitInfo.setPNext(&deviceGroupSubmitInfo);
// Submit to the graphics queue passing a wait fence
m_queue.submit(submitInfo, m_waitFences[imageIndex]);
// Presenting frame
m_swapChain.present(m_queue);
}
void nvvkhl::AppBase::setViewport(const vk::CommandBuffer& cmdBuf)
{
cmdBuf.setViewport(0, {vk::Viewport(0.0f, 0.0f, static_cast<float>(m_size.width), static_cast<float>(m_size.height), 0.0f, 1.0f)});
cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}});
}
void nvvkhl::AppBase::onFramebufferSize(int w, int h)
{
if(w == 0 || h == 0)
{
return;
}
// Update imgui
if(ImGui::GetCurrentContext() != nullptr)
{
auto& imgui_io = ImGui::GetIO();
imgui_io.DisplaySize = ImVec2(static_cast<float>(w), static_cast<float>(h));
}
// Wait to finish what is currently drawing
m_device.waitIdle();
m_queue.waitIdle();
// Request new swapschain image size
m_size = m_swapChain.update(m_size.width, m_size.height, m_vsync);
if(m_size.width != w || m_size.height != h)
{
LOGW("Requested size (%d, %d) is different from created size (%u, %u) ", w, h, m_size.width, m_size.height);
}
CameraManip.setWindowSize(m_size.width, m_size.height);
// Invoking Sample callback
onResize(m_size.width, m_size.height);
// Recreating other resources
createDepthBuffer();
createFrameBuffers();
}
void nvvkhl::AppBase::onMouseMotion(int x, int y)
{
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
{
return;
}
if(m_inputs.lmb || m_inputs.rmb || m_inputs.mmb)
{
CameraManip.mouseMove(x, y, m_inputs);
}
}
void nvvkhl::AppBase::onKeyboard(int key, int, int action, int mods)
{
const bool pressed = action != GLFW_RELEASE;
if(pressed && key == GLFW_KEY_F10)
{
m_show_gui = !m_show_gui;
}
else if(pressed && key == GLFW_KEY_ESCAPE)
{
glfwSetWindowShouldClose(m_window, 1);
}
// Remember all keys that are simultaneously pressed for animating the camera
if(pressed)
{
m_keys.insert(key);
}
else
{
m_keys.erase(key);
}
// Keeping track of the modifiers
m_inputs.ctrl = mods & GLFW_MOD_CONTROL;
m_inputs.shift = mods & GLFW_MOD_SHIFT;
m_inputs.alt = mods & GLFW_MOD_ALT;
}
void nvvkhl::AppBase::onKeyboardChar(unsigned char key)
{
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureKeyboard)
{
return;
}
// Toggling vsync
if(key == 'v')
{
m_vsync = !m_vsync;
m_device.waitIdle();
m_queue.waitIdle();
m_swapChain.update(m_size.width, m_size.height, m_vsync);
createFrameBuffers();
}
if(key == 'h' || key == '?')
m_showHelp = !m_showHelp;
}
void nvvkhl::AppBase::onMouseButton(int button, int action, int mods)
{
(void)mods;
double x, y;
glfwGetCursorPos(m_window, &x, &y);
CameraManip.setMousePosition(static_cast<int>(x), static_cast<int>(y));
m_inputs.lmb = (button == GLFW_MOUSE_BUTTON_LEFT) && (action == GLFW_PRESS);
m_inputs.mmb = (button == GLFW_MOUSE_BUTTON_MIDDLE) && (action == GLFW_PRESS);
m_inputs.rmb = (button == GLFW_MOUSE_BUTTON_RIGHT) && (action == GLFW_PRESS);
}
void nvvkhl::AppBase::onMouseWheel(int delta)
{
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
{
return;
}
CameraManip.wheel(delta > 0 ? 1 : -1, m_inputs);
}
void nvvkhl::AppBase::onFileDrop(const char* filename) {}
void nvvkhl::AppBase::initGUI(uint32_t subpassID)
{
assert(m_renderPass && "Render Pass must be set");
// UI
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr; // Avoiding the INI file
io.LogFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
ImGuiH::setStyle();
ImGuiH::setFonts();
std::vector<vk::DescriptorPoolSize> poolSize{{vk::DescriptorType::eSampler, 1}, {vk::DescriptorType::eCombinedImageSampler, 1}};
vk::DescriptorPoolCreateInfo poolInfo{{}, 2, poolSize};
poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet;
m_imguiDescPool = m_device.createDescriptorPool(poolInfo);
// Setup Platform/Renderer backends
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = m_instance;
init_info.PhysicalDevice = m_physicalDevice;
init_info.Device = m_device;
init_info.QueueFamily = m_graphicsQueueIndex;
init_info.Queue = m_queue;
init_info.PipelineCache = VK_NULL_HANDLE;
init_info.DescriptorPool = m_imguiDescPool;
init_info.RenderPass = m_renderPass;
init_info.Subpass = subpassID;
init_info.MinImageCount = 2;
init_info.ImageCount = static_cast<int>(m_framebuffers.size());
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; // <--- need argument?
init_info.CheckVkResultFn = nullptr;
init_info.Allocator = nullptr;
ImGui_ImplVulkan_Init(&init_info);
// Upload Fonts
ImGui_ImplVulkan_CreateFontsTexture();
}
void nvvkhl::AppBase::fitCamera(const glm::vec3& boxMin, const glm::vec3& boxMax, bool instantFit)
{
CameraManip.fit(boxMin, boxMax, instantFit, false, m_size.width / static_cast<float>(m_size.height));
}
bool nvvkhl::AppBase::isMinimized(bool doSleeping)
{
int w, h;
glfwGetWindowSize(m_window, &w, &h);
bool minimized(w == 0 || h == 0);
if(minimized && doSleeping)
{
#ifdef _WIN32
Sleep(50);
#else
usleep(50);
#endif
}
return minimized;
}
void nvvkhl::AppBase::setTitle(const std::string& title)
{
glfwSetWindowTitle(m_window, title.c_str());
}
void nvvkhl::AppBase::setupGlfwCallbacks(GLFWwindow* window)
{
m_window = window;
glfwSetWindowUserPointer(window, this);
glfwSetKeyCallback(window, &key_cb);
glfwSetCharCallback(window, &char_cb);
glfwSetCursorPosCallback(window, &cursorpos_cb);
glfwSetMouseButtonCallback(window, &mousebutton_cb);
glfwSetScrollCallback(window, &scroll_cb);
glfwSetFramebufferSizeCallback(window, &framebuffersize_cb);
glfwSetDropCallback(window, &drop_cb);
}
void nvvkhl::AppBase::framebuffersize_cb(GLFWwindow* window, int w, int h)
{
auto app = reinterpret_cast<AppBase*>(glfwGetWindowUserPointer(window));
app->onFramebufferSize(w, h);
}
void nvvkhl::AppBase::mousebutton_cb(GLFWwindow* window, int button, int action, int mods)
{
auto app = reinterpret_cast<AppBase*>(glfwGetWindowUserPointer(window));
app->onMouseButton(button, action, mods);
}
void nvvkhl::AppBase::cursorpos_cb(GLFWwindow* window, double x, double y)
{
auto app = reinterpret_cast<AppBase*>(glfwGetWindowUserPointer(window));
app->onMouseMotion(static_cast<int>(x), static_cast<int>(y));
}
void nvvkhl::AppBase::scroll_cb(GLFWwindow* window, double x, double y)
{
auto app = reinterpret_cast<AppBase*>(glfwGetWindowUserPointer(window));
app->onMouseWheel(static_cast<int>(y));
}
void nvvkhl::AppBase::key_cb(GLFWwindow* window, int key, int scancode, int action, int mods)
{
auto app = reinterpret_cast<AppBase*>(glfwGetWindowUserPointer(window));
app->onKeyboard(key, scancode, action, mods);
}
void nvvkhl::AppBase::char_cb(GLFWwindow* window, unsigned int key)
{
auto app = reinterpret_cast<AppBase*>(glfwGetWindowUserPointer(window));
app->onKeyboardChar(key);
}
void nvvkhl::AppBase::drop_cb(GLFWwindow* window, int count, const char** paths)
{
auto app = reinterpret_cast<AppBase*>(glfwGetWindowUserPointer(window));
int i;
for(i = 0; i < count; i++)
app->onFileDrop(paths[i]);
}
void nvvkhl::AppBase::useNvlink(bool useNvlink)
{
m_useNvlink = useNvlink;
}
vk::Instance nvvkhl::AppBase::getInstance()
{
return m_instance;
}
vk::Device nvvkhl::AppBase::getDevice()
{
return m_device;
}
vk::PhysicalDevice nvvkhl::AppBase::getPhysicalDevice()
{
return m_physicalDevice;
}
vk::Queue nvvkhl::AppBase::getQueue()
{
return m_queue;
}
uint32_t nvvkhl::AppBase::getQueueFamily()
{
return m_graphicsQueueIndex;
}
vk::CommandPool nvvkhl::AppBase::getCommandPool()
{
return m_cmdPool;
}
vk::RenderPass nvvkhl::AppBase::getRenderPass()
{
return m_renderPass;
}
vk::Extent2D nvvkhl::AppBase::getSize()
{
return m_size;
}
vk::PipelineCache nvvkhl::AppBase::getPipelineCache()
{
return m_pipelineCache;
}
vk::SurfaceKHR nvvkhl::AppBase::getSurface()
{
return m_surface;
}
const std::vector<vk::Framebuffer>& nvvkhl::AppBase::getFramebuffers()
{
return m_framebuffers;
}
const std::vector<vk::CommandBuffer>& nvvkhl::AppBase::getCommandBuffers()
{
return m_commandBuffers;
}
uint32_t nvvkhl::AppBase::getCurFrame() const
{
return m_swapChain.getActiveImageIndex();
}
vk::Format nvvkhl::AppBase::getColorFormat() const
{
return m_colorFormat;
}
vk::Format nvvkhl::AppBase::getDepthFormat() const
{
return m_depthFormat;
}
bool nvvkhl::AppBase::showGui()
{
return m_show_gui;
}
uint32_t nvvkhl::AppBase::getMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties) const
{
auto deviceMemoryProperties = m_physicalDevice.getMemoryProperties();
for(uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++)
{
if(((typeBits & (1 << i)) > 0) && (deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
{
return i;
}
}
LOGE("Unable to find memory type %u\n", static_cast<unsigned int>(properties));
assert(0);
return ~0u;
}
void nvvkhl::AppBase::uiDisplayHelp()
{
if(m_showHelp)
{
ImGui::BeginChild("Help", ImVec2(370, 120), true);
ImGui::Text("%s", CameraManip.getHelp().c_str());
ImGui::EndChild();
}
}
void nvvkhl::AppBase::updateCamera()
{
// measure one frame at a time
float factor = static_cast<float>(m_timer.elapsed());
m_timer.reset();
// Allow camera movement only when not editing
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureKeyboard)
return;
// For all pressed keys - apply the action
CameraManip.keyMotion(0, 0, nvh::CameraManipulator::NoAction);
for(auto key : m_keys)
{
switch(key)
{
case GLFW_KEY_W:
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Dolly);
break;
case GLFW_KEY_S:
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Dolly);
break;
case GLFW_KEY_A:
case GLFW_KEY_LEFT:
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Pan);
break;
case GLFW_KEY_UP:
CameraManip.keyMotion(0, factor, nvh::CameraManipulator::Pan);
break;
case GLFW_KEY_D:
case GLFW_KEY_RIGHT:
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Pan);
break;
case GLFW_KEY_DOWN:
CameraManip.keyMotion(0, -factor, nvh::CameraManipulator::Pan);
break;
default:
break;
}
}
// This makes the camera to transition smoothly to the new position
CameraManip.updateAnim();
}

View file

@ -0,0 +1,251 @@
/*
* Copyright (c) 2014-2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <set>
#include <vector>
#include <vulkan/vulkan.hpp>
#include "nvh/cameramanipulator.hpp"
#include "nvh/timesampler.hpp"
#include "nvvk/swapchain_vk.hpp"
struct GLFWwindow;
namespace nvvkhl {
/** @DOC_START
# class nvvkhl::AppBase
nvvkhl::AppBaseVk is the same as nvvkhl::AppBaseVk but makes use of the Vulkan C++ API (`vulkan.hpp`).
@DOC_END */
class AppBase
{
public:
AppBase() = default;
virtual ~AppBase() = default;
virtual void onResize(int /*w*/, int /*h*/);
; // To implement when the size of the window change
//--------------------------------------------------------------------------------------------------
// Setup the low level Vulkan for various operations
//
virtual void setup(const vk::Instance& instance, const vk::Device& device, const vk::PhysicalDevice& physicalDevice, uint32_t graphicsQueueIndex);
//--------------------------------------------------------------------------------------------------
// To call on exit
//
virtual void destroy();
//--------------------------------------------------------------------------------------------------
// Return the surface "screen" for the display
//
VkSurfaceKHR getVkSurface(const vk::Instance& instance, GLFWwindow* window);
//--------------------------------------------------------------------------------------------------
// Creating the surface for rendering
//
virtual void createSwapchain(const vk::SurfaceKHR& surface,
uint32_t width,
uint32_t height,
vk::Format colorFormat = vk::Format::eB8G8R8A8Unorm,
vk::Format depthFormat = vk::Format::eUndefined,
bool vsync = false);
//--------------------------------------------------------------------------------------------------
// Create the framebuffers in which the image will be rendered
// - Swapchain need to be created before calling this
//
virtual void createFrameBuffers();
//--------------------------------------------------------------------------------------------------
// Creating a default render pass, very simple one.
// Other examples will mostly override this one.
//
virtual void createRenderPass();
//--------------------------------------------------------------------------------------------------
// Creating an image to be used as depth buffer
//
virtual void createDepthBuffer();
//--------------------------------------------------------------------------------------------------
// Convenient function to call before rendering
//
void prepareFrame();
//--------------------------------------------------------------------------------------------------
// Convenient function to call for submitting the rendering command
//
virtual void submitFrame();
//--------------------------------------------------------------------------------------------------
// When the pipeline is set for using dynamic, this becomes useful
//
void setViewport(const vk::CommandBuffer& cmdBuf);
//--------------------------------------------------------------------------------------------------
// Window callback when the it is resized
// - Destroy allocated frames, then rebuild them with the new size
// - Call onResize() of the derived class
//
virtual void onFramebufferSize(int w, int h);
//--------------------------------------------------------------------------------------------------
// Window callback when the mouse move
// - Handling ImGui and a default camera
//
virtual void onMouseMotion(int x, int y);
//--------------------------------------------------------------------------------------------------
// Window callback when a special key gets hit
// - Handling ImGui and a default camera
//
virtual void onKeyboard(int key, int /*scancode*/, int action, int mods);
//--------------------------------------------------------------------------------------------------
// Window callback when a key gets hit
//
virtual void onKeyboardChar(unsigned char key);
//--------------------------------------------------------------------------------------------------
// Window callback when the mouse button is pressed
// - Handling ImGui and a default camera
//
virtual void onMouseButton(int button, int action, int mods);
//--------------------------------------------------------------------------------------------------
// Window callback when the mouse wheel is modified
// - Handling ImGui and a default camera
//
virtual void onMouseWheel(int delta);
virtual void onFileDrop(const char* filename);
//--------------------------------------------------------------------------------------------------
// Initialization of the GUI
// - Need to be call after the device creation
//
void initGUI(uint32_t subpassID = 0);
//--------------------------------------------------------------------------------------------------
// Fit the camera to the Bounding box
//
void fitCamera(const glm::vec3& boxMin, const glm::vec3& boxMax, bool instantFit = true);
// Return true if the window is minimized
bool isMinimized(bool doSleeping = true);
void setTitle(const std::string& title);
// GLFW Callback setup
void setupGlfwCallbacks(GLFWwindow* window);
static void framebuffersize_cb(GLFWwindow* window, int w, int h);
static void mousebutton_cb(GLFWwindow* window, int button, int action, int mods);
static void cursorpos_cb(GLFWwindow* window, double x, double y);
static void scroll_cb(GLFWwindow* window, double x, double y);
static void key_cb(GLFWwindow* window, int key, int scancode, int action, int mods);
static void char_cb(GLFWwindow* window, unsigned int key);
static void drop_cb(GLFWwindow* window, int count, const char** paths);
// GLFW Callback end
// Set if Nvlink will be used
void useNvlink(bool useNvlink);
//--------------------------------------------------------------------------------------------------
// Getters
vk::Instance getInstance();
vk::Device getDevice();
vk::PhysicalDevice getPhysicalDevice();
vk::Queue getQueue();
uint32_t getQueueFamily();
vk::CommandPool getCommandPool();
vk::RenderPass getRenderPass();
vk::Extent2D getSize();
vk::PipelineCache getPipelineCache();
vk::SurfaceKHR getSurface();
const std::vector<vk::Framebuffer>& getFramebuffers();
const std::vector<vk::CommandBuffer>& getCommandBuffers();
uint32_t getCurFrame() const;
vk::Format getColorFormat() const;
vk::Format getDepthFormat() const;
bool showGui();
protected:
uint32_t getMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties) const;
// Showing help
void uiDisplayHelp();
//--------------------------------------------------------------------------------------------------
// Called every frame to translate currently pressed keys into camera movement
//
void updateCamera();
//--------------------------------------------------------------------------------------------------
// Vulkan low level
vk::Instance m_instance;
vk::Device m_device;
vk::SurfaceKHR m_surface;
vk::PhysicalDevice m_physicalDevice;
vk::Queue m_queue;
uint32_t m_graphicsQueueIndex{VK_QUEUE_FAMILY_IGNORED};
vk::CommandPool m_cmdPool;
vk::DescriptorPool m_imguiDescPool;
// Drawing/Surface
nvvk::SwapChain m_swapChain;
std::vector<vk::Framebuffer> m_framebuffers; // All framebuffers, correspond to the Swapchain
std::vector<vk::CommandBuffer> m_commandBuffers; // Command buffer per nb element in Swapchain
std::vector<vk::Fence> m_waitFences; // Fences per nb element in Swapchain
vk::Image m_depthImage; // Depth/Stencil
vk::DeviceMemory m_depthMemory; // Depth/Stencil
vk::ImageView m_depthView; // Depth/Stencil
vk::RenderPass m_renderPass; // Base render pass
vk::Extent2D m_size{0, 0}; // Size of the window
vk::PipelineCache m_pipelineCache; // Cache for pipeline/shaders
bool m_vsync{false}; // Swapchain with vsync
bool m_useNvlink{false}; // NVLINK usage
GLFWwindow* m_window{nullptr}; // GLFW Window
// Surface buffer formats
vk::Format m_colorFormat{vk::Format::eB8G8R8A8Unorm};
vk::Format m_depthFormat{vk::Format::eUndefined};
// Camera manipulators
nvh::CameraManipulator::Inputs m_inputs; // Mouse button pressed
std::set<int> m_keys; // Keyboard pressed
nvh::Stopwatch m_timer; // measure time from frame to frame to base camera movement on
// Other
bool m_showHelp{false}; // Show help, pressing
bool m_show_gui{true};
};
} // namespace nvvkhl

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,241 @@
/*
* Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <functional>
#include <memory>
#include <vulkan/vulkan_core.h>
#include <glm/glm.hpp>
#include "nvvk/context_vk.hpp"
#include "imgui.h"
/* @DOC_START
# class nvvk::Application
The Application is basically a small modification of the ImGui example for Vulkan.
Because we support multiple viewports, duplicating the code would be not necessary
and the code is very well explained.
To use the application,
* Fill the ApplicationCreateInfo with all the information, including the Vulkan creation information (nvvk::ContextCreateInfo).
* Attach elements to the application, such as rendering, camera, etc.
* Call run() to start the application.
*
* The application will create the window, the Vulkan context, and the ImGui context.
Worth notice
* ::init() : will create the GLFW window, call nvvk::context for the creation of the
Vulkan context, initialize ImGui , create the surface and window (::setupVulkanWindow)
* ::shutdown() : the oposite of init
* ::run() : while running, render the frame and present the frame. Check for resize, minimize window
and other changes. In the loop, it will call some functions for each 'element' that is connected.
onUIRender, onUIMenu, onRender. See IApplication for details.
* The Application is a singleton, and the main loop is inside the run() function.
* The Application is the owner of the elements, and it will call the onRender, onUIRender, onUIMenu
for each element that is connected to it.
* The Application is the owner of the Vulkan context, and it will create the surface and window.
* The Application is the owner of the ImGui context, and it will create the dockspace and the main menu.
* The Application is the owner of the GLFW window, and it will create the window and handle the events.
The application itself does not render per se. It contains control buffers for the images in flight,
it calls ImGui rendering for Vulkan, but that's it. Note that none of the samples render
directly into the swapchain. Instead, they render into an image, and the image is displayed in the ImGui window
window called "Viewport".
Application elements must be created to render scenes or add "elements" to the application. Several elements
can be added to an application, and each of them will be called during the frame. This allows the application
to be divided into smaller parts, or to reuse elements in various samples. For example, there is an element
that adds a default menu (File/Tools), another that changes the window title with FPS, the resolution, and there
is also an element for our automatic tests.
Each added element will be called in a frame, see the IAppElement interface for information on virtual functions.
Basically there is a call to create and destroy, a call to render the user interface and a call to render the
frame with the command buffer.
Note: order of Elements can be important if one depends on the other. For example, the ElementCamera should
be added before the rendering sample, such that its matrices are updated before pulled by the renderer.
@DOC_END */
// Forward declarations
struct GLFWwindow;
struct ImGui_ImplVulkanH_Window;
namespace nvvkhl {
// Forward declarations
struct ContextCreateInfo;
class Context;
struct IAppElement;
// Information for creating the application
struct ApplicationCreateInfo
{
std::string name{"Vulkan App"}; // Name of the GLFW
int32_t width{-1}; // Width of the Window
int32_t height{-1}; // Height of the window
bool vSync{true}; // Is V-Sync on by default?
bool useMenu{true}; // Is the application will have a menubar?
bool useDockMenu{false}; // Is there an extra menubar ?
bool hasUndockableViewport{true}; // Create and use a default viewport
nvvk::ContextCreateInfo vkSetup{}; // Vulkan creation context information (see nvvk::Context)
std::vector<int> ignoreDbgMessages; // Turn off debug messages
ImVec4 clearColor{0.F, 0.F, 0.F, 1.F};
std::function<void(ImGuiID)> dockSetup; // Allow to configure the dock layout
ImGuiConfigFlags imguiConfigFlags =
ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_ViewportsEnable;
};
class Application
{
public:
explicit Application(ApplicationCreateInfo& info);
virtual ~Application();
// Application control
void run(); // Run indefinitely until close is requested
void close(); // Stopping the application
// Adding engines
template <typename T>
void addElement();
void addElement(const std::shared_ptr<IAppElement>& layer);
// Safely freeing up resources
static void submitResourceFree(std::function<void()>&& func);
// Utilities
void setViewport(const VkCommandBuffer& cmd); // Set viewport and scissor the the size of the viewport
bool isVsync() const; // Return true if V-Sync is on
void setVsync(bool v); // Set V-Sync on or off
void setViewportClearColor(ImVec4 col) { m_clearColor = col; }
// Following three functions affect the preparationg of the current frame's submit info.
// Content is appended to vectors that are reset every frame
void addWaitSemaphore(const VkSemaphoreSubmitInfoKHR& wait);
void addSignalSemaphore(const VkSemaphoreSubmitInfoKHR& signal);
// these command buffers are enqueued before the command buffer that is provided `onRender(cmd)`
void prependCommandBuffer(const VkCommandBufferSubmitInfoKHR& cmd);
VkCommandBuffer createTempCmdBuffer();
void submitAndWaitTempCmdBuffer(VkCommandBuffer cmd);
// Getters
inline std::shared_ptr<nvvk::Context> getContext() { return m_context; } // Return the Vulkan context
inline VkCommandPool getCommandPool() { return m_cmdPool; } // Return command pool to create command buffers
inline GLFWwindow* getWindowHandle() { return m_windowHandle; } // Return the handle of the Window
inline const VkExtent2D& getViewportSize() { return m_viewportSize; } // Return the size of the rendering viewport
inline const VkExtent2D& getWindowSize() { return m_windowSize; } // Return the size of the window
inline VkDevice getDevice() { return m_context->m_device; }
inline VkInstance getInstance() { return m_context->m_instance; }
inline VkPhysicalDevice getPhysicalDevice() { return m_context->m_physicalDevice; }
inline const nvvk::Context::Queue& getQueueGCT() { return m_context->m_queueGCT; }
inline const nvvk::Context::Queue& getQueueC() { return m_context->m_queueC; }
inline const nvvk::Context::Queue& getQueueT() { return m_context->m_queueT; }
inline const uint32_t getFrameCycleIndex() const { return m_currentFrameIndex; }
inline const uint32_t getFrameCycleSize() const { return uint32_t(m_resourceFreeQueue.size()); }
void onFileDrop(const char* filename);
void screenShot(const std::string& filename, int quality = 100); // Delay the screen shot to the end of the frame
private:
void init(ApplicationCreateInfo& info);
void shutdown();
void frameRender();
void framePresent();
void setupVulkanWindow(VkSurfaceKHR surface, int width, int height);
void createDock() const;
void setPresentMode(VkPhysicalDevice physicalDevice, ImGui_ImplVulkanH_Window* wd);
void createDescriptorPool();
void saveScreenShot(const std::string& filename, int quality); // Immediately save the frame
void resetFreeQueue(uint32_t size);
std::shared_ptr<nvvk::Context> m_context;
std::vector<std::shared_ptr<IAppElement>> m_elements;
bool m_running{false}; // Is currently running
bool m_useMenubar{true}; // Will use a menubar
bool m_useDockMenubar{false}; // Will use an exta menubar
bool m_vsyncWanted{true}; // Wanting swapchain with vsync
bool m_vsyncSet{true}; // Vsync currently set
int m_minImageCount{2}; // Nb frames in-flight
bool m_swapChainRebuild{false}; // Need to rebuild swapchain?
bool m_hasUndockableViewport{true}; // Using a default viewport
std::string m_iniFilename; // Holds on .ini name
ImVec4 m_clearColor{0.0F, 0.0F, 0.0F, 1.0F};
VkCommandPool m_cmdPool{VK_NULL_HANDLE}; //
VkPipelineCache m_pipelineCache{VK_NULL_HANDLE}; // Cache for pipeline/shaders
VkDescriptorPool m_descriptorPool{VK_NULL_HANDLE}; //
GLFWwindow* m_windowHandle{nullptr}; // GLFW Window
VkExtent2D m_viewportSize{0, 0}; // Size of the viewport
VkExtent2D m_windowSize{0, 0}; // Size of the window
VkAllocationCallbacks* m_allocator{nullptr};
std::vector<VkSemaphoreSubmitInfoKHR> m_waitSemaphores; // Possible extra frame wait semaphores
std::vector<VkSemaphoreSubmitInfoKHR> m_signalSemaphores; // Possible extra frame signal semaphores
std::vector<VkCommandBufferSubmitInfoKHR> m_commandBuffers; // Possible extra frame command buffers
std::unique_ptr<ImGui_ImplVulkanH_Window> m_mainWindowData;
//--
static uint32_t m_currentFrameIndex; // (eg. 0, 1, 2, 0, 1, 2)
static std::vector<std::vector<std::function<void()>>> m_resourceFreeQueue; // Queue of functions to free resources
//--
std::function<void(ImGuiID)> m_dockSetup;
bool m_screenShotRequested = false;
std::string m_screenShotFilename = "";
int m_screenShotQuality = 100;
};
template <typename T>
void Application::addElement()
{
static_assert(std::is_base_of<IAppElement, T>::value, "Type is not subclass of IApplication");
m_elements.emplace_back(std::make_shared<T>())->onAttach(this);
}
/*
* Interface for application elements
*/
struct IAppElement
{
// Interface
virtual void onAttach(Application* app) {} // Called once at start
virtual void onDetach() {} // Called before destroying the application
virtual void onResize(uint32_t width, uint32_t height) {} // Called when the viewport size is changing
virtual void onUIRender() {} // Called for anything related to UI
virtual void onUIMenu() {} // This is the menubar to create
virtual void onRender(VkCommandBuffer cmd) {} // For anything to render within a frame
virtual void onFileDrop(const char* filename) {} // For when a file is dragged on top of the window
virtual ~IAppElement() = default;
};
} // namespace nvvkhl

View file

@ -0,0 +1,435 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <array>
#include "application.hpp"
#include "nvh/commandlineparser.hpp"
#include "nvh/fileoperations.hpp"
#include "nvh/nvprint.hpp"
#include "nvh/parametertools.hpp"
#include "nvh/profiler.hpp"
#include "nvh/timesampler.hpp"
#include "nvpsystem.hpp"
#include "nvpwindow.hpp"
#include "nvvk/error_vk.hpp"
#include "nvvk/profiler_vk.hpp"
#include "GLFW/glfw3.h"
namespace nvvkhl {
/** @DOC_START
# class nvvkhl::ElementBenchmarkParameters
This element allows you to control an application with command line parameters. There are default
parameters, but others can be added using the parameterLists().add(..) function.
It can also use a file containing several sets of parameters, separated by "benchmark" and
which can be used to benchmark an application.
If a profiler is set, the measured performance at the end of each benchmark group is logged.
There are default parameters that can be used:
-logfile Set a logfile.txt. If string contains $DEVICE$ it will be replaced by the GPU device name
-winsize Set window size (width and height)
-winpos Set window position (x and y)
-vsync Enable or disable vsync
-screenshot Save a screenshot into this file
-benchmarkframes Set number of benchmarkframes
-benchmark Set benchmark filename
-test Enabling Testing
-test-frames If test is on, number of frames to run
-test-time If test is on, time that test will run
Example of Setup:
```cpp
std::shared_ptr<nvvkhl::ElementBenchmarkParameters> g_benchmark;
std::shared_ptr<nvvkhl::ElementProfiler> g_profiler;
main() {
...
g_benchmark = std::make_shared<nvvkhl::ElementBenchmarkParameters>(argc, argv);
g_profiler = std::make_shared<nvvkhl::ElementProfiler>(false);
g_benchmark->setProfiler(g_profiler);
app->addElement(g_profiler);
app->addElement(g_benchmark);
...
}
```
Applications can also get their parameters modified:
```cpp
void MySample::MySample() {
g_benchmark->parameterLists().add("speed|The speed", &m_speed);
g_benchmark->parameterLists().add("color", &m_color, nullptr, 3);
g_benchmark->parameterLists().add("complex", &m_complex, [&](int p){ doSomething(); });
```cpp
Example of a benchmark.txt could look like
\code{.bat}
#how many frames to average
-benchmarkframes 12
-winpos 10 10
-winsize 500 500
benchmark "No vsync"
-vsync 0
-benchmarkframes 100
-winpos 500 500
-winsize 100 100
benchmark "Image only"
-screenshot "temporal_mdi.jpg"
```
@DOC_END */
class ElementBenchmarkParameters : public nvvkhl::IAppElement
{
public:
struct Benchmark
{
bool initialized = false;
std::string filename;
std::string content;
nvh::ParameterSequence sequence;
uint32_t frameLength = 256;
uint32_t frame = 0;
};
struct Config
{
int32_t winpos[2] = {0, 0};
int32_t winsize[2] = {0, 0};
bool vsyncstate = true;
std::string screenshotFilename;
std::string logFilename;
bool testEnabled = false;
uint32_t testMaxFrames = 0;
float testMaxTime = 0;
};
ElementBenchmarkParameters(int argc, char** argv)
: m_argc(argc - 1) // Skip executable
, m_argv(argv + 1)
{
setupParameters(); // All parameter handled by benchmark
// By default this class increase the frame every time it goes through onRender.
// But this could be override externally to provide the actual rendered frame it is looking for.
getCurrentFrame = [&]() -> int { return ++m_currentFrame; };
}
~ElementBenchmarkParameters() = default;
// Get access to the parameter list, to add parameters that application wants modified
nvh::ParameterList& parameterLists() { return m_parameterList; }
// Add a callback when advancing in the benchmark
void addPostBenchmarkAdvanceCallback(std::function<void()>&& func) { m_postCallback.emplace_back(func); }
// Set the frame number from an external view
void setCurrentFrame(std::function<int()>&& func) { getCurrentFrame = func; }
// External profiler, if profiling is required.
void setProfiler(std::shared_ptr<nvvk::ProfilerVK> profiler) { m_profiler = profiler; }
int errorCode() { return m_errorMessages.empty() ? 0 : 1; }
const Benchmark& benchmark() const { return m_benchmark; }
const Config& config() const { return m_config; }
///
/// IAppElement implementation
///
void onAttach(Application* app) override
{
m_app = app;
// Parse all arguments, now that the application is attached
parseConfig(m_argc, const_cast<const char**>(m_argv), ".");
initBenchmark(); // -benchmark <file.txt>
initTesting(); // -test
m_startTime = ImGui::GetTime();
}
void onDetach() override { deinitTesting(); }
void onUIRender() override { advanceBenchmark(); }
void onRender(VkCommandBuffer /*cmd*/) override
{
m_currentFrame = getCurrentFrame();
executeTest();
}
protected:
virtual void parseConfig(int argc, const char** argv, const std::string& path)
{
// if you want to handle parameters not represented in
// m_parameterList then override this function accordingly.
m_parameterList.applyTokens(argc, argv, "-", path.c_str());
// This function is called before "begin" and provided with the commandline used in "run".
// It can also be called by the benchmarking system, and parseConfigFile.
}
private:
// This function replaces all occurrences of the substring 'from' with the substring 'to' in the given string 'str'.
void replace(std::string& str, const std::string& from, const std::string& to)
{
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos)
{
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
}
void setLogfile()
{
if(!m_config.logFilename.empty() && m_app)
{
std::string deviceName = m_app->getContext()->m_physicalInfo.properties10.deviceName;
std::string logfileName = m_config.logFilename; // Replace "$DEVICE$" with the GPU device name
replace(logfileName, "$DEVICE$", deviceName);
std::replace_if( // Replace characters not allowed in filenames with underscores
logfileName.begin(), logfileName.end(),
[](char c) { return !(std::isalnum(c) || c == '_' || c == '/' || c == '.' || c == '-'); }, '_');
nvprintSetLogFileName(logfileName.c_str());
}
}
void setupParameters()
{
m_parameterList.add(
"winsize|Set window size (width and height)", m_config.winsize,
[&](uint32_t) { glfwSetWindowSize(m_app->getWindowHandle(), m_config.winsize[0], m_config.winsize[1]); }, 2);
m_parameterList.add(
"winpos|Set window position (x and y)", m_config.winpos,
[&](uint32_t) { glfwSetWindowPos(m_app->getWindowHandle(), m_config.winpos[0], m_config.winpos[1]); }, 2);
m_parameterList.add("vsync|Enable or disable vsync", &m_config.vsyncstate,
[&](uint32_t) { m_app->setVsync(m_config.vsyncstate); });
m_parameterList.addFilename("logfile|Set logfile", &m_config.logFilename, [&](uint32_t) { setLogfile(); });
m_parameterList.add("screenshot|makes a screenshot into this file", &m_config.screenshotFilename, [&](uint32_t) {
// We don't want to capture the command line, only when it is part of the benchmark
if(!m_config.screenshotFilename.empty() && !m_benchmark.content.empty())
{
m_app->screenShot(m_config.screenshotFilename.c_str());
}
});
m_parameterList.add("benchmarkframes|Set number of benchmarkframes", &m_benchmark.frameLength);
m_parameterList.addFilename("benchmark|Set benchmark filename", &m_benchmark.filename);
m_parameterList.add("test|Testing Mode", &m_config.testEnabled, true);
m_parameterList.add("test-frames|If test is on, number of frames to run", &m_config.testMaxFrames);
m_parameterList.add("test-time|If test is on, time that test will run", &m_config.testMaxTime);
}
void initBenchmark()
{
if(m_benchmark.initialized)
return;
m_benchmark.initialized = true;
if(m_benchmark.filename.empty())
return;
m_benchmark.content = nvh::loadFile(m_benchmark.filename.c_str(), false);
if(!m_benchmark.content.empty())
{
std::vector<const char*> tokens;
nvh::ParameterList::tokenizeString(m_benchmark.content, tokens);
std::string path = nvh::getFilePath(m_benchmark.filename.c_str());
m_benchmark.sequence.init(&m_parameterList, tokens);
// do first iteration manually, due to custom arg parsing
uint32_t argBegin;
uint32_t argCount;
if(!m_benchmark.sequence.advanceIteration("benchmark", 1, argBegin, argCount))
{
parseConfig(argCount, &tokens[argBegin], path);
}
m_benchmark.frame = 0;
}
}
void advanceBenchmark()
{
if(!m_benchmark.sequence.isActive())
return;
m_benchmark.frame++;
if(m_benchmark.frame > m_benchmark.frameLength + nvh::Profiler::CONFIG_DELAY + nvh::Profiler::FRAME_DELAY)
{
m_benchmark.frame = 0;
std::string stats;
if(m_profiler)
m_profiler->print(stats);
LOGI("BENCHMARK %d \"%s\" {\n", m_benchmark.sequence.getIteration(), m_benchmark.sequence.getSeparatorArg(0));
LOGI("%s}\n\n", stats.c_str());
bool done = m_benchmark.sequence.applyIteration("benchmark", 1, "-");
if(m_profiler)
m_profiler->reset(nvh::Profiler::CONFIG_DELAY);
// Callback all registered functions
for(auto& func : m_postCallback)
func();
if(done)
{
m_app->close(); // request to stop
}
}
}
void initTesting()
{
if(!m_config.testEnabled)
return;
// Setting defaults
if(m_config.testMaxFrames == 0 && m_config.testMaxTime == 0)
m_config.testMaxFrames = 1;
if(m_config.testMaxFrames == 0)
m_config.testMaxFrames = std::numeric_limits<uint32_t>::max();
if(m_config.testMaxTime == 0)
m_config.testMaxTime = std::numeric_limits<float>::max();
// The following is adding a callback for Vulkan messages, and collect all error messages.
// If errors are found errorCode will return 1, otherwise 0
VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
dbg_messenger_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT // Vulkan issues
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; // Invalid usage
dbg_messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT // Other
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; // Violation of spec
dbg_messenger_create_info.pUserData = this;
dbg_messenger_create_info.pfnUserCallback = [](VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT /*messageType*/,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData) {
ElementBenchmarkParameters* elementBenchParamClass = reinterpret_cast<ElementBenchmarkParameters*>(userData);
if(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
{
elementBenchParamClass->addError(callbackData->pMessage);
}
return VK_FALSE;
};
NVVK_CHECK(vkCreateDebugUtilsMessengerEXT(m_app->getContext()->m_instance, &dbg_messenger_create_info, nullptr, &m_dbgMessenger));
}
void deinitTesting()
{
if(m_dbgMessenger != nullptr)
vkDestroyDebugUtilsMessengerEXT(m_app->getContext()->m_instance, m_dbgMessenger, nullptr);
}
void executeTest()
{
if(!m_config.testEnabled)
return;
bool closingApp = false;
double elapseTime = (ImGui::GetTime() - m_startTime);
uint32_t maxFrames =
std::max(m_config.testMaxFrames, m_profiler ? nvh::Profiler::CONFIG_DELAY + nvh::Profiler::FRAME_DELAY : 0);
closingApp |= elapseTime >= m_config.testMaxTime;
closingApp |= m_currentFrame >= static_cast<int>(maxFrames);
if(closingApp)
{
if(!m_config.screenshotFilename.empty())
{
m_app->screenShot(m_config.screenshotFilename.c_str());
}
if(m_profiler)
{
std::string stats;
m_profiler->print(stats);
LOGI("%s", stats.c_str());
}
LOGI("Number of frames: %d\n", m_currentFrame);
LOGI("Testing Time: %.3f s\n", elapseTime);
// Signal errors
if(!m_errorMessages.empty())
{
LOGE("+-+ ERRORS +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
for(auto& e : m_errorMessages)
LOGE("%s\n", e.c_str());
LOGE("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
}
m_app->close(); // Request to close
}
}
void addError(const char* msg) { m_errorMessages.emplace_back(msg); }
//---------------------------------
Application* m_app = nullptr;
VkDebugUtilsMessengerEXT m_dbgMessenger = nullptr;
std::vector<std::function<void()>> m_postCallback; // To get called after a new benchmark setting
std::function<int()> getCurrentFrame; // To get the current frame from possible external source
std::vector<std::string> m_errorMessages; // Collect Vulkan error messages
Benchmark m_benchmark; // Benchmark file setting
Config m_config; // Current settings
nvh::ParameterList m_parameterList; // List of all command line parameters, from this class and external when set
int m_currentFrame = 0; // Current states
double m_startTime = 0.f;
// Keeping the command line argument, until the application attachment
int m_argc;
char** m_argv;
std::shared_ptr<nvvk::ProfilerVK> m_profiler; // [optional]
};
} // namespace nvvkhl

View file

@ -0,0 +1,166 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2023 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include "nvh/cameramanipulator.hpp"
#include "nvvkhl/application.hpp"
/** @DOC_START
# class nvvkhl::ElementCamera
This class is an element of the application that is responsible for the camera manipulation. It is using the `nvh::CameraManipulator` to handle the camera movement and interaction.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
@DOC_END */
namespace nvvkhl {
struct ElementCamera : public nvvkhl::IAppElement
{
// Return true if the current window is active
bool isWindowHovered(ImGuiWindow* ref_window, ImGuiHoveredFlags flags)
{
ImGuiContext& g = *ImGui::GetCurrentContext();
if(g.HoveredWindow != ref_window)
return false;
if(!ImGui::IsWindowContentHoverable(ref_window, ImGuiFocusedFlags_RootWindow))
return false;
if(g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != ref_window->MoveId)
return false;
// Cancel if over the title bar
{
if(g.IO.ConfigWindowsMoveFromTitleBarOnly)
if(!(ref_window->Flags & ImGuiWindowFlags_NoTitleBar) || ref_window->DockIsActive)
if(ref_window->TitleBarRect().Contains(g.IO.MousePos))
return false;
}
return true;
}
void onUIRender() override
{
CameraManip.updateAnim(); // This makes the camera to transition smoothly to the new position
ImGuiWindow* viewportWindow = ImGui::FindWindowByName("Viewport");
if(!viewportWindow)
return;
// If the mouse cursor is over the "Viewport", check for all inputs that can manipulate
// the camera.
if(isWindowHovered(viewportWindow, ImGuiFocusedFlags_RootWindow))
{
updateCamera();
}
}
void onResize(uint32_t width, uint32_t height) override { CameraManip.setWindowSize(width, height); }
//--------------------------------------------------------------------------------------------------
// Fit the camera to the Bounding box
//
void fitCamera(const glm::vec3& boxMin, const glm::vec3& boxMax, bool instantFit /*= true*/) const
{
float aspect_ratio = static_cast<float>(m_viewportSize.x / m_viewportSize.y);
CameraManip.fit(boxMin, boxMax, instantFit, false, aspect_ratio);
}
void setSceneRadius(float r) { m_sceneRadius = r; }
private:
void updateCamera()
{
// measure one frame at a time
float factor = ImGui::GetIO().DeltaTime * 5.0F * m_sceneRadius;
m_inputs.lmb = ImGui::IsMouseDown(ImGuiMouseButton_Left);
m_inputs.rmb = ImGui::IsMouseDown(ImGuiMouseButton_Right);
m_inputs.mmb = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
m_inputs.ctrl = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl);
m_inputs.shift = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
m_inputs.alt = ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt);
auto mouse_pos = ImGui::GetMousePos();
// For all pressed keys - apply the action
CameraManip.keyMotion(0, 0, nvh::CameraManipulator::NoAction);
if(!(ImGui::IsKeyDown(ImGuiMod_Alt) || ImGui::IsKeyDown(ImGuiMod_Ctrl) || ImGui::IsKeyDown(ImGuiMod_Shift)))
{
if(ImGui::IsKeyDown(ImGuiKey_W))
{
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Dolly);
}
if(ImGui::IsKeyDown(ImGuiKey_S))
{
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Dolly);
}
if(ImGui::IsKeyDown(ImGuiKey_D) || ImGui::IsKeyDown(ImGuiKey_RightArrow))
{
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Pan);
}
if(ImGui::IsKeyDown(ImGuiKey_A) || ImGui::IsKeyDown(ImGuiKey_LeftArrow))
{
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Pan);
}
if(ImGui::IsKeyDown(ImGuiKey_UpArrow))
{
CameraManip.keyMotion(0, factor, nvh::CameraManipulator::Pan);
}
if(ImGui::IsKeyDown(ImGuiKey_DownArrow))
{
CameraManip.keyMotion(0, -factor, nvh::CameraManipulator::Pan);
}
}
if(ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseClicked(ImGuiMouseButton_Middle)
|| ImGui::IsMouseClicked(ImGuiMouseButton_Right))
{
CameraManip.setMousePosition(static_cast<int>(mouse_pos.x), static_cast<int>(mouse_pos.y));
}
if(ImGui::IsMouseDragging(ImGuiMouseButton_Left, 1.0F) || ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0F)
|| ImGui::IsMouseDragging(ImGuiMouseButton_Right, 1.0F))
{
CameraManip.mouseMove(static_cast<int>(mouse_pos.x), static_cast<int>(mouse_pos.y), m_inputs);
}
// Mouse Wheel
if(ImGui::GetIO().MouseWheel != 0.0F)
{
CameraManip.wheel(static_cast<int>(ImGui::GetIO().MouseWheel * 3), m_inputs);
}
}
ImVec2 m_viewportSize{0, 0};
float m_sceneRadius{10.0F};
nvh::CameraManipulator::Inputs m_inputs; // Mouse button pressed
};
} // namespace nvvkhl

View file

@ -0,0 +1,152 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "application.hpp"
#include "nvh/nvprint.hpp"
#include "nvvk/error_vk.hpp"
#include "imgui.h"
#include "imgui_internal.h"
namespace nvvkhl {
/** @DOC_START
# class nvvkhl::ElementDbgPrintf
> This class is an element of the application that is responsible for the debug printf in the shader. It is using the `VK_EXT_debug_printf` extension to print information from the shader.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
Create the element such that it will be available to the target application
- Example:
```cpp
std::shared_ptr<nvvkhl::ElementDbgPrintf> g_dbgPrintf = std::make_shared<nvvkhl::ElementDbgPrintf>();
```
Add to main
- Before creating the nvvkhl::Application, set:
` spec.vkSetup.instanceCreateInfoExt = g_dbgPrintf->getFeatures(); `
- Add the Element to the Application
` app->addElement(g_dbgPrintf); `
- In the target application, push the mouse coordinated
` m_pushConst.mouseCoord = g_dbgPrintf->getMouseCoord(); `
In the Shader, do:
- Add the extension
` #extension GL_EXT_debug_printf : enable `
- Where to get the information
```cpp
ivec2 fragCoord = ivec2(floor(gl_FragCoord.xy));
if(fragCoord == ivec2(pushC.mouseCoord))
debugPrintfEXT("Value: %f\n", myVal);
```
@DOC_END */
class ElementDbgPrintf : public nvvkhl::IAppElement
{
public:
ElementDbgPrintf() = default;
static VkLayerSettingsCreateInfoEXT* getFeatures()
{
// #debug_printf
// Adding the GPU debug information to the KHRONOS validation layer
// See: https://vulkan.lunarg.com/doc/sdk/1.3.275.0/linux/khronos_validation_layer.html
static const char* layer_name = "VK_LAYER_KHRONOS_validation";
static const char* validate_gpu_based[] = {"GPU_BASED_DEBUG_PRINTF"};
static const VkBool32 printf_verbose = VK_FALSE;
static const VkBool32 printf_to_stdout = VK_FALSE;
static const int32_t printf_buffer_size = 1024;
static const VkLayerSettingEXT settings[] = {
{layer_name, "validate_gpu_based", VK_LAYER_SETTING_TYPE_STRING_EXT,
static_cast<uint32_t>(std::size(validate_gpu_based)), &validate_gpu_based},
{layer_name, "printf_verbose", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &printf_verbose},
{layer_name, "printf_to_stdout", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &printf_to_stdout},
{layer_name, "printf_buffer_size", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &printf_buffer_size},
};
static VkLayerSettingsCreateInfoEXT layer_settings_create_info = {
.sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT,
.settingCount = static_cast<uint32_t>(std::size(settings)),
.pSettings = settings,
};
return &layer_settings_create_info;
}
// Return the relative mouse coordinates in the window named "Viewport"
glm::vec2 getMouseCoord() { return m_mouseCoord; }
void onAttach(Application* app) override
{
m_instance = app->getContext()->m_instance;
// Vulkan message callback - for receiving the printf in the shader
// Note: there is already a callback in nvvk::Context, but by defaut it is not printing INFO severity
// this callback will catch the message and will make it clean for display.
auto dbg_messenger_callback = [](VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData) -> VkBool32 {
// Get rid of all the extra message we don't need
std::string clean_msg = callbackData->pMessage;
const std::string searchStr = "vkQueueSubmit(): ";
std::size_t pos = clean_msg.find(searchStr);
if(pos != std::string::npos)
{
clean_msg = clean_msg.substr(pos + searchStr.size() + 1); // Remove everything before the search string
}
LOGI("%s", clean_msg.c_str()); // <- This will end up in the Logger
return VK_FALSE; // to continue
};
// Creating the callback
VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
dbg_messenger_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
dbg_messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
dbg_messenger_create_info.pfnUserCallback = dbg_messenger_callback;
NVVK_CHECK(vkCreateDebugUtilsMessengerEXT(m_instance, &dbg_messenger_create_info, nullptr, &m_dbgMessenger));
}
void onDetach() override { vkDestroyDebugUtilsMessengerEXT(m_instance, m_dbgMessenger, nullptr); }
void onUIRender() override
{
// Pick the mouse coordinate if the mouse is down
if(ImGui::GetIO().MouseDown[0])
{
ImGuiWindow* window = ImGui::FindWindowByName("Viewport");
assert(window);
const glm::vec2 mouse_pos = {ImGui::GetMousePos().x, ImGui::GetMousePos().y}; // Current mouse pos in window
const glm::vec2 corner = {window->Pos.x, window->Pos.y}; // Corner of the viewport
m_mouseCoord = mouse_pos - corner;
}
else
{
m_mouseCoord = {-1, -1};
}
}
private:
VkInstance m_instance = {};
glm::vec2 m_mouseCoord = {-1, -1};
VkDebugUtilsMessengerEXT m_dbgMessenger = {};
};
} // namespace nvvkhl

View file

@ -0,0 +1,173 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
// Various Application utilities
// - Display a menu with File/Quit
// - Display basic information in the window title
#include "imgui.h"
#include "implot.h"
#include "application.hpp"
#include "GLFW/glfw3.h"
// Use:
// include this file at the end of all other includes,
// and add engines
//
// Ex:
// app->addEngine(std::make_shared<AppDefaultMenu>());
//
namespace nvvkhl {
/** @DOC_START
# class nvvkhl::ElementDefaultMenu
> This class is an element of the application that is responsible for the default menu of the application. It is using the `ImGui` library to create a menu with File/Exit and View/V-Sync.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
@DOC_END */
class ElementDefaultMenu : public nvvkhl::IAppElement
{
public:
void onAttach(nvvkhl::Application* app) override { m_app = app; }
void onUIMenu() override
{
static bool close_app{false};
bool v_sync = m_app->isVsync();
#ifndef NDEBUG
static bool s_showDemo{false};
static bool s_showDemoPlot{false};
#endif
if(ImGui::BeginMenu("File"))
{
if(ImGui::MenuItem("Exit", "Ctrl+Q"))
{
close_app = true;
}
ImGui::EndMenu();
}
if(ImGui::BeginMenu("View"))
{
ImGui::MenuItem("V-Sync", "Ctrl+Shift+V", &v_sync);
ImGui::EndMenu();
}
#ifndef NDEBUG
if(ImGui::BeginMenu("Debug"))
{
ImGui::MenuItem("Show ImGui Demo", nullptr, &s_showDemo);
ImGui::MenuItem("Show ImPlot Demo", nullptr, &s_showDemoPlot);
ImGui::EndMenu();
}
#endif // !NDEBUG
// Shortcuts
if(ImGui::IsKeyPressed(ImGuiKey_Q) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl))
{
close_app = true;
}
if(ImGui::IsKeyPressed(ImGuiKey_V) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyDown(ImGuiKey_LeftShift))
{
v_sync = !v_sync;
}
if(close_app)
{
m_app->close();
}
#ifndef NDEBUG
if(s_showDemo)
{
ImGui::ShowDemoWindow(&s_showDemo);
}
if(s_showDemoPlot)
{
ImPlot::ShowDemoWindow(&s_showDemoPlot);
}
#endif // !NDEBUG
if(m_app->isVsync() != v_sync)
{
m_app->setVsync(v_sync);
}
}
private:
nvvkhl::Application* m_app{nullptr};
};
/** @DOC_START
# class nvvkhl::ElementDefaultWindowTitle
> This class is an element of the application that is responsible for the default window title of the application. It is using the `GLFW` library to set the window title with the application name, the size of the window and the frame rate.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
@DOC_END */
class ElementDefaultWindowTitle : public nvvkhl::IAppElement
{
public:
ElementDefaultWindowTitle(const std::string& prefix = "", const std::string& suffix = "")
: m_prefix(prefix)
, m_suffix(suffix)
{
}
void onAttach(nvvkhl::Application* app) override { m_app = app; }
void onUIRender() override
{
// Window Title
m_dirtyTimer += ImGui::GetIO().DeltaTime;
if(m_dirtyTimer > 1.0F) // Refresh every seconds
{
const auto& size = m_app->getViewportSize();
std::string title;
if(!m_prefix.empty())
{
title += fmt::format("{} | ", m_prefix.c_str());
}
title += fmt::format("{} | {}x{} | {:.0f} FPS / {:.3f}ms", PROJECT_NAME, size.width, size.height,
ImGui::GetIO().Framerate, 1000.F / ImGui::GetIO().Framerate);
if(!m_suffix.empty())
{
title += fmt::format(" | {}", m_suffix.c_str());
}
glfwSetWindowTitle(m_app->getWindowHandle(), title.c_str());
m_dirtyTimer = 0;
}
}
void setPrefix(const std::string& str) { m_prefix = str; }
void setSuffix(const std::string& str) { m_suffix = str; }
private:
nvvkhl::Application* m_app{nullptr};
float m_dirtyTimer{0.0F};
std::string m_prefix;
std::string m_suffix;
};
} // namespace nvvkhl

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,647 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2023 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "nvvkhl/application.hpp"
#include "backends/imgui_impl_vulkan.h"
#include "nvvk/images_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
#include "nvvkhl/application.hpp"
#include <iosfwd>
#include <sstream>
#include "nvvk/descriptorsets_vk.hpp"
#include "nvh/timesampler.hpp"
#include "nvvk/commands_vk.hpp"
/** @DOC_START
# class nvvkhl::ElementInspector
--------------------------------------------------------------------------------------------------
This element is used to facilitate GPU debugging by inspection of:
- Image contents
- Buffer contents
- Variables in compute shaders
- Variables in fragment shaders
IMPORTANT NOTE: if used in a multi threaded environment synchronization needs to be performed
externally by the application.
Basic usage:
------------------------------------------------------------------------------------------------
### INITIALIZATION
------------------------------------------------------------------------------------------------
Create the element as a global variable, and add it to the applications
```cpp
std::shared_ptr<ElementInspector> g_inspectorElement = std::make_shared<ElementInspector>();
void main(...)
{
...
app->addElement(g_inspectorElement);
...
}
```
Upon attachment of the main app element, initialize the Inspector and specify the number of
buffers, images, compute shader variables and fragment shader variables that it will need to
inspect
```cpp
void onAttach(nvvkhl::Application* app) override
{
...
ElementInspector::InitInfo initInfo{};
initInfo.allocator = m_alloc.get();
initInfo.imageCount = imageInspectionCount;
initInfo.bufferCount = bufferInspectionCount;
initInfo.computeCount = computeInspectionCount;
initInfo.fragmentCount = fragmentInspectionCount;
initInfo.customCount = customInspectionCount;
initInfo.device = m_app->getDevice();
initInfo.graphicsQueueFamilyIndex = m_app->getQueueGCT().familyIndex;
g_inspectorElement->init(initInfo);
...
}
```
------------------------------------------------------------------------------------------------
### BUFFER INSPECTION
------------------------------------------------------------------------------------------------
Each inspection needs to be initialized before use:
Inspect a buffer of size bufferSize, where each entry contains 5 values. The buffer format specifies the data
structure of the entries. The following format is the equivalent of
```cpp
// struct
// {
// uint32_t counterU32;
// float counterF32;
// int16_t anI16Value;
// uint16_t myU16;
// int32_t anI32;
// };
```
```cpp
bufferFormat = std::vector<ElementInspector::ValueFormat>(5);
bufferFormat[0] = {ElementInspector::eUint32, "counterU32"};
bufferFormat[1] = {ElementInspector::eFloat32, "counterF32"};
bufferFormat[2] = {ElementInspector::eInt16, "anI16Value"};
bufferFormat[3] = {ElementInspector::eUint16, "myU16"};
bufferFormat[4] = {ElementInspector::eInt32, "anI32"};
ElementInspector::BufferInspectionInfo info{};
info.entryCount = bufferSize;
info.format = bufferFormat;
info.name = "myBuffer";
info.sourceBuffer = m_buffer.buffer;
g_inspectorElement->initBufferInspection(0, info);
```
When the inspection is desired, simply add it to the current command buffer. Required barriers are added internally.
IMPORTANT: the buffer MUST have been created with the VK_BUFFER_USAGE_TRANSFER_SRC_BIT flag
```cpp
g_inspectorElement->inspectBuffer(cmd, 0);
```
------------------------------------------------------------------------------------------------
### IMAGE INSPECTION
------------------------------------------------------------------------------------------------
Inspection of the image stored in m_texture, with format RGBA32F. Other formats can be specified using the syntax
above
```cpp
ElementInspector::ImageInspectionInfo info{};
info.createInfo = create_info;
info.format = g_inspectorElement->formatRGBA32();
info.name = "MyImageInspection";
info.sourceImage = m_texture.image;
g_inspectorElement->initImageInspection(0, info);
```
When the inspection is desired, simply add it to the current command buffer. Required barriers are added internally.
```cpp
g_inspectorElement->inspectImage(cmd, 0, imageCurrentLayout);
```
------------------------------------------------------------------------------------------------
### COMPUTE SHADER VARIABLE INSPECTION
------------------------------------------------------------------------------------------------
Inspect a compute shader variable for a given 3D grid and block size (use 1 for unused dimensions). This mode applies
to shaders where invocation IDs (e.g. gl_LocalInvocationID) are defined, such as compute, mesh and task shaders.
Since grids may contain many threads capturing a variable for all threads
may incur large memory consumption and performance loss. The blocks to inspect, and the warps within those blocks can
be specified using inspectedMin/MaxBlocks and inspectedMin/MaxWarp.
```cpp
computeInspectionFormat = std::vector<ElementInspector::ValueFormat>(...);
ElementInspector::ComputeInspectionInfo info{}; info.blockSize = blockSize;
// Create a 4-component vector format where each component is a uint32_t. The components will be labeled myVec4u.x,
// myVec4u.y, myVec4u.z, myVec4u.w in the UI
info.format = ElementInspector::formatVector4(eUint32, "myVec4u");
info.gridSizeInBlocks = gridSize;
info.minBlock = inspectedMinBlock;
info.maxBlock = inspectedMaxBlock;
info.minWarp = inspectedMinWarp;
info.maxWarp = inspectedMaxWarp;
info.name = "My Compute Inspection";
g_inspectorElement->initComputeInspection(0, info);
```
To allow variable inspection two buffers need to be made available to the target shader:
m_computeShader.updateBufferBinding(eThreadInspection, g_inspectorElement->getComputeInspectionBuffer(0));
m_computeShader.updateBufferBinding(eThreadMetadata, g_inspectorElement->getComputeMetadataBuffer(0));
The shader code needs to indicate include the Inspector header along with preprocessor variables to set the
inspection mode to Compute, and indicate the binding points for the buffers:
```cpp
#define INSPECTOR_MODE_COMPUTE
#define INSPECTOR_DESCRIPTOR_SET 0
#define INSPECTOR_INSPECTION_DATA_BINDING 1
#define INSPECTOR_METADATA_BINDING 2
#include "dh_inspector.h"
```
The inspection of a variable is then done as follows. For alignment purposes the inspection is done with a 32-bit
granularity. The shader is responsible for packing the inspected variables in 32-bit uint words. Those will be
unpacked within the Inspector for display according to the specified format.
```cpp
uint32_t myVariable = myFunction(...);
inspect32BitValue(0, myVariable);
```
The inspection is triggered on the host side right after the compute shader invocation:
```cpp
m_computeShader.dispatchBlocks(cmd, computGridSize, &constants);
g_inspectorElement->inspectComputeVariables(cmd, 0);
```
------------------------------------------------------------------------------------------------
### FRAGMENT SHADER VARIABLE INSPECTION
------------------------------------------------------------------------------------------------
Inspect a fragment shader variable for a given output image resolution. Since the image may have high resolution
capturing a variable for all threads may incur large memory consumption and performance loss. The bounding box of the
fragments to inspect can be specified using inspectedMin/MaxCoord.
IMPORTANT: Overlapping geometry may trigger
several fragment shader invocations for a given pixel. The inspection will only store the value of the foremost
fragment (with the lowest gl_FragCoord.z).
```cpp
fragmentInspectionFormat = std::vector<ElementInspector::ValueFormat>(...);
FragmentInspectionInfo info{};
info.name = "My Fragment Inspection";
info.format = fragmentInspectionFormat;
info.renderSize = imageSize;
info.minFragment = inspectedMinCoord;
info.maxFragment = inspectedMaxCoord;
g_inspectorElement->initFragmentInspection(0, info);
```
To allow variable inspection two storage buffers need to be declared in the pipeline layout and made available
as follows:
```cpp
std::vector<VkWriteDescriptorSet> writes;
const VkDescriptorBufferInfo inspectorInspection{
g_inspectorElement->getFragmentInspectionBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 1, &inspectorInspection));
const VkDescriptorBufferInfo inspectorMetadata{
g_inspectorElement->getFragmentMetadataBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 2, &inspectorMetadata));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
```
The shader code needs to indicate include the Inspector header along with preprocessor variables to set the
inspection mode to Fragment, and indicate the binding points for the buffers:
```cpp
#define INSPECTOR_MODE_FRAGMENT
#define INSPECTOR_DESCRIPTOR_SET 0
#define INSPECTOR_INSPECTION_DATA_BINDING 1
#define INSPECTOR_METADATA_BINDING 2
#include "dh_inspector.h"
```
The inspection of a variable is then done as follows. For alignment purposes the inspection is done with a 32-bit
granularity. The shader is responsible for packing the inspected variables in 32-bit uint words. Those will be
unpacked within the Inspector for display according to the specified format.
```cpp
uint32_t myVariable = myFunction(...);
inspect32BitValue(0, myVariable);
```
The inspection data for a pixel will only be written if a fragment actually covers that pixel. To avoid ghosting
where no fragments are rendered it is useful to clear the inspection data before rendering:
```cpp
g_inspectorElement->clearFragmentVariables(cmd, 0);
vkCmdBeginRendering(...);
```
The inspection is triggered on the host side right after rendering:
```cpp
vkCmdEndRendering(cmd);
g_inspectorElement->inspectFragmentVariables(cmd, 0);
```
------------------------------------------------------------------------------------------------
### CUSTOM SHADER VARIABLE INSPECTION
------------------------------------------------------------------------------------------------
In case some in-shader data has to be inspected in other shader types, or not on a once-per-thread basis, the custom
inspection mode can be used. This mode allows the user to specify the overall size of the generated data as well as
an effective inspection window. This mode may be used in conjunction with the COMPUTE and FRAGMENT modes.
std::vector<ElementInspector::ValueFormat> customCaptureFormat;
```cpp
...
ElementInspector::CustomInspectionInfo info{};
info.extent = totalInspectionSize;
info.format = customCaptureFormat;
info.minCoord = inspectionWindowMin;
info.maxCoord = inspectionWindowMax;
info.name = "My Custom Capture";
g_inspectorElement->initCustomInspection(0, info);
```
To allow variable inspection two buffers need to be made available to the target pipeline:
```cpp
std::vector<VkWriteDescriptorSet> writes;
const VkDescriptorBufferInfo inspectorInspection{
g_inspectorElement->getCustomInspectionBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 1, &inspectorInspection));
const VkDescriptorBufferInfo inspectorMetadata{
g_inspectorElement->getCustomMetadataBuffer(0),
0,
VK_WHOLE_SIZE};
writes.emplace_back(m_dset->makeWrite(0, 2, &inspectorMetadata));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
```
The shader code needs to indicate include the Inspector header along with preprocessor variables to set the
inspection mode to Fragment, and indicate the binding points for the buffers:
```cpp
#define INSPECTOR_MODE_CUSTOM
#define INSPECTOR_DESCRIPTOR_SET 0
#define INSPECTOR_CUSTOM_INSPECTION_DATA_BINDING 1
#define INSPECTOR_CUSTOM_METADATA_BINDING 2
#include "dh_inspector.h"
```
The inspection of a variable is then done as follows. For alignment purposes the inspection is done with a 32-bit
granularity. The shader is responsible for packing the inspected variables in 32-bit uint words. Those will be
unpacked within the Inspector for display according to the specified format.
```cpp
uint32_t myVariable = myFunction(...);
inspectCustom32BitValue(0, myCoordinates, myVariable);
```
The inspection is triggered on the host side right after running the pipeline:
```cpp
g_inspectorElement->inspectCustomVariables(cmd, 0);
```
@DOC_END */
namespace nvvkhl {
class ElementInspectorInternal;
class ElementInspector : public nvvkhl::IAppElement
{
public:
ElementInspector();
~ElementInspector() override;
enum ValueType
{
eUint8,
eUint16,
eUint32,
eUint64,
eInt8,
eInt16,
eInt32,
eInt64,
eFloat16,
eFloat32,
eU8Vec2,
eU8Vec3,
eU8Vec4,
eU16Vec2,
eU16Vec3,
eU16Vec4,
eU32Vec2,
eU32Vec3,
eU32Vec4,
eU64Vec2,
eU64Vec3,
eU64Vec4,
eS8Vec2,
eS8Vec3,
eS8Vec4,
eS16Vec2,
eS16Vec3,
eS16Vec4,
eS32Vec2,
eS32Vec3,
eS32Vec4,
eS64Vec2,
eS64Vec3,
eS64Vec4,
eF16Vec2,
eF16Vec3,
eF16Vec4,
eF32Vec2,
eF32Vec3,
eF32Vec4,
eU8Mat2x2,
eU8Mat2x3,
eU8Mat2x4,
eU16Mat2x2,
eU16Mat2x3,
eU16Mat2x4,
eU32Mat2x2,
eU32Mat2x3,
eU32Mat2x4,
eU64Mat2x2,
eU64Mat2x3,
eU64Mat2x4,
eU8Mat3x2,
eU8Mat3x3,
eU8Mat3x4,
eU16Mat3x2,
eU16Mat3x3,
eU16Mat3x4,
eU32Mat3x2,
eU32Mat3x3,
eU32Mat3x4,
eU64Mat3x2,
eU64Mat3x3,
eU64Mat3x4,
eU8Mat4x2,
eU8Mat4x3,
eU8Mat4x4,
eU16Mat4x2,
eU16Mat4x3,
eU16Mat4x4,
eU32Mat4x2,
eU32Mat4x3,
eU32Mat4x4,
eU64Mat4x2,
eU64Mat4x3,
eU64Mat4x4,
eS8Mat2x2,
eS8Mat2x3,
eS8Mat2x4,
eS16Mat2x2,
eS16Mat2x3,
eS16Mat2x4,
eS32Mat2x2,
eS32Mat2x3,
eS32Mat2x4,
eS64Mat2x2,
eS64Mat2x3,
eS64Mat2x4,
eS8Mat3x2,
eS8Mat3x3,
eS8Mat3x4,
eS16Mat3x2,
eS16Mat3x3,
eS16Mat3x4,
eS32Mat3x2,
eS32Mat3x3,
eS32Mat3x4,
eS64Mat3x2,
eS64Mat3x3,
eS64Mat3x4,
eS8Mat4x2,
eS8Mat4x3,
eS8Mat4x4,
eS16Mat4x2,
eS16Mat4x3,
eS16Mat4x4,
eS32Mat4x2,
eS32Mat4x3,
eS32Mat4x4,
eS64Mat4x2,
eS64Mat4x3,
eS64Mat4x4,
eF16Mat2x2,
eF16Mat2x3,
eF16Mat2x4,
eF32Mat2x2,
eF32Mat2x3,
eF32Mat2x4,
eF16Mat3x2,
eF16Mat3x3,
eF16Mat3x4,
eF32Mat3x2,
eF32Mat3x3,
eF32Mat3x4,
eF16Mat4x2,
eF16Mat4x3,
eF16Mat4x4,
eF32Mat4x2,
eF32Mat4x3,
eF32Mat4x4,
};
enum ValueFormatFlagBits
{
eVisible = 0,
eHidden = 1,
eValueFormatFlagCount
};
typedef uint32_t ValueFormatFlag;
struct ValueFormat
{
ValueType type{eUint32};
std::string name;
bool hexDisplay{false};
// One of ValueFormatFlagBits
ValueFormatFlag flags{eVisible};
};
void onAttach(nvvkhl::Application* app) override;
void onDetach() override;
void onUIRender() override;
// Called if showMenu is true
void onUIMenu() override;
struct InitInfo
{
VkDevice device{VK_NULL_HANDLE};
uint32_t graphicsQueueFamilyIndex{~0u};
nvvk::ResourceAllocator* allocator{nullptr};
uint32_t imageCount{0u};
uint32_t bufferCount{0u};
uint32_t computeCount{0u};
uint32_t fragmentCount{0u};
uint32_t customCount{0u};
};
void init(const InitInfo& info);
void deinit();
struct ImageInspectionInfo
{
std::string name;
std::string comment;
VkImageCreateInfo createInfo{};
VkImage sourceImage{VK_NULL_HANDLE};
std::vector<ValueFormat> format;
};
void initImageInspection(uint32_t index, const ImageInspectionInfo& info);
void deinitImageInspection(uint32_t index);
bool updateImageFormat(uint32_t index, const std::vector<nvvkhl::ElementInspector::ValueFormat>& newFormat);
void inspectImage(VkCommandBuffer cmd, uint32_t index, VkImageLayout currentLayout);
struct BufferInspectionInfo
{
std::string name;
std::string comment;
VkBuffer sourceBuffer{VK_NULL_HANDLE};
std::vector<ValueFormat> format;
uint32_t entryCount{0u};
uint32_t minEntry{0u};
uint32_t viewMin{0};
uint32_t viewMax{~0u};
};
void initBufferInspection(uint32_t index, const BufferInspectionInfo& info);
void deinitBufferInspection(uint32_t index);
void inspectBuffer(VkCommandBuffer cmd, uint32_t index);
bool updateBufferFormat(uint32_t index, const std::vector<nvvkhl::ElementInspector::ValueFormat>& newFormat);
static void appendStructToFormat(std::vector<ValueFormat>& format,
const std::vector<ValueFormat>& addedStruct,
const std::string addedStructName,
bool forceHidden = false);
struct ComputeInspectionInfo
{
std::string name;
std::string comment;
std::vector<ValueFormat> format;
glm::uvec3 gridSizeInBlocks{0, 0, 0};
glm::uvec3 blockSize{0, 0, 0};
glm::uvec3 minBlock{0, 0, 0};
glm::uvec3 maxBlock{~0u, ~0u, ~0u};
uint32_t minWarp{0u};
uint32_t maxWarp{~0u};
int32_t uiBlocksPerRow{1};
};
void initComputeInspection(uint32_t index, const ComputeInspectionInfo& info);
void deinitComputeInspection(uint32_t index);
void inspectComputeVariables(VkCommandBuffer cmd, uint32_t index);
bool updateComputeFormat(uint32_t index, const std::vector<nvvkhl::ElementInspector::ValueFormat>& newFormat);
VkBuffer getComputeInspectionBuffer(uint32_t index);
VkBuffer getComputeMetadataBuffer(uint32_t index);
struct CustomInspectionInfo
{
std::string name;
std::string comment;
std::vector<ValueFormat> format;
glm::uvec3 extent{0, 0, 0};
glm::uvec3 minCoord{0u, 0u, 0u};
glm::uvec3 maxCoord{~0u, ~0u, ~0u};
};
void initCustomInspection(uint32_t index, const CustomInspectionInfo& info);
void deinitCustomInspection(uint32_t index);
void inspectCustomVariables(VkCommandBuffer cmd, uint32_t index);
bool updateCustomFormat(uint32_t index, const std::vector<ValueFormat>& newFormat);
VkBuffer getCustomInspectionBuffer(uint32_t index);
VkBuffer getCustomMetadataBuffer(uint32_t index);
struct FragmentInspectionInfo
{
std::string name;
std::string comment;
std::vector<ValueFormat> format;
glm::uvec2 renderSize{0, 0};
glm::uvec2 minFragment{0, 0};
glm::uvec2 maxFragment{~0u, ~0u};
};
void initFragmentInspection(uint32_t index, const FragmentInspectionInfo& info);
void deinitFragmentInspection(uint32_t index);
void updateMinMaxFragmentInspection(VkCommandBuffer cmd, uint32_t index, const glm::uvec2& minFragment, const glm::uvec2& maxFragment);
void clearFragmentVariables(VkCommandBuffer cmd, uint32_t index);
void inspectFragmentVariables(VkCommandBuffer cmd, uint32_t index);
bool updateFragmentFormat(uint32_t index, const std::vector<ValueFormat>& newFormat);
VkBuffer getFragmentInspectionBuffer(uint32_t index);
VkBuffer getFragmentMetadataBuffer(uint32_t index);
static std::vector<ValueFormat> formatRGBA8(const std::string& name = "");
static std::vector<ValueFormat> formatRGBA32(const std::string& name = "");
static std::vector<ValueFormat> formatVector4(ValueType type, const std::string& name);
static std::vector<ValueFormat> formatVector3(ValueType type, const std::string& name);
static std::vector<ValueFormat> formatVector2(ValueType type, const std::string& name);
static std::vector<ValueFormat> formatValue(ValueType type, const std::string& name);
static std::vector<ValueFormat> formatInt32(const std::string& name = "value");
static std::vector<ValueFormat> formatUint32(const std::string& name = "value");
static std::vector<ValueFormat> formatFloat32(const std::string& name = "value");
private:
ElementInspectorInternal* m_internals{nullptr};
};
} // namespace nvvkhl

View file

@ -0,0 +1,290 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "imgui.h"
#include "imgui_internal.h"
namespace nvvkhl {
/** @DOC_START
# class nvvkhl::ElementLogger
> This class is an element of the application that can redirect all logs to a ImGui window in the application
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
Create the element such that it will be available to the target application
Example:
```cpp
static nvvkhl::SampleAppLog g_logger;
nvprintSetCallback([](int level, const char* fmt)
{
g_logger.addLog("%s", fmt);
});
app->addElement(std::make_unique<nvvkhl::ElementLogger>(&g_logger, true)); // Add logger window
```
@DOC_END */
// Helper structure to hold the log
struct SampleAppLog
{
public:
SampleAppLog() { clear(); }
void setLogLevel(uint32_t level) { m_levelFilter = level; }
uint32_t getLogLevel() { return m_levelFilter; }
void clear()
{
m_buf.clear();
m_lineOffsets.clear();
m_lineOffsets.push_back(0);
}
void addLog(uint32_t level, const char* fmt, ...)
{
if((m_levelFilter & (1 << level)) == 0)
return;
int old_size = m_buf.size();
va_list args = {};
va_start(args, fmt);
m_buf.appendfv(fmt, args);
va_end(args);
for(int new_size = m_buf.size(); old_size < new_size; old_size++)
{
if(m_buf[old_size] == '\n')
{
m_lineOffsets.push_back(old_size + 1);
}
}
}
void draw(const char* title, bool* p_open = nullptr)
{
if(!ImGui::Begin(title, p_open))
{
ImGui::End();
return;
}
// Options menu
if(ImGui::BeginPopup("Options"))
{
ImGui::Checkbox("Auto-scroll", &m_autoScroll);
ImGui::EndPopup();
}
// Main window
if(ImGui::Button("Options"))
{
ImGui::OpenPopup("Options");
}
ImGui::SameLine();
bool do_clear = ImGui::Button("Clear");
ImGui::SameLine();
bool copy = ImGui::Button("Copy");
ImGui::SameLine();
ImGui::CheckboxFlags("All", &m_levelFilter, LOGBITS_ALL);
ImGui::SameLine();
ImGui::CheckboxFlags("Stats", &m_levelFilter, LOGBIT_STATS);
ImGui::SameLine();
ImGui::CheckboxFlags("Debug", &m_levelFilter, LOGBIT_DEBUG);
ImGui::SameLine();
ImGui::CheckboxFlags("Info", &m_levelFilter, LOGBIT_INFO);
ImGui::SameLine();
ImGui::CheckboxFlags("Warnings", &m_levelFilter, LOGBIT_WARNING);
ImGui::SameLine();
ImGui::CheckboxFlags("Errors", &m_levelFilter, LOGBIT_ERROR);
ImGui::SameLine();
ImGui::Text("Filter");
ImGui::SameLine();
m_filter.Draw("##Filter", -100.0F);
ImGui::SameLine();
bool clear_filter = ImGui::SmallButton("X");
ImGui::Separator();
ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
if(do_clear)
{
clear();
}
if(copy)
{
ImGui::SetClipboardText(m_buf.c_str());
}
if(clear_filter)
{
m_filter.Clear();
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
const char* buf = m_buf.begin();
const char* buf_end = m_buf.end();
if(m_filter.IsActive())
{
// In this example we don't use the clipper when Filter is enabled.
// This is because we don't have a random access on the result on our filter.
// A real application processing logs with ten of thousands of entries may want to store the result of
// search/filter.. especially if the filtering function is not trivial (e.g. reg-exp).
for(int line_no = 0; line_no < m_lineOffsets.Size; line_no++)
{
const char* line_start = buf + m_lineOffsets[line_no];
const char* line_end = (line_no + 1 < m_lineOffsets.Size) ? (buf + m_lineOffsets[line_no + 1] - 1) : buf_end;
if(m_filter.PassFilter(line_start, line_end))
{
ImGui::TextUnformatted(line_start, line_end);
}
}
}
else
{
// The simplest and easy way to display the entire buffer:
// ImGui::TextUnformatted(buf_begin, buf_end);
// And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward
// to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are
// within the visible area.
// If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them
// on your side is recommended. Using ImGuiListClipper requires
// - A) random access into your data
// - B) items all being the same height,
// both of which we can handle since we an array pointing to the beginning of each line of text.
// When using the filter (in the block of code above) we don't have random access into the data to display
// anymore, which is why we don't use the clipper. Storing or skimming through the search result would make
// it possible (and would be recommended if you want to search through tens of thousands of entries).
ImGuiListClipper clipper;
clipper.Begin(m_lineOffsets.Size);
while(clipper.Step())
{
for(int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
{
const char* line_start = buf + m_lineOffsets[line_no];
const char* line_end = (line_no + 1 < m_lineOffsets.Size) ? (buf + m_lineOffsets[line_no + 1] - 1) : buf_end;
ImGui::TextUnformatted(line_start, line_end);
}
}
clipper.End();
}
ImGui::PopStyleVar();
if(m_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
{
ImGui::SetScrollHereY(1.0F);
}
ImGui::EndChild();
ImGui::End();
}
private:
ImGuiTextBuffer m_buf{};
ImGuiTextFilter m_filter{};
ImVector<int> m_lineOffsets; // Index to lines offset. We maintain this with AddLog() calls.
bool m_autoScroll{true}; // Keep scrolling if already at the bottom.
uint32_t m_levelFilter = LOGBITS_WARNINGS;
};
//extern SampleAppLog g_logger;
struct ElementLogger : public nvvkhl::IAppElement
{
explicit ElementLogger(SampleAppLog* logger, bool show = false)
: m_showLog(show)
, m_logger(logger)
{
addSettingsHandler();
}
virtual ~ElementLogger() = default;
void onUIRender() override
{
if(ImGui::IsKeyDown(ImGuiKey_ModCtrl) && ImGui::IsKeyDown(ImGuiKey_ModShift) && !ImGui::IsKeyDown(ImGuiKey_ModAlt))
{
if(ImGui::IsKeyPressed(ImGuiKey_L))
{
m_showLog = !m_showLog;
}
}
if(!m_showLog)
{
return;
}
ImGui::SetNextWindowCollapsed(false, ImGuiCond_Appearing);
ImGui::SetNextWindowSize({400, 200}, ImGuiCond_Appearing);
ImGui::SetNextWindowBgAlpha(0.7F);
m_logger->draw("Log", &m_showLog);
} // Called for anything related to UI
void onUIMenu() override
{
if(ImGui::BeginMenu("View"))
{
ImGui::MenuItem("Log Window", "Ctrl+Shift+L", &m_showLog);
ImGui::EndMenu();
}
} // This is the menubar to create
// This goes in the .ini file and remember the state of the window [open/close]
void addSettingsHandler()
{
// Persisting the window
ImGuiSettingsHandler ini_handler{};
ini_handler.TypeName = "LoggerEngine";
ini_handler.TypeHash = ImHashStr("LoggerEngine");
ini_handler.ClearAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler*) {};
ini_handler.ApplyAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler*) {};
ini_handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) -> void* { return (void*)1; };
ini_handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, void* entry, const char* line) {
ElementLogger* s = (ElementLogger*)handler->UserData;
int x;
if(sscanf(line, "ShowLoader=%d", &x) == 1)
{
s->m_showLog = (x == 1);
}
else if(sscanf(line, "Level=%d", &x) == 1)
{
s->m_logger->setLogLevel(x);
}
};
ini_handler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) {
ElementLogger* s = (ElementLogger*)handler->UserData;
buf->appendf("[%s][State]\n", handler->TypeName);
buf->appendf("ShowLoader=%d\n", s->m_showLog ? 1 : 0);
buf->appendf("Level=%d\n", s->m_logger->getLogLevel());
buf->appendf("\n");
};
ini_handler.UserData = this;
ImGui::AddSettingsHandler(&ini_handler);
}
private:
bool m_showLog{false};
SampleAppLog* m_logger{nullptr};
};
} // namespace nvvkhl

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,520 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/** @DOC_START
# class nvvkhl::ElementProfiler
> This class is an element of the application that is responsible for the profiling of the application. It is using the `nvvk::ProfilerVK` to profile the time parts of the computation done on the GPU.
To use this class, you need to add it to the `nvvkhl::Application` using the `addElement` method.
The profiler element, is there to help profiling the time parts of the
computation is done on the GPU. To use it, follow those simple steps
In the main() program, create an instance of the profiler and add it to the
nvvkhl::Application
```cpp
std::shared_ptr<nvvkhl::ElementProfiler> profiler = std::make_shared<nvvkhl::ElementProfiler>();
app->addElement(profiler);
```
In the application where profiling needs to be done, add profiling sections
```cpp
void mySample::onRender(VkCommandBuffer cmd)
{
auto sec = m_profiler->timeRecurring(__FUNCTION__, cmd);
...
// Subsection
{
auto sec = m_profiler->timeRecurring("Dispatch", cmd);
vkCmdDispatch(cmd, (size.width + (GROUP_SIZE - 1)) / GROUP_SIZE, (size.height + (GROUP_SIZE - 1)) / GROUP_SIZE, 1);
}
...
```
This is it and the execution time on the GPU for each part will be showing in the Profiler window.
@DOC_END */
#include <implot.h>
#include <imgui_internal.h>
#include "application.hpp"
#include "nvh/commandlineparser.hpp"
#include "nvh/nvprint.hpp"
#include "nvh/timesampler.hpp"
#include "nvpsystem.hpp"
#include "nvvk/error_vk.hpp"
#include "nvvk/profiler_vk.hpp"
#define PROFILER_GRAPH_TEMPORAL_SMOOTHING 20.f
#define PROFILER_GRAPH_MINIMAL_LUMINANCE 0.1f
namespace nvvkhl {
class ElementProfiler : public nvvkhl::IAppElement, public nvvk::ProfilerVK
{
public:
ElementProfiler(bool showWindow = true)
: m_showWindow(showWindow)
{
addSettingsHandler();
};
~ElementProfiler() = default;
void onAttach(Application* app) override
{
m_app = app;
nvvk::ProfilerVK::init(m_app->getDevice(), m_app->getPhysicalDevice());
nvvk::ProfilerVK::setLabelUsage(m_app->getContext()->hasInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME));
nvvk::ProfilerVK::beginFrame();
}
void onDetach() override
{
nvvk::ProfilerVK::endFrame();
vkDeviceWaitIdle(m_app->getDevice());
nvvk::ProfilerVK::deinit();
}
void onUIMenu() override
{
if(ImGui::BeginMenu("View"))
{
ImGui::MenuItem("Profiler", "", &m_showWindow);
ImGui::EndMenu();
}
} // This is the menubar to create
void onUIRender() override
{
constexpr float frequency = (1.0f / 60.0f);
static float s_minElapsed = 0;
s_minElapsed += ImGui::GetIO().DeltaTime;
if(!m_showWindow)
return;
// Opening the window
if(!ImGui::Begin("Profiler", &m_showWindow))
{
ImGui::End();
return;
}
if(s_minElapsed >= frequency)
{
s_minElapsed = 0;
m_node.child.clear();
m_node.name = "Frame";
m_node.cpuTime = static_cast<float>(m_data->cpuTime.getAveraged() / 1000.);
m_single.child.clear();
m_single.name = "Single";
addEntries(m_node.child, 0, m_data->numLastSections, 0);
}
bool copyToClipboard = ImGui::SmallButton("Copy");
if(copyToClipboard)
ImGui::LogToClipboard();
if(ImGui::BeginTabBar("Profiler Tabs"))
{
if(ImGui::BeginTabItem("Table"))
{
renderTable();
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("PieChart"))
{
renderPieChart();
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("LineChart"))
{
renderLineChart();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
if(copyToClipboard)
ImGui::LogFinish();
ImGui::End();
}
void onRender(VkCommandBuffer /*cmd*/) override
{
nvvk::ProfilerVK::endFrame();
nvvk::ProfilerVK::beginFrame();
}
private:
struct MyEntryNode
{
std::string name;
float cpuTime = 0.f;
float gpuTime = -1.f;
std::vector<MyEntryNode> child;
Entry* entry = nullptr;
};
uint32_t addEntries(std::vector<MyEntryNode>& nodes, uint32_t startIndex, uint32_t endIndex, uint32_t currentLevel)
{
for(uint32_t curIndex = startIndex; curIndex < endIndex; curIndex++)
{
Entry& entry = m_data->entries[curIndex];
if(entry.level < currentLevel)
return curIndex;
MyEntryNode entryNode;
entryNode.name = entry.name.empty() ? "N/A" : entry.name;
entryNode.gpuTime = static_cast<float>(entry.gpuTime.getAveraged() / 1000.);
entryNode.cpuTime = static_cast<float>(entry.cpuTime.getAveraged() / 1000.);
entryNode.entry = &entry;
if(entry.level == LEVEL_SINGLESHOT)
{
m_single.child.push_back(entryNode);
continue;
}
uint32_t nextLevel = curIndex + 1 < endIndex ? m_data->entries[curIndex + 1].level : currentLevel;
if(nextLevel > currentLevel)
{
curIndex = addEntries(entryNode.child, curIndex + 1, endIndex, nextLevel);
}
nodes.push_back(entryNode);
if(nextLevel < currentLevel)
return curIndex;
}
return endIndex;
}
void displayTableNode(const MyEntryNode& node)
{
ImGuiTableFlags flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanAllColumns;
ImGui::TableNextRow();
ImGui::TableNextColumn();
const bool is_folder = (node.child.empty() == false);
flags = is_folder ? flags : flags | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen;
bool open = ImGui::TreeNodeEx(node.name.c_str(), flags);
ImGui::TableNextColumn();
if(node.gpuTime <= 0)
ImGui::TextDisabled("--");
else
ImGui::Text("%3.3f", node.gpuTime);
ImGui::TableNextColumn();
if(node.cpuTime <= 0)
ImGui::TextDisabled("--");
else
ImGui::Text("%3.3f", node.cpuTime);
if(open && is_folder)
{
for(int child_n = 0; child_n < static_cast<int>(node.child.size()); child_n++)
displayTableNode(node.child[child_n]);
ImGui::TreePop();
}
}
void renderTable()
{
// Using those as a base value to create width/height that are factor of the size of our font
const float textBaseWidth = ImGui::CalcTextSize("A").x;
static ImGuiTableFlags s_flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable
| ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
bool copy = false;
if(ImGui::Button("Copy"))
{
ImGui::LogToClipboard();
copy = true;
}
if(ImGui::BeginTable("EntryTable", 3, s_flags))
{
// The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
ImGui::TableSetupColumn("GPU", ImGuiTableColumnFlags_WidthFixed, textBaseWidth * 4.0f);
ImGui::TableSetupColumn("CPU", ImGuiTableColumnFlags_WidthFixed, textBaseWidth * 4.0f);
ImGui::TableHeadersRow();
displayTableNode(m_node);
// Display only if an element
if(!m_single.child.empty())
{
displayTableNode(m_single);
}
ImGui::EndTable();
}
if(copy)
{
ImGui::LogFinish();
}
}
//-------------------------------------------------------------------------------------------------
// Rendering the data as a PieChart, showing the percentage of utilization
//
void renderPieChart()
{
static bool s_showSubLevel = false;
ImGui::Checkbox("Show SubLevel 1", &s_showSubLevel);
if(ImPlot::BeginPlot("##Pie1", ImVec2(-1, -1), ImPlotFlags_NoMouseText))
{
ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_Lock,
ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_Lock);
ImPlot::SetupAxesLimits(0, 1, 0, 1, ImPlotCond_Always);
// Get all Level 0
std::vector<const char*> labels1(m_node.child.size());
std::vector<float> data1(m_node.child.size());
double angle0 = 90;
for(size_t i = 0; i < m_node.child.size(); i++)
{
labels1[i] = m_node.child[i].name.c_str();
data1[i] = m_node.child[i].gpuTime / m_node.cpuTime;
}
ImPlot::PlotPieChart(labels1.data(), data1.data(), static_cast<int>(data1.size()), 0.5, 0.5, 0.4, "%.2f", angle0);
// Level 1
if(s_showSubLevel)
{
double a0 = angle0;
for(size_t i = 0; i < m_node.child.size(); i++)
{
auto& currentNode = m_node.child[i];
if(!currentNode.child.empty())
{
labels1.resize(currentNode.child.size());
data1.resize(currentNode.child.size());
for(size_t j = 0; j < currentNode.child.size(); j++)
{
labels1[j] = currentNode.child[j].name.c_str();
data1[j] = currentNode.child[j].gpuTime / m_node.cpuTime;
}
ImPlot::PlotPieChart(labels1.data(), data1.data(), static_cast<int>(data1.size()), 0.5, 0.5, 0.1, "", a0,
ImPlotPieChartFlags_None);
}
// Increment the position of the next sub-element
double percent = currentNode.gpuTime / m_node.cpuTime;
a0 += a0 + 360 * percent;
}
}
ImPlot::EndPlot();
}
}
static uint32_t wangHash(uint32_t seed)
{
seed = (seed ^ 61) ^ (seed >> 16);
seed *= 9;
seed = seed ^ (seed >> 4);
seed *= 0x27d4eb2d;
seed = seed ^ (seed >> 15);
return seed;
}
static ImColor uintToColor(uint32_t v)
{
uint32_t hashed = wangHash(v);
float r = (hashed & 0xFF) / 255.f;
hashed = hashed >> 8;
float g = (hashed & 0xFF) / 255.f;
hashed = hashed >> 8;
float b = (hashed & 0xFF) / 255.f;
// Boost luminance of darker colors for visibility
float luminance = (0.2126f * r + 0.7152f * g + 0.0722f * b);
float boost = std::max(1.f, PROFILER_GRAPH_MINIMAL_LUMINANCE / luminance);
return ImColor(r * boost, g * boost, b * boost, 1.f);
}
//-------------------------------------------------------------------------------------------------
// Rendering the data as a cumulated line chart
//
void renderLineChart()
{
std::vector<const char*> gpuTimesLabels(m_node.child.size());
std::vector<std::vector<float>> gpuTimes(m_node.child.size());
std::vector<float> cpuTimes(m_data->cpuTime.numValid);
static float maxY = 0.f;
float avgCpuTime = 0.f;
for(size_t i = 0; i < m_node.child.size(); i++)
{
gpuTimesLabels[i] = m_node.child[i].name.c_str();
if(m_node.child[i].entry)
{
gpuTimes[i].resize(m_node.child[i].entry->gpuTime.numValid);
for(size_t j = 0; j < m_node.child[i].entry->gpuTime.numValid; j++)
{
uint32_t index = (m_node.child[i].entry->gpuTime.index - m_node.child[i].entry->gpuTime.numValid + j) % m_data->numAveraging;
gpuTimes[i][j] = float(m_node.child[i].entry->gpuTime.times[index] / 1000.0);
if(i > 0)
{
gpuTimes[i][j] += gpuTimes[i - 1][j];
}
}
}
}
for(size_t j = 0; j < m_data->cpuTime.numValid; j++)
{
uint32_t index = (m_data->cpuTime.index - m_data->cpuTime.numValid + j) % m_data->numAveraging;
cpuTimes[j] = float(m_data->cpuTime.times[index] / 1000.0);
avgCpuTime += cpuTimes[j];
}
if(m_data->cpuTime.numValid > 0)
{
avgCpuTime /= m_data->cpuTime.numValid;
}
if(maxY == 0.f)
{
maxY = avgCpuTime;
}
else
{
maxY = (PROFILER_GRAPH_TEMPORAL_SMOOTHING * maxY + avgCpuTime) / (PROFILER_GRAPH_TEMPORAL_SMOOTHING + 1.f);
}
if(gpuTimes.size() > 0 && gpuTimes[0].size() > 0)
{
const ImPlotFlags plotFlags = ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText | ImPlotFlags_Crosshairs;
const ImPlotAxisFlags axesFlags = ImPlotAxisFlags_Lock | ImPlotAxisFlags_NoLabel;
if(ImPlot::BeginPlot("##Line1", ImVec2(-1, -1), plotFlags))
{
ImPlot::SetupLegend(ImPlotLocation_NorthWest, ImPlotLegendFlags_NoButtons);
ImPlot::SetupAxes(nullptr, "Count", axesFlags | ImPlotAxisFlags_NoTickLabels, axesFlags);
ImPlot::SetupAxesLimits(0, m_node.child[0].entry->gpuTime.numValid, 0, maxY * 1.2f, ImPlotCond_Always);
ImPlot::SetAxes(ImAxis_X1, ImAxis_Y1);
ImPlot::SetNextLineStyle(ImColor(0.03f, 0.45f, 0.02f, 1.0f), 0.1f);
ImPlot::PlotLine("CPU", cpuTimes.data(), (int)cpuTimes.size());
ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 1.f);
ImPlot::SetAxes(ImAxis_X1, ImAxis_Y1);
for(size_t i = 0; i < m_node.child.size(); i++)
{
size_t index = m_node.child.size() - i - 1;
uint32_t h = 0;
for(size_t j = 0; j < m_node.child[index].name.size(); j++)
{
h = wangHash(h + m_node.child[index].name[j]);
}
ImPlot::SetNextFillStyle(uintToColor(h));
ImPlot::PlotShaded(m_node.child[index].name.c_str(), gpuTimes[index].data(), (int)gpuTimes[index].size(),
-INFINITY, 1.0, 0.0, 0, 0);
}
ImPlot::PopStyleVar();
if(ImPlot::IsPlotHovered())
{
ImPlotPoint mouse = ImPlot::GetPlotMousePos();
int mouseOffset = (int(mouse.x)) % (int)gpuTimes[0].size();
std::vector<float> localTimes(m_node.child.size());
ImGui::BeginTooltip();
ImGui::Text("CPU: %.3f ms", cpuTimes[mouseOffset]);
float totalGpu = 0.f;
for(size_t i = 0; i < m_node.child.size(); i++)
{
if(i == 0)
{
localTimes[i] = gpuTimes[i][mouseOffset];
}
else
{
localTimes[i] = gpuTimes[i][mouseOffset] - gpuTimes[i - 1][mouseOffset];
}
totalGpu += localTimes[i];
}
ImGui::Text("GPU: %.3f ms", totalGpu);
for(size_t i = 0; i < m_node.child.size(); i++)
{
ImGui::Text(" %s: %.3f ms (%.1f%%)", m_node.child[i].name.c_str(), localTimes[i], localTimes[i] * 100.f / totalGpu);
}
ImGui::EndTooltip();
}
ImPlot::EndPlot();
}
}
}
// This goes in the .ini file and remember the state of the window [open/close]
void addSettingsHandler()
{
// Persisting the window
ImGuiSettingsHandler iniHandler{};
iniHandler.TypeName = "ElementProfiler";
iniHandler.TypeHash = ImHashStr("ElementProfiler");
iniHandler.ClearAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler*) {};
iniHandler.ApplyAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler*) {};
iniHandler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) -> void* { return (void*)1; };
iniHandler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, void* entry, const char* line) {
ElementProfiler* s = (ElementProfiler*)handler->UserData;
int x;
if(sscanf(line, "ShowWindow=%d", &x) == 1)
{
s->m_showWindow = (x == 1);
}
};
iniHandler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) {
ElementProfiler* s = (ElementProfiler*)handler->UserData;
buf->appendf("[%s][State]\n", handler->TypeName);
buf->appendf("ShowWindow=%d\n", s->m_showWindow ? 1 : 0);
buf->appendf("\n");
};
iniHandler.UserData = this;
ImGui::AddSettingsHandler(&iniHandler);
}
//---
Application* m_app{nullptr};
MyEntryNode m_node;
MyEntryNode m_single;
bool m_showWindow = true;
};
} // namespace nvvkhl

View file

@ -0,0 +1,140 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "application.hpp"
#include "nvh/commandlineparser.hpp"
#include "nvh/nvprint.hpp"
#include "nvp/nvpsystem.hpp"
#include "nvh/timesampler.hpp"
#include "nvvk/error_vk.hpp"
namespace nvvkhl {
//--------------------------------------------------------------------------------------------------
// This testing element allow to
// - Capture Vulkan validation errors, if any return error code 1
// - Dump the result image to disk (CurrentPath/"name of project".bmp)
//
// At startup, it looks for arguments
// --test (bool) Enable testing
// --snapshot (bool) Saving or not image
// --frames: (int) number of iteration frame the application do before sending a close signal
class ElementTesting : public nvvkhl::IAppElement
{
struct Settings
{
bool enabled{false};
bool snapshot{false};
uint32_t maxFrames{5};
} m_settings;
public:
ElementTesting(int argc, char** argv)
{
nvh::CommandLineParser cmd_parser("");
cmd_parser.addArgument({"--test"}, &m_settings.enabled, "Enable testing");
cmd_parser.addArgument({"--snapshot"}, &m_settings.snapshot, "Take and save a snapshot");
cmd_parser.addArgument({"--frames"}, &m_settings.maxFrames, "Max number of frames");
cmd_parser.parse(argc, argv);
};
~ElementTesting() = default;
void onAttach(Application* app) override
{
m_app = app;
m_startTime.reset();
if(m_settings.enabled)
{
VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
dbg_messenger_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT // Vulkan issues
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; // Invalid usage
dbg_messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT // Other
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; // Violation of spec
dbg_messenger_create_info.pUserData = this;
// Trapping in the callback the validation errors that could show up. If errors are found errorCode will return 1, otherwise 0
dbg_messenger_create_info.pfnUserCallback = [](VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT /*messageType*/,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData) {
ElementTesting* testing = reinterpret_cast<ElementTesting*>(userData);
if(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
{
testing->addError(callbackData->pMessage);
}
return VK_FALSE;
};
NVVK_CHECK(vkCreateDebugUtilsMessengerEXT(m_app->getContext()->m_instance, &dbg_messenger_create_info, nullptr, &m_dbgMessenger));
}
}
void onDetach() override
{
if(m_settings.enabled)
{
vkDestroyDebugUtilsMessengerEXT(m_app->getContext()->m_instance, m_dbgMessenger, nullptr);
// Signal errors
if(!m_errorMessages.empty())
{
LOGE("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
for(auto& e : m_errorMessages)
LOGE("%s\n", e.c_str());
LOGE("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
}
}
}
void onRender(VkCommandBuffer /*cmd*/) override
{
if(!m_settings.enabled)
return;
m_counter++;
if(m_counter >= m_settings.maxFrames)
{
if(m_settings.snapshot)
{
std::string name = std::string("snap_") + std::string(PROJECT_NAME) + std::string(".png");
NVPSystem::windowScreenshot(m_app->getWindowHandle(), name.c_str());
LOGI("Saving image: %s \n", name.c_str());
}
LOGI("Testing Time: %.3f ms\n", m_startTime.elapsed());
m_app->close(); // request to stop
}
}
void addError(const char* msg) { m_errorMessages.emplace_back(msg); }
int errorCode() { return m_errorMessages.empty() ? 0 : 1; }
bool enabled() { return m_settings.enabled; }
private:
Application* m_app{nullptr};
uint32_t m_counter{0};
VkDebugUtilsMessengerEXT m_dbgMessenger = nullptr;
std::vector<std::string> m_errorMessages;
nvh::Stopwatch m_startTime;
};
} // namespace nvvkhl

View file

@ -0,0 +1,241 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2023 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "gbuffer.hpp"
#include <utility>
#include "application.hpp"
#include "third_party/imgui/backends/imgui_impl_vulkan.h"
#include "nvvk/images_vk.hpp"
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/commands_vk.hpp"
nvvkhl::GBuffer::GBuffer(VkDevice device, nvvk::ResourceAllocator* alloc)
: m_device(device)
, m_alloc(alloc)
{
}
nvvkhl::GBuffer::GBuffer(VkDevice device, nvvk::ResourceAllocator* alloc, const VkExtent2D& size, VkFormat color, VkFormat depth)
: m_device(device)
, m_alloc(alloc)
{
create(size, {color}, depth);
}
nvvkhl::GBuffer::GBuffer(VkDevice device, nvvk::ResourceAllocator* alloc, const VkExtent2D& size, std::vector<VkFormat> color, VkFormat depth)
: m_device(device)
, m_alloc(alloc)
{
create(size, color, depth);
}
nvvkhl::GBuffer::~GBuffer()
{
destroy();
}
void nvvkhl::GBuffer::create(const VkExtent2D& size, std::vector<VkFormat> color, VkFormat depth)
{
assert(m_colorFormat.empty()); // The buffer must be cleared before creating a new one
m_imageSize = size;
m_colorFormat = std::move(color);
m_depthFormat = depth;
nvvk::DebugUtil dutil(m_device);
VkImageLayout layout{VK_IMAGE_LAYOUT_GENERAL};
auto num_color = static_cast<uint32_t>(m_colorFormat.size());
m_res.gBufferColor.resize(num_color);
m_res.descriptor.resize(num_color);
m_res.uiImageViews.resize(num_color);
for(uint32_t c = 0; c < num_color; c++)
{
{ // Color image
VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
VkImageCreateInfo info = nvvk::makeImage2DCreateInfo(m_imageSize, m_colorFormat[c], usage);
m_res.gBufferColor[c] = m_alloc->createImage(info);
dutil.setObjectName(m_res.gBufferColor[c].image, "G-Color" + std::to_string(c));
}
{ // Image color view
VkImageViewCreateInfo info = nvvk::makeImage2DViewCreateInfo(m_res.gBufferColor[c].image, m_colorFormat[c]);
vkCreateImageView(m_device, &info, nullptr, &m_res.descriptor[c].imageView);
dutil.setObjectName(m_res.descriptor[c].imageView, "G-Color" + std::to_string(c));
}
{ // UI Image color view
VkImageViewCreateInfo info = nvvk::makeImage2DViewCreateInfo(m_res.gBufferColor[c].image, m_colorFormat[c]);
info.components.a = VK_COMPONENT_SWIZZLE_ONE;
vkCreateImageView(m_device, &info, nullptr, &m_res.uiImageViews[c]);
dutil.setObjectName(m_res.uiImageViews[c], "UI G-Color" + std::to_string(c));
}
if(m_res.descriptor[c].sampler == VK_NULL_HANDLE)
{ // Image sampler: nearest sampling by default
VkSamplerCreateInfo info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
m_res.descriptor[c].sampler = m_alloc->acquireSampler(info);
dutil.setObjectName(m_res.descriptor[c].sampler, "G-Sampler");
}
}
if(m_depthFormat != VK_FORMAT_UNDEFINED)
{ // Depth buffer
VkImageCreateInfo info = nvvk::makeImage2DCreateInfo(m_imageSize, m_depthFormat,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
m_res.gBufferDepth = m_alloc->createImage(info);
dutil.setObjectName(m_res.gBufferDepth.image, "G-Depth");
}
if(m_depthFormat != VK_FORMAT_UNDEFINED)
{ // Image depth view
VkImageViewCreateInfo info = nvvk::makeImage2DViewCreateInfo(m_res.gBufferDepth.image, m_depthFormat);
info.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
vkCreateImageView(m_device, &info, nullptr, &m_res.depthView);
dutil.setObjectName(m_res.depthView, "G-Depth");
}
{ // Change color image layout
nvvk::CommandPool cpool(m_device, 0);
VkCommandBuffer cmd = cpool.createCommandBuffer();
for(uint32_t c = 0; c < num_color; c++)
{
nvvk::cmdBarrierImageLayout(cmd, m_res.gBufferColor[c].image, VK_IMAGE_LAYOUT_UNDEFINED, layout);
m_res.descriptor[c].imageLayout = layout;
// Clear to avoid garbage data
VkClearColorValue clear_value = {{0.F, 0.F, 0.F, 0.F}};
VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vkCmdClearColorImage(cmd, m_res.gBufferColor[c].image, layout, &clear_value, 1, &range);
}
cpool.submitAndWait(cmd);
}
// Descriptor Set for ImGUI
if((ImGui::GetCurrentContext() != nullptr) && ImGui::GetIO().BackendPlatformUserData != nullptr)
{
VkSamplerCreateInfo info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
info.minFilter = VK_FILTER_LINEAR;
info.magFilter = VK_FILTER_LINEAR;
m_res.linearSampler = m_alloc->acquireSampler(info);
for(size_t d = 0; d < m_res.descriptor.size(); ++d)
{
const VkDescriptorImageInfo& desc = m_res.descriptor[d];
m_descriptorSet.push_back(ImGui_ImplVulkan_AddTexture(m_res.linearSampler, m_res.uiImageViews[d], layout));
}
}
}
//-------------------------------------------
// Destroying all allocated resources
//
void nvvkhl::GBuffer::destroy()
{
if((ImGui::GetCurrentContext() != nullptr) && ImGui::GetIO().BackendPlatformUserData != nullptr)
{
for(VkDescriptorSet set : m_descriptorSet)
{
ImGui_ImplVulkan_RemoveTexture(set);
}
if(m_res.linearSampler)
m_alloc->releaseSampler(m_res.linearSampler);
}
for(nvvk::Image bc : m_res.gBufferColor)
{
m_alloc->destroy(bc);
}
m_alloc->destroy(m_res.gBufferDepth);
vkDestroyImageView(m_device, m_res.depthView, nullptr);
for(const VkDescriptorImageInfo& desc : m_res.descriptor)
{
vkDestroyImageView(m_device, desc.imageView, nullptr);
m_alloc->releaseSampler(desc.sampler);
}
for(const VkImageView& view : m_res.uiImageViews)
{
vkDestroyImageView(m_device, view, nullptr);
}
// Reset everything to zero
m_res = {};
m_imageSize = {};
m_colorFormat.clear();
m_descriptorSet.clear();
}
//--------------------------------------------------------------------------------------------------------------
// Creating a buffer from one of the color image.
// This can be used to save image to disk:
//
// Note: it is the responsibility to the function who call this, to destroy the buffer.
nvvk::Buffer nvvkhl::GBuffer::createImageToBuffer(VkCommandBuffer cmd, uint32_t i /*= 0*/) const
{
// Source image
VkImage src_image = getColorImage(i);
VkExtent2D img_size = getSize();
VkFormat format = getColorFormat(i);
uint32_t bytesPerPixel = 0;
if(format >= VK_FORMAT_R8G8B8A8_UNORM && format <= VK_FORMAT_B8G8R8A8_SRGB)
bytesPerPixel = 4 * sizeof(uint8_t);
else if(format >= VK_FORMAT_R16G16B16A16_UNORM && format <= VK_FORMAT_R16G16B16A16_SFLOAT)
bytesPerPixel = 4 * sizeof(uint16_t);
else if(format >= VK_FORMAT_R32G32B32A32_UINT && format <= VK_FORMAT_R32G32B32A32_SFLOAT)
bytesPerPixel = 4 * sizeof(uint32_t);
assert(bytesPerPixel != 0); // Format unsupported
// Destination buffer
size_t buf_size = static_cast<size_t>(bytesPerPixel) * img_size.width * img_size.height;
nvvk::Buffer dst_buffer = m_alloc->createBuffer(buf_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
// Region to copy from the image (all)
VkBufferImageCopy region = {};
region.bufferRowLength = img_size.width;
region.bufferImageHeight = img_size.height;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = img_size.width;
region.imageExtent.height = img_size.height;
region.imageExtent.depth = 1;
// Copy the image to buffer
nvvk::cmdBarrierImageLayout(cmd, src_image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
vkCmdCopyImageToBuffer(cmd, src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_buffer.buffer, 1, &region);
nvvk::cmdBarrierImageLayout(cmd, src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
// Barrier to make sure work is done
VkMemoryBarrier memBarrier = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
memBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memBarrier, 0,
nullptr, 0, nullptr);
return dst_buffer;
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2023 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "vulkan/vulkan_core.h"
#include "nvvk/resourceallocator_vk.hpp"
/** @DOC_START
# class nvvkhl::GBuffer
> This class is an help for creating GBuffers.
This can be use to create a GBuffer with multiple color images and a depth image. The GBuffer can be used to render the scene in multiple passes, such as deferred rendering.
To use this class, you need to create it and call the `create` method to create the GBuffer. The `create` method will create the images and the descriptor set for the GBuffer. The `destroy` method will destroy the images and the descriptor set.
Note: the `getDescriptorSet` method can be use to display the image in ImGui. Ex: `ImGui::Image((ImTextureID)gbuffer.getDescriptorSet(), ImVec2(128, 128));`
@DOC_END */
namespace nvvkhl {
class GBuffer
{
public:
GBuffer(VkDevice device, nvvk::ResourceAllocator* alloc);
GBuffer(VkDevice device, nvvk::ResourceAllocator* alloc, const VkExtent2D& size, VkFormat color, VkFormat depth = VK_FORMAT_UNDEFINED);
GBuffer(VkDevice device, nvvk::ResourceAllocator* alloc, const VkExtent2D& size, std::vector<VkFormat> color, VkFormat depth = VK_FORMAT_UNDEFINED);
~GBuffer();
void create(const VkExtent2D& size, std::vector<VkFormat> color, VkFormat depth);
void destroy();
VkDescriptorSet getDescriptorSet(uint32_t i = 0) const { return m_descriptorSet[i]; }
VkExtent2D getSize() const { return m_imageSize; }
VkImage getColorImage(uint32_t i = 0) const { return m_res.gBufferColor[i].image; }
VkImage getDepthImage() const { return m_res.gBufferDepth.image; }
VkImageView getColorImageView(uint32_t i = 0) const { return m_res.descriptor[i].imageView; }
const VkDescriptorImageInfo& getDescriptorImageInfo(uint32_t i = 0) const { return m_res.descriptor[i]; }
VkImageView getDepthImageView() const { return m_res.depthView; }
VkFormat getColorFormat(uint32_t i = 0) const { return m_colorFormat[i]; }
VkFormat getDepthFormat() const { return m_depthFormat; }
float getAspectRatio() { return static_cast<float>(m_imageSize.width) / static_cast<float>(m_imageSize.height); }
// Create a buffer from the VkImage, useful for saving to disk
nvvk::Buffer createImageToBuffer(VkCommandBuffer cmd, uint32_t i = 0) const;
private:
struct Resources
{
std::vector<nvvk::Image> gBufferColor; // All color image to render into
nvvk::Image gBufferDepth; // Depth buffer
VkImageView depthView = VK_NULL_HANDLE; // Image view of the depth buffer
std::vector<VkDescriptorImageInfo> descriptor; // Holds the sampler and image view
std::vector<VkImageView> uiImageViews; // Image view for UI purposes with alpha channel set to 1.0
VkSampler linearSampler = VK_NULL_HANDLE;
};
Resources m_res;
VkExtent2D m_imageSize{0U, 0U}; // Current image size
std::vector<VkFormat> m_colorFormat; // Color format of the image
VkFormat m_depthFormat{VK_FORMAT_X8_D24_UNORM_PACK32}; // Depth format of the depth buffer
std::vector<VkDescriptorSet> m_descriptorSet; // For displaying the image with ImGui
VkDevice m_device{VK_NULL_HANDLE};
nvvk::ResourceAllocator* m_alloc;
};
} // namespace nvvkhl

View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/** @DOC_START
# class nvvkhl::GlslCompiler
> This class is a wrapper around the shaderc compiler to help compiling GLSL to Spir-V using shaderC
@DOC_END */
#pragma once
#include <memory>
#include <filesystem>
#include <shaderc/shaderc.hpp>
#include "nvh/fileoperations.hpp"
#include "nvvk/error_vk.hpp"
namespace nvvkhl {
// Implementation of the libshaderc includer interface.
class GlslIncluder : public shaderc::CompileOptions::IncluderInterface
{
public:
GlslIncluder(const std::vector<std::string>& includePaths)
: m_includePaths(includePaths)
{
}
// Subtype of shaderc_include_result that holds the include data we found;
// MUST be static_cast to this type before deleting as shaderc_include_result lacks virtual destructor.
struct IncludeResult : public shaderc_include_result
{
IncludeResult(const std::string& content, const std::string& filenameFound)
: m_content(content)
, m_filenameFound(filenameFound)
{
this->source_name = m_filenameFound.data();
this->source_name_length = m_filenameFound.size();
this->content = m_content.data();
this->content_length = m_content.size();
this->user_data = nullptr;
}
const std::string m_content;
const std::string m_filenameFound;
};
shaderc_include_result* GetInclude(const char* requested_source, shaderc_include_type type, const char* requesting_source, size_t include_depth) override
{
// Check the relative path for #include "quotes"
std::string find_name;
if(type == shaderc_include_type_relative)
{
std::filesystem::path relative_path = std::filesystem::path(requesting_source).parent_path() / requested_source;
if(std::filesystem::exists(relative_path))
find_name = relative_path.string();
}
// If nothing found yet, search include directories
// TODO: skip nvh::findFile searching the current working directory
if(find_name.empty())
{
find_name = nvh::findFile(requested_source, m_includePaths, true /*warn*/);
}
std::string src_code;
if(find_name.empty())
{
// [shaderc.h] For a failed inclusion, this contains the error message.
src_code = "Could not find include file in any include path.";
}
else
{
src_code = nvh::loadFile(find_name, false /*binary*/);
}
return new IncludeResult(src_code, find_name);
}
// Handles shaderc_include_result_release_fn callbacks.
void ReleaseInclude(shaderc_include_result* data) override { delete static_cast<IncludeResult*>(data); };
const std::vector<std::string>& m_includePaths;
};
class GlslCompiler : public shaderc::Compiler
{
public:
GlslCompiler() { m_compilerOptions = makeOptions(); };
~GlslCompiler() = default;
void addInclude(const std::string& p) { m_includePaths.push_back(p); }
shaderc::CompileOptions* options() { return m_compilerOptions.get(); }
// Returns a blank CompileOptions object initialized with this GlslCompiler's
// GlslIncluder. CompileOptions must not outlive the GlslCompiler as it holds
// a reference to the includes.
std::unique_ptr<shaderc::CompileOptions> makeOptions()
{
auto options = std::make_unique<shaderc::CompileOptions>();
options->SetIncluder(std::make_unique<GlslIncluder>(m_includePaths));
return options;
}
shaderc::SpvCompilationResult compileFile(const std::string& filename, shaderc_shader_kind shader_kind)
{
std::string find_file = nvh::findFile(filename, m_includePaths, true);
if(find_file.empty())
return {};
return compileFile(find_file, shader_kind, *m_compilerOptions);
}
// Overload to provide a separate compile options object.
shaderc::SpvCompilationResult compileFile(const std::string& filename, shaderc_shader_kind shader_kind, const shaderc::CompileOptions& options)
{
if(!std::filesystem::exists(filename))
return {};
std::string source_code = nvh::loadFile(filename, false);
return CompileGlslToSpv(source_code, shader_kind, filename.c_str(), options);
}
VkShaderModule createModule(VkDevice device, const shaderc::SpvCompilationResult& compResult)
{
VkShaderModuleCreateInfo shaderModuleCreateInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
shaderModuleCreateInfo.codeSize = (compResult.end() - compResult.begin()) * sizeof(uint32_t);
shaderModuleCreateInfo.pCode = reinterpret_cast<const uint32_t*>(compResult.begin());
VkShaderModule shaderModule;
NVVK_CHECK(vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &shaderModule));
return shaderModule;
}
void resetOptions() { m_compilerOptions = makeOptions(); }
void resetIncludes() { m_includePaths = {}; }
private:
std::vector<std::string> m_includePaths;
std::unique_ptr<shaderc::CompileOptions> m_compilerOptions;
};
} // namespace nvvkhl

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "gltf_scene.hpp"
#include <filesystem>
#include "nvh/timesampler.hpp"
void nvvkhl::Scene::load(const std::string& filename, nvh::GltfAttributes requested, nvh::GltfAttributes forced)
{
nvh::ScopedTimer st(std::string(__FUNCTION__) + "\n");
LOGI("%s%s\n", nvh::ScopedTimer::indent().c_str(), filename.c_str());
m_scene = {};
m_model = {};
m_filename = filename;
nvh::Stopwatch sw;
tinygltf::TinyGLTF tcontext;
std::string warn;
std::string error;
auto ext = std::filesystem::path(filename).extension().string();
bool result{false};
if(ext == ".gltf")
{
result = tcontext.LoadASCIIFromFile(&m_model, &error, &warn, filename);
}
else if(ext == ".glb")
{
result = tcontext.LoadBinaryFromFile(&m_model, &error, &warn, filename);
}
if(!result)
{
LOGW("%s%s\n", st.indent().c_str(), warn.c_str());
LOGE("%s%s\n", st.indent().c_str(), error.c_str());
assert(!"Error while loading scene");
}
m_scene.importMaterials(m_model);
m_scene.importDrawableNodes(m_model, requested, forced);
}
void nvvkhl::Scene::destroy()
{
m_scene = {};
m_model = {};
m_filename = {};
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <string>
#include "nvh/gltfscene.hpp"
/** @DOC_START
# class nvvkhl::Scene
> This class is responsible for loading and managing a glTF scene.
It is using the `nvh::GltfScene` to load and manage the scene and `tinygltf` to parse the glTF file.
@DOC_END */
namespace nvvkhl {
class Scene
{
public:
// Loading .gltf or .glb, `request` attribute and `force` to create if not present
void load(const std::string& filename,
nvh::GltfAttributes requested = nvh::GltfAttributes::Normal | nvh::GltfAttributes::Texcoord_0 | nvh::GltfAttributes::Tangent,
nvh::GltfAttributes forced = nvh::GltfAttributes::Normal | nvh::GltfAttributes::Texcoord_0 | nvh::GltfAttributes::Tangent);
// Getters
const nvh::GltfScene& scene() const { return m_scene; }
const tinygltf::Model& model() const { return m_model; }
nvh::GltfScene& scene() { return m_scene; }
tinygltf::Model& model() { return m_model; }
bool valid() const { return !m_scene.m_nodes.empty(); }
const std::string& filename() const { return m_filename; }
// Clearing loaded model
void clearModel() { m_model = {}; }
void destroy();
private:
nvh::GltfScene m_scene;
tinygltf::Model m_model;
std::string m_filename;
};
} // namespace nvvkhl

View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2022-2023 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "gltf_scene_rtx.hpp"
#include "nvvk/buffers_vk.hpp"
#include "shaders/dh_scn_desc.h"
#include "nvh/timesampler.hpp"
nvvkhl::SceneRtx::SceneRtx(nvvk::Context* ctx, nvvk::ResourceAllocator* alloc, uint32_t queueFamilyIndex)
: m_ctx(ctx)
, m_alloc(alloc)
{
// Requesting ray tracing properties
VkPhysicalDeviceProperties2 prop2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
prop2.pNext = &m_rtProperties;
vkGetPhysicalDeviceProperties2(m_ctx->m_physicalDevice, &prop2);
// Create utilities to create BLAS/TLAS and the Shading Binding Table (SBT)
m_rtBuilder.setup(m_ctx->m_device, m_alloc, queueFamilyIndex);
}
nvvkhl::SceneRtx::~SceneRtx()
{
destroy();
}
void nvvkhl::SceneRtx::create(const Scene& scn, const SceneVk& scnVk, VkBuildAccelerationStructureFlagsKHR flags /*=VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR */)
{
destroy(); // Make sure not to leave allocated buffers
createBottomLevelAS(scn.scene(), scnVk, flags);
createTopLevelAS(scn.scene(), flags);
}
VkAccelerationStructureKHR nvvkhl::SceneRtx::tlas()
{
return m_rtBuilder.getAccelerationStructure();
}
void nvvkhl::SceneRtx::destroy()
{
m_rtBuilder.destroy();
}
//--------------------------------------------------------------------------------------------------
// Converting a PrimitiveMesh as input for BLAS
//
nvvk::RaytracingBuilderKHR::BlasInput nvvkhl::SceneRtx::primitiveToGeometry(const nvh::GltfPrimMesh& prim,
VkDeviceAddress vertexAddress,
VkDeviceAddress indexAddress)
{
uint32_t max_prim_count = prim.indexCount / 3;
// Describe buffer as array of VertexObj.
VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR};
triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data.
triangles.vertexData.deviceAddress = vertexAddress;
triangles.vertexStride = sizeof(nvvkhl_shaders::Vertex);
triangles.indexType = VK_INDEX_TYPE_UINT32;
triangles.indexData.deviceAddress = indexAddress;
triangles.maxVertex = prim.vertexCount - 1;
//triangles.transformData; // Identity
// Identify the above data as containing opaque triangles.
VkAccelerationStructureGeometryKHR as_geom{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR};
as_geom.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
as_geom.flags = VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR;
as_geom.geometry.triangles = triangles;
VkAccelerationStructureBuildRangeInfoKHR offset{};
offset.firstVertex = 0;
offset.primitiveCount = max_prim_count;
offset.primitiveOffset = 0;
offset.transformOffset = 0;
// Our blas is made from only one geometry, but could be made of many geometries
nvvk::RaytracingBuilderKHR::BlasInput input;
input.asGeometry.emplace_back(as_geom);
input.asBuildOffsetInfo.emplace_back(offset);
return input;
}
void nvvkhl::SceneRtx::createBottomLevelAS(const nvh::GltfScene& scn, const SceneVk& scnVk, VkBuildAccelerationStructureFlagsKHR flags)
{
nvh::ScopedTimer st(std::string(__FUNCTION__) + "\n");
// BLAS - Storing each primitive in a geometry
std::vector<nvvk::RaytracingBuilderKHR::BlasInput> all_blas;
all_blas.reserve(scn.m_primMeshes.size());
// Retrieve the array of primitive buffers (see in SceneVk)
const auto& vertices = scnVk.vertices();
const auto& indices = scnVk.indices();
for(uint32_t p_idx = 0; p_idx < scn.m_primMeshes.size(); p_idx++)
{
auto vertex_address = nvvk::getBufferDeviceAddress(m_ctx->m_device, vertices[p_idx].buffer);
auto index_address = nvvk::getBufferDeviceAddress(m_ctx->m_device, indices[p_idx].buffer);
auto geo = primitiveToGeometry(scn.m_primMeshes[p_idx], vertex_address, index_address);
all_blas.push_back({geo});
}
m_rtBuilder.buildBlas(all_blas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR
| VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR);
}
void nvvkhl::SceneRtx::createTopLevelAS(const nvh::GltfScene& scn, VkBuildAccelerationStructureFlagsKHR flags)
{
nvh::ScopedTimer st(__FUNCTION__);
std::vector<VkAccelerationStructureInstanceKHR> tlas;
tlas.reserve(scn.m_nodes.size());
for(const auto& node : scn.m_nodes)
{
VkGeometryInstanceFlagsKHR flags{};
const nvh::GltfPrimMesh& prim_mesh = scn.m_primMeshes[node.primMesh];
const nvh::GltfMaterial& mat = scn.m_materials[prim_mesh.materialIndex];
// Always opaque, no need to use anyhit (faster)
if(mat.alphaMode == 0 || (mat.baseColorFactor.w == 1.0F && mat.baseColorTexture == -1))
{
flags |= VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR;
}
// Need to skip the cull flag in traceray_rtx for double sided materials
if(mat.doubleSided == 1 || mat.volume.thicknessFactor > 0.0F)
{
flags |= VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
}
VkAccelerationStructureInstanceKHR ray_inst{};
ray_inst.transform = nvvk::toTransformMatrixKHR(node.worldMatrix); // Position of the instance
ray_inst.instanceCustomIndex = node.primMesh; // gl_InstanceCustomIndexEXT
ray_inst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(node.primMesh);
ray_inst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects
ray_inst.flags = flags;
ray_inst.mask = 0xFF;
tlas.emplace_back(ray_inst);
}
m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR);
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "nvvk/context_vk.hpp"
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/raytraceKHR_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
#include "nvvkhl/pipeline_container.hpp"
#include "gltf_scene_vk.hpp"
/** @DOC_START
# class nvvkhl::SceneRtx
> This class is responsible for the ray tracing acceleration structure.
It is using the `nvvkhl::Scene` and `nvvkhl::SceneVk` information to create the acceleration structure.
@DOC_END */
namespace nvvkhl {
class SceneRtx
{
public:
SceneRtx(nvvk::Context* ctx, nvvk::ResourceAllocator* alloc, uint32_t queueFamilyIndex = 0U);
~SceneRtx();
// Create both bottom and top level acceleration structures
void create(const Scene& scn,
const SceneVk& scnVk,
VkBuildAccelerationStructureFlagsKHR flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR
| VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR);
// Create all bottom level acceleration structures (BLAS)
virtual void createBottomLevelAS(const nvh::GltfScene& scn,
const SceneVk& scnVk,
VkBuildAccelerationStructureFlagsKHR flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR
| VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR);
// Create the top level acceleration structures, referencing all BLAS
virtual void createTopLevelAS(const nvh::GltfScene& scn,
VkBuildAccelerationStructureFlagsKHR flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR
| VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR);
// Return the constructed acceleration structure
VkAccelerationStructureKHR tlas();
void destroy();
protected:
nvvk::RaytracingBuilderKHR::BlasInput primitiveToGeometry(const nvh::GltfPrimMesh& prim,
VkDeviceAddress vertexAddress,
VkDeviceAddress indexAddress);
nvvk::Context* m_ctx;
nvvk::ResourceAllocator* m_alloc;
VkPhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR};
nvvk::RaytracingBuilderKHR m_rtBuilder;
};
} // namespace nvvkhl

View file

@ -0,0 +1,604 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "gltf_scene_vk.hpp"
#include <inttypes.h>
#include <mutex>
#include <sstream>
#include "stb_image.h"
#include "nvvk/images_vk.hpp"
#include "nvh/parallel_work.hpp"
#include "nvh/timesampler.hpp"
#include "nvvk/buffers_vk.hpp"
#include "shaders/dh_scn_desc.h"
nvvkhl::SceneVk::SceneVk(nvvk::Context* ctx, nvvk::ResourceAllocator* alloc)
: m_ctx(ctx)
, m_alloc(alloc)
{
m_dutil = std::make_unique<nvvk::DebugUtil>(ctx->m_device); // Debug utility
}
//--------------------------------------------------------------------------------------------------
// Create all Vulkan resources to hold a nvvkhl::Scene
//
void nvvkhl::SceneVk::create(VkCommandBuffer cmd, const nvvkhl::Scene& scn)
{
nvh::ScopedTimer st(__FUNCTION__);
destroy(); // Make sure not to leave allocated buffers
namespace fs = std::filesystem;
fs::path basedir = fs::path(scn.filename()).parent_path();
createMaterialBuffer(cmd, scn.scene());
createInstanceInfoBuffer(cmd, scn.scene());
createVertexBuffer(cmd, scn.scene());
createTextureImages(cmd, scn.model(), basedir);
// Buffer references
nvvkhl_shaders::SceneDescription scene_desc{};
scene_desc.materialAddress = nvvk::getBufferDeviceAddress(m_ctx->m_device, m_bMaterial.buffer);
scene_desc.primInfoAddress = nvvk::getBufferDeviceAddress(m_ctx->m_device, m_bPrimInfo.buffer);
scene_desc.instInfoAddress = nvvk::getBufferDeviceAddress(m_ctx->m_device, m_bInstances.buffer);
m_bSceneDesc = m_alloc->createBuffer(cmd, sizeof(nvvkhl_shaders::SceneDescription), &scene_desc,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
m_dutil->DBG_NAME(m_bSceneDesc.buffer);
}
//--------------------------------------------------------------------------------------------------
// Create a buffer of all materials, with only the elements we need
//
void nvvkhl::SceneVk::createMaterialBuffer(VkCommandBuffer cmd, const nvh::GltfScene& scn)
{
nvh::ScopedTimer st(__FUNCTION__);
std::vector<nvvkhl_shaders::GltfShadeMaterial> shade_materials;
shade_materials.reserve(scn.m_materials.size());
for(const auto& m : scn.m_materials)
{
nvvkhl_shaders::GltfShadeMaterial s{};
s.emissiveFactor = m.emissiveFactor;
s.emissiveTexture = m.emissiveTexture;
s.normalTexture = m.normalTexture;
s.normalTextureScale = m.normalTextureScale;
s.pbrBaseColorFactor = m.baseColorFactor;
s.pbrBaseColorTexture = m.baseColorTexture;
s.pbrMetallicFactor = m.metallicFactor;
s.pbrMetallicRoughnessTexture = m.metallicRoughnessTexture;
s.pbrRoughnessFactor = m.roughnessFactor;
s.alphaMode = m.alphaMode;
s.alphaCutoff = m.alphaCutoff;
// KHR_materials_transmission
s.transmissionFactor = m.transmission.factor;
s.transmissionTexture = m.transmission.texture;
// KHR_materials_ior
s.ior = m.ior.ior;
// KHR_materials_volume
s.attenuationColor = m.volume.attenuationColor;
s.thicknessFactor = m.volume.thicknessFactor;
s.thicknessTexture = m.volume.thicknessTexture;
s.attenuationDistance = m.volume.attenuationDistance;
// KHR_materials_clearcoat
s.clearcoatFactor = m.clearcoat.factor;
s.clearcoatRoughness = m.clearcoat.roughnessFactor;
s.clearcoatRoughnessTexture = m.clearcoat.roughnessTexture;
s.clearcoatTexture = m.clearcoat.texture;
s.clearcoatNormalTexture = m.clearcoat.normalTexture;
// KHR_materials_specular
s.specularFactor = m.specular.specularFactor;
s.specularTexture = m.specular.specularTexture;
s.specularColorFactor = m.specular.specularColorFactor;
s.specularColorTexture = m.specular.specularColorTexture;
// KHR_texture_transform
s.uvTransform = m.textureTransform.uvTransform;
// KHR_materials_emissive_strength
s.emissiveFactor *= m.emissiveStrength.emissiveStrength;
shade_materials.emplace_back(s);
}
m_bMaterial = m_alloc->createBuffer(cmd, shade_materials,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
m_dutil->DBG_NAME(m_bMaterial.buffer);
}
//--------------------------------------------------------------------------------------------------
// Array of instance information
// - Use by the vertex shader to retrieve the position of the instance
void nvvkhl::SceneVk::createInstanceInfoBuffer(VkCommandBuffer cmd, const nvh::GltfScene& scn)
{
nvh::ScopedTimer st(__FUNCTION__);
std::vector<nvvkhl_shaders::InstanceInfo> inst_info;
for(const auto& node : scn.m_nodes)
{
nvvkhl_shaders::InstanceInfo info{};
info.objectToWorld = node.worldMatrix;
info.worldToObject = glm::inverse(node.worldMatrix);
inst_info.emplace_back(info);
}
m_bInstances = m_alloc->createBuffer(cmd, inst_info, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
m_dutil->DBG_NAME(m_bInstances.buffer);
}
//--------------------------------------------------------------------------------------------------
// Creating information per primitive
// - Create a buffer of Vertex and Index for each primitive
// - Each primInfo has a reference to the vertex and index buffer, and which material id it uses
//
void nvvkhl::SceneVk::createVertexBuffer(VkCommandBuffer cmd, const nvh::GltfScene& scn)
{
nvh::ScopedTimer st(__FUNCTION__);
std::vector<nvvkhl_shaders::PrimMeshInfo> prim_info; // The array of all primitive information
uint32_t prim_idx{0};
auto usage_flag = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT // Buffer read/write access within shaders, without size limitation
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT // The buffer can be referred to using its address instead of a binding
| VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR // Usage as a data source for acceleration structure builds
| VK_BUFFER_USAGE_TRANSFER_DST_BIT // Buffer can be copied into
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT; // Buffer can be copied from (e.g. for inspection)
// Primitives in glTF can be reused, this allow to retrieve them
std::unordered_map<std::string, nvvk::Buffer> cache_primitive;
prim_info.resize(scn.m_primMeshes.size());
m_bVertices.resize(scn.m_primMeshes.size());
m_bIndices.resize(scn.m_primMeshes.size());
for(const auto& prim_mesh : scn.m_primMeshes)
{
// Create a key to find a primitive that is already uploaded
std::stringstream o;
o << prim_mesh.vertexOffset << ":" << prim_mesh.vertexCount;
std::string key = o.str();
nvvk::Buffer v_buffer; // Vertex buffer result
auto it = cache_primitive.find(key);
if(it == cache_primitive.end())
{
// Filling in parallel the vector of vertex used on the GPU
std::vector<nvvkhl_shaders::Vertex> vertices(prim_mesh.vertexCount);
for(uint32_t v_ctx = 0; v_ctx < prim_mesh.vertexCount; v_ctx++)
{
size_t idx = prim_mesh.vertexOffset + v_ctx;
const glm::vec3& p = scn.m_positions[idx];
const glm::vec3& n = scn.m_normals[idx];
const glm::vec4& t = scn.m_tangents[idx];
const glm::vec2& u = scn.m_texcoords0[idx];
nvvkhl_shaders::Vertex& v = vertices[v_ctx];
v.position = glm::vec4(p, u.x); // Adding texcoord to the end of position and normal vector
v.normal = glm::vec4(n, u.y);
v.tangent = t;
}
// Buffer of Vertex per primitive
v_buffer = m_alloc->createBuffer(cmd, vertices, usage_flag | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
m_dutil->DBG_NAME_IDX(v_buffer.buffer, prim_idx);
cache_primitive[key] = v_buffer;
}
else
{
v_buffer = it->second;
}
m_bVertices[prim_idx] = v_buffer;
// Buffer of indices
std::vector<uint32_t> indices(prim_mesh.indexCount);
memcpy(indices.data(), &scn.m_indices[prim_mesh.firstIndex], sizeof(uint32_t) * prim_mesh.indexCount);
auto i_buffer = m_alloc->createBuffer(cmd, indices, usage_flag | VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
m_dutil->DBG_NAME_IDX(i_buffer.buffer, prim_idx);
m_bIndices[prim_idx] = i_buffer;
// Primitive information, material Id and addresses of buffers
prim_info[prim_idx].materialIndex = prim_mesh.materialIndex;
prim_info[prim_idx].vertexAddress = nvvk::getBufferDeviceAddress(m_ctx->m_device, v_buffer.buffer);
prim_info[prim_idx].indexAddress = nvvk::getBufferDeviceAddress(m_ctx->m_device, i_buffer.buffer);
prim_idx++;
}
// Creating the buffer of all primitive information
m_bPrimInfo = m_alloc->createBuffer(cmd, prim_info, usage_flag);
m_dutil->DBG_NAME(m_bPrimInfo.buffer);
}
//--------------------------------------------------------------------------------------------------------------
// Returning the Vulkan sampler information from the information in the tinygltf
//
VkSamplerCreateInfo getSampler(const tinygltf::Model& tiny, int index)
{
VkSamplerCreateInfo samplerInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.maxLod = FLT_MAX;
if(index < 0)
return samplerInfo;
const auto& sampler = tiny.samplers[index];
const std::map<int, VkFilter> filters = {{9728, VK_FILTER_NEAREST}, {9729, VK_FILTER_LINEAR},
{9984, VK_FILTER_NEAREST}, {9985, VK_FILTER_LINEAR},
{9986, VK_FILTER_NEAREST}, {9987, VK_FILTER_LINEAR}};
const std::map<int, VkSamplerMipmapMode> mipmapModes = {
{9728, VK_SAMPLER_MIPMAP_MODE_NEAREST}, {9729, VK_SAMPLER_MIPMAP_MODE_LINEAR},
{9984, VK_SAMPLER_MIPMAP_MODE_NEAREST}, {9985, VK_SAMPLER_MIPMAP_MODE_LINEAR},
{9986, VK_SAMPLER_MIPMAP_MODE_NEAREST}, {9987, VK_SAMPLER_MIPMAP_MODE_LINEAR}};
const std::map<int, VkSamplerAddressMode> wrapModes = {
{TINYGLTF_TEXTURE_WRAP_REPEAT, VK_SAMPLER_ADDRESS_MODE_REPEAT},
{TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE},
{TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT, VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT}};
if(sampler.minFilter > -1)
samplerInfo.minFilter = filters.at(sampler.minFilter);
if(sampler.magFilter > -1)
{
samplerInfo.magFilter = filters.at(sampler.magFilter);
samplerInfo.mipmapMode = mipmapModes.at(sampler.magFilter);
}
samplerInfo.addressModeU = wrapModes.at(sampler.wrapS);
samplerInfo.addressModeV = wrapModes.at(sampler.wrapT);
return samplerInfo;
}
//--------------------------------------------------------------------------------------------------------------
// This is creating all images stored in textures
//
void nvvkhl::SceneVk::createTextureImages(VkCommandBuffer cmd, const tinygltf::Model& tiny, const std::filesystem::path& basedir)
{
nvh::ScopedTimer st(std::string(__FUNCTION__) + "\n");
VkSamplerCreateInfo default_sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
default_sampler.minFilter = VK_FILTER_LINEAR;
default_sampler.magFilter = VK_FILTER_LINEAR;
default_sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
default_sampler.maxLod = FLT_MAX;
// Find and all textures/images that should be sRgb encoded.
findSrgbImages(tiny);
// Make dummy image(1,1), needed as we cannot have an empty array
auto addDefaultImage = [&](uint32_t idx, const std::array<uint8_t, 4>& color) {
VkImageCreateInfo image_create_info = nvvk::makeImage2DCreateInfo(VkExtent2D{1, 1});
nvvk::Image image = m_alloc->createImage(cmd, 4, color.data(), image_create_info);
assert(idx < m_images.size());
m_images[idx] = {image, image_create_info};
m_dutil->setObjectName(m_images[idx].nvvkImage.image, "Dummy");
};
// Make dummy texture/image(1,1), needed as we cannot have an empty array
auto addDefaultTexture = [&]() {
assert(!m_images.empty());
SceneImage& scn_image = m_images[0];
VkImageViewCreateInfo iv_info = nvvk::makeImageViewCreateInfo(scn_image.nvvkImage.image, scn_image.createInfo);
m_textures.emplace_back(m_alloc->createTexture(scn_image.nvvkImage, iv_info, default_sampler));
};
// Load images in parallel
m_images.resize(tiny.images.size());
uint32_t num_threads = std::min((uint32_t)tiny.images.size(), std::thread::hardware_concurrency());
const std::string indent = st.indent();
nvh::parallel_batches<1>( // Not batching
tiny.images.size(),
[&](uint64_t i) {
const auto& image = tiny.images[i];
LOGI("%s(%" PRIu64 ") %s \n", indent.c_str(), i, image.uri.c_str());
loadImage(basedir, image, static_cast<int>(i));
},
num_threads);
// Create Vulkan images
for(size_t i = 0; i < m_images.size(); i++)
{
if(!createImage(cmd, m_images[i]))
{
addDefaultImage((uint32_t)i, {255, 0, 255, 255}); // Image not present or incorrectly loaded (image.empty)
}
}
// Add default image if nothing was loaded
if(tiny.images.empty())
{
m_images.resize(1);
addDefaultImage(0, {255, 255, 255, 255});
}
// Creating the textures using the above images
m_textures.reserve(tiny.textures.size());
for(size_t i = 0; i < tiny.textures.size(); i++)
{
int source_image = tiny.textures[i].source;
if(source_image >= tiny.images.size() || source_image < 0)
{
addDefaultTexture(); // Incorrect source image
continue;
}
VkSamplerCreateInfo sampler = getSampler(tiny, tiny.textures[i].sampler);
SceneImage& scn_image = m_images[source_image];
VkImageViewCreateInfo iv_info = nvvk::makeImageViewCreateInfo(scn_image.nvvkImage.image, scn_image.createInfo);
m_textures.emplace_back(m_alloc->createTexture(scn_image.nvvkImage, iv_info, sampler));
}
// Add a default texture, cannot work with empty descriptor set
if(tiny.textures.empty())
{
addDefaultTexture();
}
}
//-------------------------------------------------------------------------------------------------
// Some images must be sRgb encoded, we find them and will be uploaded with the _SRGB format.
//
void nvvkhl::SceneVk::findSrgbImages(const tinygltf::Model& tiny)
{
// Lambda helper functions
auto addImage = [&](int texID) {
if(texID > -1)
m_sRgbImages.insert(tiny.textures[texID].source);
};
// For images in extensions
auto addImageFromExtension = [&](const tinygltf::Material& mat, const std::string extName, const std::string name) {
const auto& ext = mat.extensions.find(extName);
if(ext != mat.extensions.end())
{
if(ext->second.Has(name))
addImage(ext->second.Get(name).Get("index").Get<int>());
}
};
// Loop over all materials and find the sRgb textures
for(size_t matID = 0; matID < tiny.materials.size(); matID++)
{
const auto& mat = tiny.materials[matID];
// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#metallic-roughness-material
addImage(mat.pbrMetallicRoughness.baseColorTexture.index);
addImage(mat.emissiveTexture.index);
// https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md#extending-materials
addImageFromExtension(mat, "KHR_materials_specular", "specularColorTexture");
// https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_sheen/README.md#sheen
addImageFromExtension(mat, "KHR_materials_sheen", "sheenColorTexture");
// **Deprecated** but still used with some scenes
// https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
addImageFromExtension(mat, "KHR_materials_pbrSpecularGlossiness", "diffuseTexture");
addImageFromExtension(mat, "KHR_materials_pbrSpecularGlossiness", "specularGlossinessTexture");
}
// Special, if the 'extra' in the texture has a gamma defined greater than 1.0, it is sRGB
for(size_t texID = 0; texID < tiny.textures.size(); texID++)
{
const auto& texture = tiny.textures[texID];
if(texture.extras.Has("gamma") && texture.extras.Get("gamma").GetNumberAsDouble() > 1.0)
{
m_sRgbImages.insert(texture.source);
}
}
}
//--------------------------------------------------------------------------------------------------
// Loading images from disk
//
void nvvkhl::SceneVk::loadImage(const std::filesystem::path& basedir, const tinygltf::Image& gltfImage, int imageID)
{
namespace fs = std::filesystem;
auto& image = m_images[imageID];
bool is_srgb = m_sRgbImages.find(imageID) != m_sRgbImages.end();
std::string uri_decoded;
tinygltf::URIDecode(gltfImage.uri, &uri_decoded, nullptr); // ex. whitespace may be represented as %20
fs::path uri = fs::path(uri_decoded);
std::string extension = uri.extension().string();
std::string imgName = uri.filename().string();
std::string img_uri = fs::path(basedir / uri).string();
if(!extension.empty())
{
stbi_uc* data;
int w = 0, h = 0, comp = 0;
// Read the header once to check how many channels it has. We can't trivially use RGB/VK_FORMAT_R8G8B8_UNORM and
// need to set req_comp=4 in such cases.
if(!stbi_info(img_uri.c_str(), &w, &h, &comp))
{
LOGE("Failed to read %s\n", img_uri.c_str());
return;
}
// Read the header again to check if it has 16 bit data, e.g. for a heightmap.
bool is_16Bit = stbi_is_16_bit(img_uri.c_str());
// Load the image
size_t bytes_per_pixel;
int req_comp = comp == 1 ? 1 : 4;
if(is_16Bit)
{
auto data16 = stbi_load_16(img_uri.c_str(), &w, &h, &comp, req_comp);
bytes_per_pixel = sizeof(*data16) * req_comp;
data = reinterpret_cast<stbi_uc*>(data16);
}
else
{
data = stbi_load(img_uri.c_str(), &w, &h, &comp, req_comp);
bytes_per_pixel = sizeof(*data) * req_comp;
}
switch(req_comp)
{
case 1:
image.format = is_16Bit ? VK_FORMAT_R16_UNORM : VK_FORMAT_R8_UNORM;
break;
case 4:
image.format = is_16Bit ? VK_FORMAT_R16G16B16A16_UNORM :
is_srgb ? VK_FORMAT_R8G8B8A8_SRGB :
VK_FORMAT_R8G8B8A8_UNORM;
break;
default:
assert(false);
image.format = VK_FORMAT_UNDEFINED;
}
// Make a copy of the image data to be uploaded to vulkan later
if(data && w > 0 && h > 0 && image.format != VK_FORMAT_UNDEFINED)
{
VkDeviceSize buffer_size = static_cast<VkDeviceSize>(w) * h * bytes_per_pixel;
image.size = VkExtent2D{(uint32_t)w, (uint32_t)h};
image.mipData = {{data, data + buffer_size}};
}
stbi_image_free(data);
}
else
{ // Loaded internally using GLB
image.size = VkExtent2D{(uint32_t)gltfImage.width, (uint32_t)gltfImage.height};
image.format = is_srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
image.mipData.emplace_back(gltfImage.image);
}
}
bool nvvkhl::SceneVk::createImage(const VkCommandBuffer& cmd, SceneImage& image)
{
if(image.size.width == 0 || image.size.height == 0)
return false;
VkFormat format = image.format;
VkExtent2D img_size = image.size;
VkImageCreateInfo image_create_info = nvvk::makeImage2DCreateInfo(img_size, format, VK_IMAGE_USAGE_SAMPLED_BIT, true);
// Check if we can generate mipmap with the the incoming image
bool can_generate_mipmaps = false;
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties(m_ctx->m_physicalDevice, format, &format_properties);
if((format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) == VK_FORMAT_FEATURE_BLIT_DST_BIT)
can_generate_mipmaps = true;
if(image.mipData.size() > 1) // Use only the number of levels defined
image_create_info.mipLevels = (uint32_t)image.mipData.size();
if(image.mipData.size() == 1 && can_generate_mipmaps == false)
image_create_info.mipLevels = 1; // Cannot use cmdGenerateMipmaps
// Keep info for the creation of the texture
image.createInfo = image_create_info;
VkDeviceSize buffer_size = image.mipData[0].size();
nvvk::Image result_image = m_alloc->createImage(cmd, buffer_size, image.mipData[0].data(), image_create_info);
if(image.mipData.size() == 1 && can_generate_mipmaps)
{
nvvk::cmdGenerateMipmaps(cmd, result_image.image, format, img_size, image_create_info.mipLevels);
}
else
{
// Create all mip-levels
nvvk::cmdBarrierImageLayout(cmd, result_image.image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
auto staging = m_alloc->getStaging();
for(uint32_t mip = 1; mip < (uint32_t)image.mipData.size(); mip++)
{
image_create_info.extent.width = std::max(1u, image.size.width >> mip);
image_create_info.extent.height = std::max(1u, image.size.height >> mip);
VkOffset3D offset{};
VkImageSubresourceLayers subresource{};
subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresource.layerCount = 1;
subresource.mipLevel = mip;
std::vector<uint8_t>& mipresource = image.mipData[mip];
VkDeviceSize bufferSize = mipresource.size();
if(image_create_info.extent.width > 0 && image_create_info.extent.height > 0)
{
staging->cmdToImage(cmd, result_image.image, offset, image_create_info.extent, subresource, bufferSize,
mipresource.data());
}
}
nvvk::cmdBarrierImageLayout(cmd, result_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
if(!image.imgName.empty())
{
m_dutil->setObjectName(result_image.image, image.imgName);
}
else
{
m_dutil->DBG_NAME(result_image.image);
}
// Clear image.mipData as it is no longer needed
image = {result_image, image_create_info, image.srgb, image.imgName};
return true;
}
void nvvkhl::SceneVk::destroy()
{
try
{
std::set<VkBuffer> v_set; // Vertex buffer can be shared
for(auto& v : m_bVertices)
{
if(v_set.find(v.buffer) == v_set.end())
{
v_set.insert(v.buffer);
m_alloc->destroy(v); // delete only the one that was not deleted
}
}
}
catch(const std::bad_alloc& /* e */)
{
assert(!"Failed to allocate memory to identify which vertex buffers to destroy!");
}
m_bVertices.clear();
for(auto& i : m_bIndices)
{
m_alloc->destroy(i);
}
m_bIndices.clear();
m_alloc->destroy(m_bMaterial);
m_alloc->destroy(m_bPrimInfo);
m_alloc->destroy(m_bInstances);
m_alloc->destroy(m_bSceneDesc);
for(auto& i : m_images)
{
m_alloc->destroy(i.nvvkImage);
}
m_images.clear();
for(auto& t : m_textures)
{
vkDestroyImageView(m_ctx->m_device, t.descriptor.imageView, nullptr);
}
m_textures.clear();
m_sRgbImages.clear();
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <filesystem>
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/context_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
#include "gltf_scene.hpp"
/** @DOC_START
# class nvvkhl::SceneVk
> This class is responsible for the Vulkan version of the scene.
It is using `nvvkhl::Scene` to create the Vulkan buffers and images.
@DOC_END */
namespace nvvkhl {
// Create the Vulkan version of the Scene
// Allocate the buffers, etc.
class SceneVk
{
public:
SceneVk(nvvk::Context* ctx, nvvk::ResourceAllocator* alloc);
virtual ~SceneVk() { destroy(); }
virtual void create(VkCommandBuffer cmd, const nvvkhl::Scene& scn);
virtual void destroy();
// Getters
const nvvk::Buffer& material() const { return m_bMaterial; }
const nvvk::Buffer& primInfo() const { return m_bPrimInfo; }
const nvvk::Buffer& instances() const { return m_bInstances; }
const nvvk::Buffer& sceneDesc() const { return m_bSceneDesc; }
const std::vector<nvvk::Buffer>& vertices() const { return m_bVertices; }
const std::vector<nvvk::Buffer>& indices() const { return m_bIndices; }
const std::vector<nvvk::Texture>& textures() const { return m_textures; }
uint32_t nbTextures() const { return static_cast<uint32_t>(m_textures.size()); }
protected:
struct SceneImage // Image to be loaded and created
{
nvvk::Image nvvkImage;
VkImageCreateInfo createInfo;
// Loading information
bool srgb{false};
std::string imgName;
VkExtent2D size{0, 0};
VkFormat format{VK_FORMAT_UNDEFINED};
std::vector<std::vector<uint8_t>> mipData;
};
virtual void createMaterialBuffer(VkCommandBuffer cmd, const nvh::GltfScene& scn);
virtual void createInstanceInfoBuffer(VkCommandBuffer cmd, const nvh::GltfScene& scn);
virtual void createVertexBuffer(VkCommandBuffer cmd, const nvh::GltfScene& scn);
virtual void createTextureImages(VkCommandBuffer cmd, const tinygltf::Model& tiny, const std::filesystem::path& basedir);
void findSrgbImages(const tinygltf::Model& tiny);
virtual void loadImage(const std::filesystem::path& basedir, const tinygltf::Image& gltfImage, int imageID);
virtual bool createImage(const VkCommandBuffer& cmd, SceneImage& image);
//--
nvvk::Context* m_ctx;
nvvk::ResourceAllocator* m_alloc;
std::unique_ptr<nvvk::DebugUtil> m_dutil;
nvvk::Buffer m_bMaterial;
nvvk::Buffer m_bPrimInfo;
nvvk::Buffer m_bInstances;
nvvk::Buffer m_bSceneDesc;
std::vector<nvvk::Buffer> m_bVertices;
std::vector<nvvk::Buffer> m_bIndices;
std::vector<SceneImage> m_images;
std::vector<nvvk::Texture> m_textures; // Vector of all textures of the scene
std::set<int> m_sRgbImages; // All images that are in sRGB (typically, only the one used by baseColorTexture)
};
} // namespace nvvkhl

View file

@ -0,0 +1,348 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/*
* HDR sampling is loading an HDR image and creating an acceleration structure for
* sampling the environment.
*/
#define _USE_MATH_DEFINES
#include <numeric>
#include <chrono>
#include <array>
#include "hdr_env.hpp"
#include <glm/glm.hpp>
#include "stb_image.h"
#include "nvh/fileoperations.hpp"
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/commands_vk.hpp"
#include "nvvk/descriptorsets_vk.hpp"
#include "nvvk/context_vk.hpp"
#include "shaders/dh_hdr.h"
#include "nvh/timesampler.hpp"
namespace nvvkhl {
// Forward declaration
std::vector<nvvkhl_shaders::EnvAccel> createEnvironmentAccel(float*& pixels,
const uint32_t& width,
const uint32_t& height,
float& average,
float& integral);
HdrEnv::HdrEnv(nvvk::Context* ctx, nvvk::ResourceAllocator* allocator, uint32_t queueFamilyIndex)
{
setup(ctx->m_device, ctx->m_physicalDevice, queueFamilyIndex, allocator);
}
//--------------------------------------------------------------------------------------------------
//
//
void HdrEnv::setup(const VkDevice& device, const VkPhysicalDevice& /*physicalDevice*/, uint32_t familyIndex, nvvk::ResourceAllocator* allocator)
{
m_device = device;
m_alloc = allocator;
m_familyIndex = familyIndex;
m_debug.setup(device);
}
//--------------------------------------------------------------------------------------------------
//
//
void HdrEnv::destroy()
{
vkDestroyDescriptorPool(m_device, m_descPool, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr);
m_descPool = {};
m_descSetLayout = {};
m_alloc->destroy(m_texHdr);
m_alloc->destroy(m_accelImpSmpl);
}
//--------------------------------------------------------------------------------------------------
// Loading the HDR environment texture (HDR) and create the important accel structure
//
void HdrEnv::loadEnvironment(const std::string& hrdImage)
{
nvh::ScopedTimer st(__FUNCTION__);
m_valid = false;
if(!hrdImage.empty())
{
int32_t width{0};
int32_t height{0};
int32_t component{0};
float* pixels = nullptr;
if(stbi_is_hdr(hrdImage.c_str()) != 0)
{
pixels = stbi_loadf(hrdImage.c_str(), &width, &height, &component, STBI_rgb_alpha);
}
if(pixels != nullptr)
{
VkDeviceSize buffer_size = width * height * 4 * sizeof(float);
VkExtent2D img_size{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
m_hdrImageSize = img_size;
VkSamplerCreateInfo sampler_create_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
sampler_create_info.minFilter = VK_FILTER_LINEAR;
sampler_create_info.magFilter = VK_FILTER_LINEAR;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
// The map is parameterized with the U axis corresponding to the azimuthal angle, and V to the polar angle
// Therefore, in U the sampler will use VK_SAMPLER_ADDRESS_MODE_REPEAT (default), but V needs to use
// CLAMP_TO_EDGE to avoid having light leaking from one pole to another.
sampler_create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT;
VkImageCreateInfo ic_info =
nvvk::makeImage2DCreateInfo(img_size, format, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT);
// We can use a different family index (1 - transfer), to allow loading in a different queue/thread than the display (0)
VkQueue queue = nullptr;
vkGetDeviceQueue(m_device, m_familyIndex, 0, &queue);
{
nvh::ScopedTimer st("Generating Acceleration structure");
{
nvvk::ScopeCommandBuffer cmd_buf(m_device, m_familyIndex, queue);
// Creating the importance sampling for the HDR and storing the info in the m_accelImpSmpl buffer
auto env_accel = createEnvironmentAccel(pixels, img_size.width, img_size.height, m_average, m_integral);
m_accelImpSmpl = m_alloc->createBuffer(cmd_buf, env_accel, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
m_debug.setObjectName(m_accelImpSmpl.buffer, "HDR_accel");
nvvk::Image image = m_alloc->createImage(cmd_buf, buffer_size, pixels, ic_info);
VkImageViewCreateInfo iv_info = nvvk::makeImageViewCreateInfo(image.image, ic_info);
m_texHdr = m_alloc->createTexture(image, iv_info, sampler_create_info);
m_debug.setObjectName(m_texHdr.image, "HDR");
}
m_alloc->finalizeAndReleaseStaging();
}
stbi_image_free(pixels);
m_valid = true;
}
}
if(!m_valid)
{ // Dummy
VkQueue queue = nullptr;
vkGetDeviceQueue(m_device, m_familyIndex, 0, &queue);
{
nvvk::ScopeCommandBuffer cmd_buf(m_device, m_familyIndex, queue);
VkImageCreateInfo image_create_info = nvvk::makeImage2DCreateInfo(VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT);
std::vector<uint8_t> color{255, 255, 255, 255};
nvvk::Image image = m_alloc->createImage(cmd_buf, 4, color.data(), image_create_info);
VkImageViewCreateInfo iv_info = nvvk::makeImageViewCreateInfo(image.image, image_create_info);
VkSamplerCreateInfo sampler_create_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
m_texHdr = m_alloc->createTexture(image, iv_info, sampler_create_info);
m_accelImpSmpl = m_alloc->createBuffer(cmd_buf, color, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
}
m_alloc->finalizeAndReleaseStaging();
m_valid = false;
}
createDescriptorSetLayout();
}
//--------------------------------------------------------------------------------------------------
// Descriptors of the HDR and the acceleration structure
//
void HdrEnv::createDescriptorSetLayout()
{
nvvk::DescriptorSetBindings bind;
VkShaderStageFlags flags = VK_SHADER_STAGE_ALL;
bind.addBinding(nvvkhl_shaders::EnvBindings::eHdr, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, flags); // HDR image
bind.addBinding(nvvkhl_shaders::EnvBindings::eImpSamples, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, flags); // importance sampling
m_descPool = bind.createPool(m_device, 1);
CREATE_NAMED_VK(m_descSetLayout, bind.createLayout(m_device));
CREATE_NAMED_VK(m_descSet, nvvk::allocateDescriptorSet(m_device, m_descPool, m_descSetLayout));
std::vector<VkWriteDescriptorSet> writes;
VkDescriptorBufferInfo accel_imp_smpl{m_accelImpSmpl.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(bind.makeWrite(m_descSet, nvvkhl_shaders::EnvBindings::eHdr, &m_texHdr.descriptor));
writes.emplace_back(bind.makeWrite(m_descSet, nvvkhl_shaders::EnvBindings::eImpSamples, &accel_imp_smpl));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------------------------------
// Build alias map for the importance sampling: Each texel is associated to another texel, or alias,
// so that their combined intensities are a close as possible to the average of the environment map.
// This will later allow the sampling shader to uniformly select a texel in the environment, and
// select either that texel or its alias depending on their relative intensities
//
inline float buildAliasmap(const std::vector<float>& data, std::vector<nvvkhl_shaders::EnvAccel>& accel)
{
auto size = static_cast<uint32_t>(data.size());
// Compute the integral of the emitted radiance of the environment map
// Since each element in data is already weighted by its solid angle
// the integral is a simple sum
float sum = std::accumulate(data.begin(), data.end(), 0.F);
// For each texel, compute the ratio q between the emitted radiance of the texel and the average
// emitted radiance over the entire sphere
// We also initialize the aliases to identity, ie. each texel is its own alias
auto f_size = static_cast<float>(size);
float inverse_average = f_size / sum;
for(uint32_t i = 0; i < size; ++i)
{
accel[i].q = data[i] * inverse_average;
accel[i].alias = i;
}
// Partition the texels according to their emitted radiance ratio wrt. average.
// Texels with a value q < 1 (ie. below average) are stored incrementally from the beginning of the
// array, while texels emitting higher-than-average radiance are stored from the end of the array
std::vector<uint32_t> partition_table(size);
uint32_t s = 0U;
uint32_t large = size;
for(uint32_t i = 0; i < size; ++i)
{
if(accel[i].q < 1.F)
partition_table[s++] = i;
else
partition_table[--large] = i;
}
// Associate the lower-energy texels to higher-energy ones. Since the emission of a high-energy texel may
// be vastly superior to the average,
for(s = 0; s < large && large < size; ++s)
{
// Index of the smaller energy texel
const uint32_t small_energy_index = partition_table[s];
// Index of the higher energy texel
const uint32_t high_energy_index = partition_table[large];
// Associate the texel to its higher-energy alias
accel[small_energy_index].alias = high_energy_index;
// Compute the difference between the lower-energy texel and the average
const float difference_with_average = 1.F - accel[small_energy_index].q;
// The goal is to obtain texel couples whose combined intensity is close to the average.
// However, some texels may have low energies, while others may have very high intensity
// (for example a sunset: the sky is quite dark, but the sun is still visible). In this case
// it may not be possible to obtain a value close to average by combining only two texels.
// Instead, we potentially associate a single high-energy texel to many smaller-energy ones until
// the combined average is similar to the average of the environment map.
// We keep track of the combined average by subtracting the difference between the lower-energy texel and the average
// from the ratio stored in the high-energy texel.
accel[high_energy_index].q -= difference_with_average;
// If the combined ratio to average of the higher-energy texel reaches 1, a balance has been found
// between a set of low-energy texels and the higher-energy one. In this case, we will use the next
// higher-energy texel in the partition when processing the next texel.
if(accel[high_energy_index].q < 1.0F)
large++;
}
// Return the integral of the emitted radiance. This integral will be used to normalize the probability
// distribution function (PDF) of each pixel
return sum;
}
// CIE luminance
inline float luminance(const float* color)
{
return color[0] * 0.2126F + color[1] * 0.7152F + color[2] * 0.0722F;
}
//--------------------------------------------------------------------------------------------------
// Create acceleration data for importance sampling
// See: https://arxiv.org/pdf/1901.05423.pdf
// And store the PDF into the ALPHA channel of pixels
//
inline std::vector<nvvkhl_shaders::EnvAccel> createEnvironmentAccel(float*& pixels,
const uint32_t& width,
const uint32_t& height,
float& average,
float& integral)
{
const uint32_t rx = width;
const uint32_t ry = height;
// Create importance sampling data
std::vector<nvvkhl_shaders::EnvAccel> env_accel(rx * ry);
std::vector<float> importance_data(rx * ry);
float cos_theta0 = 1.0F;
const float step_phi = static_cast<float>(2.0F * M_PI) / static_cast<float>(rx);
const float step_theta = static_cast<float>(M_PI) / static_cast<float>(ry);
double total = 0.0;
// For each texel of the environment map, we compute the related solid angle
// subtended by the texel, and store the weighted luminance in importance_data,
// representing the amount of energy emitted through each texel.
// Also compute the average CIE luminance to drive the tonemapping of the final image
for(uint32_t y = 0; y < ry; ++y)
{
const float theta1 = static_cast<float>(y + 1) * step_theta;
const float cos_theta1 = std::cos(theta1);
const float area = (cos_theta0 - cos_theta1) * step_phi; // solid angle
cos_theta0 = cos_theta1;
for(uint32_t x = 0; x < rx; ++x)
{
const uint32_t idx = y * rx + x;
const uint32_t idx4 = idx * 4;
float cie_luminance = luminance(&pixels[idx4]);
importance_data[idx] = area * std::max(pixels[idx4], std::max(pixels[idx4 + 1], pixels[idx4 + 2]));
total += cie_luminance;
}
}
average = static_cast<float>(total) / static_cast<float>(rx * ry);
// Build the alias map, which aims at creating a set of texel couples
// so that all couples emit roughly the same amount of energy. To this aim,
// each smaller radiance texel will be assigned an "alias" with higher emitted radiance
// As a byproduct this function also returns the integral of the radiance emitted by the environment
integral = buildAliasmap(importance_data, env_accel);
// We deduce the PDF of each texel by normalizing its emitted radiance by the radiance integral
const float inv_env_integral = 1.0F / integral;
for(uint32_t i = 0; i < rx * ry; ++i)
{
const uint32_t idx4 = i * 4;
pixels[idx4 + 3] = std::max(pixels[idx4], std::max(pixels[idx4 + 1], pixels[idx4 + 2])) * inv_env_integral;
}
return env_accel;
}
} // namespace nvvkhl

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
//////////////////////////////////////////////////////////////////////////
#include <array>
#include <vector>
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/images_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
namespace nvvk {
class Context;
}
namespace nvvkhl {
/** @DOC_START
# class nvvkhl::HdrEnv
> Load an environment image (HDR) and create an acceleration structure for important light sampling.
@DOC_END */
class HdrEnv
{
public:
HdrEnv() = default;
HdrEnv(nvvk::Context* ctx, nvvk::ResourceAllocator* allocator, uint32_t queueFamilyIndex = 0U);
~HdrEnv() { destroy(); }
void setup(const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t familyIndex, nvvk::ResourceAllocator* allocator);
void loadEnvironment(const std::string& hrdImage);
void destroy();
float getIntegral() const { return m_integral; }
float getAverage() const { return m_average; }
bool isValid() const { return m_valid; }
inline VkDescriptorSetLayout getDescriptorSetLayout() { return m_descSetLayout; } // HDR + importance sampling
inline VkDescriptorSet getDescriptorSet() { return m_descSet; }
const nvvk::Texture& getHdrTexture() { return m_texHdr; } // The loaded HDR texture
VkExtent2D getHdrImageSize() { return m_hdrImageSize; }
private:
VkDevice m_device{VK_NULL_HANDLE};
uint32_t m_familyIndex{0};
nvvk::ResourceAllocator* m_alloc{nullptr};
nvvk::DebugUtil m_debug;
float m_integral{1.F};
float m_average{1.F};
bool m_valid{false};
VkExtent2D m_hdrImageSize{1, 1};
// Resources
nvvk::Texture m_texHdr;
nvvk::Buffer m_accelImpSmpl;
VkDescriptorPool m_descPool{VK_NULL_HANDLE};
VkDescriptorSetLayout m_descSetLayout{VK_NULL_HANDLE};
VkDescriptorSet m_descSet{VK_NULL_HANDLE};
void createDescriptorSetLayout();
};
} // namespace nvvkhl

View file

@ -0,0 +1,504 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#define _USE_MATH_DEFINES
#include <chrono>
#include <iostream>
#include <cmath>
#include <numeric>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "nvh/nvprint.hpp"
#include "nvvk/commands_vk.hpp"
#include "nvvk/descriptorsets_vk.hpp"
#include "nvvk/shaders_vk.hpp"
#include "nvvk/context_vk.hpp"
#include "hdr_env_dome.hpp"
#include "stb_image.h"
#include "nvvkhl/shaders/dh_comp.h"
#include "nvvkhl/shaders/dh_hdr.h"
#include "_autogen/hdr_dome.comp.h"
#include "_autogen/hdr_integrate_brdf.comp.h"
#include "_autogen/hdr_prefilter_diffuse.comp.h"
#include "_autogen/hdr_prefilter_glossy.comp.h"
#include "nvh/timesampler.hpp"
namespace nvvkhl {
HdrEnvDome::HdrEnvDome(nvvk::Context* ctx, nvvk::ResourceAllocator* allocator, uint32_t queueFamilyIndex)
{
setup(ctx->m_device, ctx->m_physicalDevice, queueFamilyIndex, allocator);
}
//--------------------------------------------------------------------------------------------------
//
//
void HdrEnvDome::setup(const VkDevice& device, const VkPhysicalDevice& /*physicalDevice*/, uint32_t familyIndex, nvvk::ResourceAllocator* allocator)
{
m_device = device;
m_alloc = allocator;
m_familyIndex = familyIndex;
m_debug.setup(device);
}
//--------------------------------------------------------------------------------------------------
// The descriptor set and layout are from HDR_ENV
// - it consists of the HDR image and the acceleration structure
// - those will be used to create the diffuse and glossy image
// - Also use to 'clear' the image with the background image
//
void HdrEnvDome::create(VkDescriptorSet dstSet, VkDescriptorSetLayout dstSetLayout)
{
destroy();
m_hdrEnvSet = dstSet;
m_hdrEnvLayout = dstSetLayout;
VkShaderModule diff_module = nvvk::createShaderModule(m_device, hdr_prefilter_diffuse_comp, sizeof(hdr_prefilter_diffuse_comp));
VkShaderModule gloss_module = nvvk::createShaderModule(m_device, hdr_prefilter_glossy_comp, sizeof(hdr_prefilter_glossy_comp));
createDrawPipeline();
integrateBrdf(512, m_textures.lutBrdf);
prefilterHdr(128, m_textures.diffuse, diff_module, false);
prefilterHdr(512, m_textures.glossy, gloss_module, true);
createDescriptorSetLayout();
m_debug.setObjectName(m_textures.lutBrdf.image, "HDR_BRDF");
m_debug.setObjectName(m_textures.diffuse.image, "HDR_Diffuse");
m_debug.setObjectName(m_textures.glossy.image, "HDR_Glossy");
vkDestroyShaderModule(m_device, diff_module, nullptr);
vkDestroyShaderModule(m_device, gloss_module, nullptr);
}
//--------------------------------------------------------------------------------------------------
// This is the image the HDR will be write to, a framebuffer image or an offsceen image
//
void HdrEnvDome::setOutImage(const VkDescriptorImageInfo& outimage)
{
VkWriteDescriptorSet wds{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
wds.dstSet = m_domeSet;
wds.dstBinding = nvvkhl_shaders::EnvDomeDraw::eHdrImage;
wds.descriptorCount = 1;
wds.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
wds.pImageInfo = &outimage;
vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Compute Pipeline to "Clear" the image with the HDR as background
//
void HdrEnvDome::createDrawPipeline()
{
// Descriptor: the output image
nvvk::DescriptorSetBindings bind;
bind.addBinding(nvvkhl_shaders::EnvDomeDraw::eHdrImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT);
m_domeLayout = bind.createLayout(m_device);
m_domePool = bind.createPool(m_device);
m_domeSet = nvvk::allocateDescriptorSet(m_device, m_domePool, m_domeLayout);
// Creating the pipeline layout
VkPushConstantRange push_constant_ranges = {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(nvvkhl_shaders::HdrDomePushConstant)};
std::vector<VkDescriptorSetLayout> layouts{m_domeLayout, m_hdrEnvLayout};
VkPipelineLayoutCreateInfo create_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
create_info.setLayoutCount = static_cast<uint32_t>(layouts.size());
create_info.pSetLayouts = layouts.data();
create_info.pushConstantRangeCount = 1;
create_info.pPushConstantRanges = &push_constant_ranges;
vkCreatePipelineLayout(m_device, &create_info, nullptr, &m_domePipelineLayout);
// HDR Dome compute shader
VkPipelineShaderStageCreateInfo stage_info{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage_info.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stage_info.module = nvvk::createShaderModule(m_device, hdr_dome_comp, sizeof(hdr_dome_comp));
stage_info.pName = "main";
VkComputePipelineCreateInfo comp_info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
comp_info.layout = m_domePipelineLayout;
comp_info.stage = stage_info;
vkCreateComputePipelines(m_device, {}, 1, &comp_info, nullptr, &m_domePipeline);
NAME_VK(m_domePipeline);
// Clean up
vkDestroyShaderModule(m_device, comp_info.stage.module, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Draw the HDR to the image (setOutImage)
// - view and projection matrix should come from the camera
// - size is the image output size (framebuffer size)
// - color is the color multiplier of the HDR (intensity)
//
void HdrEnvDome::draw(const VkCommandBuffer& cmdBuf,
const glm::mat4& view,
const glm::mat4& proj,
const VkExtent2D& size,
const float* color,
float rotation /*=0.f*/)
{
LABEL_SCOPE_VK(cmdBuf);
// Information to the compute shader
nvvkhl_shaders::HdrDomePushConstant pc{};
pc.mvp = glm::inverse(view) * glm::inverse(proj); // This will be to have a world direction vector pointing to the pixel
pc.multColor = glm::vec4(*color);
pc.rotation = rotation;
// Execution
std::vector<VkDescriptorSet> dst_sets{m_domeSet, m_hdrEnvSet};
vkCmdPushConstants(cmdBuf, m_domePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0,
sizeof(nvvkhl_shaders::HdrDomePushConstant), &pc);
vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_domePipelineLayout, 0,
static_cast<uint32_t>(dst_sets.size()), dst_sets.data(), 0, nullptr);
vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_domePipeline);
VkExtent2D group_counts = getGroupCounts(size);
vkCmdDispatch(cmdBuf, group_counts.width, group_counts.height, 1);
}
//--------------------------------------------------------------------------------------------------
//
//
void HdrEnvDome::destroy()
{
m_alloc->destroy(m_textures.diffuse);
m_alloc->destroy(m_textures.lutBrdf);
m_alloc->destroy(m_textures.glossy);
vkDestroyPipeline(m_device, m_domePipeline, nullptr);
vkDestroyPipelineLayout(m_device, m_domePipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_domeLayout, nullptr);
vkDestroyDescriptorPool(m_device, m_domePool, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_hdrLayout, nullptr);
vkDestroyDescriptorPool(m_device, m_hdrPool, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Descriptors of the HDR and the acceleration structure
//
void HdrEnvDome::createDescriptorSetLayout()
{
nvvk::DescriptorSetBindings bind;
VkShaderStageFlags flags = VK_SHADER_STAGE_ALL;
bind.addBinding(nvvkhl_shaders::EnvDomeBindings::eHdrBrdf, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, flags); // HDR image
bind.addBinding(nvvkhl_shaders::EnvDomeBindings::eHdrDiffuse, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, flags); // HDR image
bind.addBinding(nvvkhl_shaders::EnvDomeBindings::eHdrSpecular, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, flags); // HDR image
m_hdrPool = bind.createPool(m_device, 1);
CREATE_NAMED_VK(m_hdrLayout, bind.createLayout(m_device));
CREATE_NAMED_VK(m_hdrSet, nvvk::allocateDescriptorSet(m_device, m_hdrPool, m_hdrLayout));
std::vector<VkWriteDescriptorSet> writes;
writes.emplace_back(bind.makeWrite(m_hdrSet, nvvkhl_shaders::EnvDomeBindings::eHdrBrdf, &m_textures.lutBrdf.descriptor));
writes.emplace_back(bind.makeWrite(m_hdrSet, nvvkhl_shaders::EnvDomeBindings::eHdrDiffuse, &m_textures.diffuse.descriptor));
writes.emplace_back(bind.makeWrite(m_hdrSet, nvvkhl_shaders::EnvDomeBindings::eHdrSpecular, &m_textures.glossy.descriptor));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Pre-integrate glossy BRDF, see
// http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
void HdrEnvDome::integrateBrdf(uint32_t dimension, nvvk::Texture& target)
{
nvh::ScopedTimer st(__FUNCTION__);
// Create an image RG16 to store the BRDF
VkImageCreateInfo image_ci = nvvk::makeImage2DCreateInfo({dimension, dimension}, VK_FORMAT_R16G16_SFLOAT,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
nvvk::Image image = m_alloc->createImage(image_ci);
VkImageViewCreateInfo image_view_info = nvvk::makeImageViewCreateInfo(image.image, image_ci);
VkSamplerCreateInfo sampler_ci = nvvk::makeSamplerCreateInfo();
target = m_alloc->createTexture(image, image_view_info, sampler_ci);
target.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
// Compute shader
VkDescriptorSet dst_set{VK_NULL_HANDLE};
VkDescriptorSetLayout dst_layout{VK_NULL_HANDLE};
VkDescriptorPool dst_pool{VK_NULL_HANDLE};
VkPipeline pipeline{VK_NULL_HANDLE};
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
{
nvvk::ScopeCommandBuffer cmd_buf(m_device, m_familyIndex);
LABEL_SCOPE_VK(cmd_buf);
// Change image layout to general
nvvk::cmdBarrierImageLayout(cmd_buf, target.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
// The output image is the one we have just created
nvvk::DescriptorSetBindings bind;
bind.addBinding(nvvkhl_shaders::EnvDomeDraw::eHdrImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT);
dst_layout = bind.createLayout(m_device);
dst_pool = bind.createPool(m_device);
dst_set = nvvk::allocateDescriptorSet(m_device, dst_pool, dst_layout);
std::vector<VkWriteDescriptorSet> writes;
writes.emplace_back(bind.makeWrite(dst_set, 0, &target.descriptor));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
// Creating the pipeline
VkPipelineLayoutCreateInfo create_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
create_info.setLayoutCount = 1;
create_info.pSetLayouts = &dst_layout;
vkCreatePipelineLayout(m_device, &create_info, nullptr, &pipeline_layout);
NAME_VK(pipeline_layout);
VkPipelineShaderStageCreateInfo stage_info{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage_info.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stage_info.module = nvvk::createShaderModule(m_device, hdr_integrate_brdf_comp, sizeof(hdr_integrate_brdf_comp));
stage_info.pName = "main";
VkComputePipelineCreateInfo comp_info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
comp_info.layout = pipeline_layout;
comp_info.stage = stage_info;
vkCreateComputePipelines(m_device, {}, 1, &comp_info, nullptr, &pipeline);
vkDestroyShaderModule(m_device, comp_info.stage.module, nullptr);
// Run shader
vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &dst_set, 0, nullptr);
vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
VkExtent2D group_counts = getGroupCounts({dimension, dimension});
vkCmdDispatch(cmd_buf, group_counts.width, group_counts.height, 1);
}
// Clean up
vkDestroyDescriptorSetLayout(m_device, dst_layout, nullptr);
vkDestroyDescriptorPool(m_device, dst_pool, nullptr);
vkDestroyPipeline(m_device, pipeline, nullptr);
vkDestroyPipelineLayout(m_device, pipeline_layout, nullptr);
}
//--------------------------------------------------------------------------------------------------
//
//
void HdrEnvDome::prefilterHdr(uint32_t dim, nvvk::Texture& target, const VkShaderModule& module, bool doMipmap)
{
const VkExtent2D size{dim, dim};
VkFormat format = VK_FORMAT_R16G16B16A16_SFLOAT;
const uint32_t num_mips = doMipmap ? static_cast<uint32_t>(floor(::log2(dim))) + 1 : 1;
nvh::ScopedTimer st("%s: %u", __FUNCTION__, num_mips);
VkSamplerCreateInfo sampler_create_info = nvvk::makeSamplerCreateInfo();
sampler_create_info.maxLod = static_cast<float>(num_mips);
{ // Target - cube
VkImageCreateInfo image_create_info = nvvk::makeImageCubeCreateInfo(
size, format, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, doMipmap);
nvvk::Image image = m_alloc->createImage(image_create_info);
VkImageViewCreateInfo image_view_info = nvvk::makeImageViewCreateInfo(image.image, image_create_info, true);
target = m_alloc->createTexture(image, image_view_info, sampler_create_info);
target.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
}
nvvk::Texture scratch_texture;
{ // Scratch texture
VkImageCreateInfo image_ci = nvvk::makeImage2DCreateInfo(size, format, VK_IMAGE_USAGE_STORAGE_BIT);
nvvk::Image image = m_alloc->createImage(image_ci);
VkImageViewCreateInfo image_view_info = nvvk::makeImageViewCreateInfo(image.image, image_ci);
VkSamplerCreateInfo sampler_ci = nvvk::makeSamplerCreateInfo();
scratch_texture = m_alloc->createTexture(image, image_view_info, sampler_ci);
scratch_texture.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
}
// Compute shader
VkDescriptorSet dst_set{VK_NULL_HANDLE};
VkDescriptorSetLayout dst_layout{VK_NULL_HANDLE};
VkDescriptorPool dst_pool{VK_NULL_HANDLE};
VkPipeline pipeline{VK_NULL_HANDLE};
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
// Descriptors
nvvk::DescriptorSetBindings bind;
bind.addBinding(nvvkhl_shaders::EnvDomeDraw::eHdrImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT);
dst_layout = bind.createLayout(m_device);
dst_pool = bind.createPool(m_device);
dst_set = nvvk::allocateDescriptorSet(m_device, dst_pool, dst_layout);
std::vector<VkWriteDescriptorSet> writes;
writes.emplace_back(bind.makeWrite(dst_set, 0, &scratch_texture.descriptor)); // Writing to scratch
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
// Creating the pipeline
VkPushConstantRange push_constant_range{VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(nvvkhl_shaders::HdrPushBlock)};
std::vector<VkDescriptorSetLayout> layouts{dst_layout, m_hdrEnvLayout};
VkPipelineLayoutCreateInfo create_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
create_info.setLayoutCount = static_cast<uint32_t>(layouts.size());
create_info.pSetLayouts = layouts.data();
create_info.pushConstantRangeCount = 1;
create_info.pPushConstantRanges = &push_constant_range;
vkCreatePipelineLayout(m_device, &create_info, nullptr, &pipeline_layout);
VkPipelineShaderStageCreateInfo stage_info{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage_info.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stage_info.module = module;
stage_info.pName = "main";
VkComputePipelineCreateInfo comp_info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
comp_info.layout = pipeline_layout;
comp_info.stage = stage_info;
vkCreateComputePipelines(m_device, {}, 1, &comp_info, nullptr, &pipeline);
{
nvvk::ScopeCommandBuffer cmd_buf(m_device, m_familyIndex);
// Change scratch to general
nvvk::cmdBarrierImageLayout(cmd_buf, scratch_texture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
// Change target to destination
VkImageSubresourceRange subresource_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, num_mips, 0, 6};
nvvk::cmdBarrierImageLayout(cmd_buf, target.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresource_range);
std::vector<VkDescriptorSet> dst_sets{dst_set, m_hdrEnvSet};
vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0,
static_cast<uint32_t>(dst_sets.size()), dst_sets.data(), 0, nullptr);
vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
renderToCube(cmd_buf, target, scratch_texture, pipeline_layout, dim, num_mips);
}
// Clean up
vkDestroyDescriptorSetLayout(m_device, dst_layout, nullptr);
vkDestroyDescriptorPool(m_device, dst_pool, nullptr);
vkDestroyPipeline(m_device, pipeline, nullptr);
vkDestroyPipelineLayout(m_device, pipeline_layout, nullptr);
m_alloc->destroy(scratch_texture);
}
//--------------------------------------------------------------------------------------------------
//
//
void HdrEnvDome::renderToCube(const VkCommandBuffer& cmdBuf,
nvvk::Texture& target,
nvvk::Texture& scratch,
VkPipelineLayout pipelineLayout,
uint32_t dim,
const uint32_t numMips)
{
LABEL_SCOPE_VK(cmdBuf);
glm::mat4 mat_pers = glm::perspectiveRH_ZO(glm::radians(90.0F), 1.0F, 0.1F, 10.0F);
mat_pers[1][1] *= -1.0F;
mat_pers = glm::inverse(mat_pers);
std::array<glm::mat4, 6> mv;
const glm::vec3 pos(0.0F, 0.0F, 0.0F);
mv[0] = glm::lookAt(pos, glm::vec3(1.0F, 0.0F, 0.0F), glm::vec3(0.0F, -1.0F, 0.0F)); // Positive X
mv[1] = glm::lookAt(pos, glm::vec3(-1.0F, 0.0F, 0.0F), glm::vec3(0.0F, -1.0F, 0.0F)); // Negative X
mv[2] = glm::lookAt(pos, glm::vec3(0.0F, -1.0F, 0.0F), glm::vec3(0.0F, 0.0F, -1.0F)); // Positive Y
mv[3] = glm::lookAt(pos, glm::vec3(0.0F, 1.0F, 0.0F), glm::vec3(0.0F, 0.0F, 1.0F)); // Negative Y
mv[4] = glm::lookAt(pos, glm::vec3(0.0F, 0.0F, 1.0F), glm::vec3(0.0F, -1.0F, 0.0F)); // Positive Z
mv[5] = glm::lookAt(pos, glm::vec3(0.0F, 0.0F, -1.0F), glm::vec3(0.0F, -1.0F, 0.0F)); // Negative Z
for(auto& m : mv)
m = glm::inverse(m);
// Change image layout for all cubemap faces to transfer destination
VkImageSubresourceRange subresource_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, numMips, 0, 6};
nvvk::cmdBarrierImageLayout(cmdBuf, target.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresource_range);
// Image barrier for compute stage
auto barrier = [&](VkImageLayout oldLayout, VkImageLayout newLayout) {
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkImageMemoryBarrier image_memory_barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
image_memory_barrier.oldLayout = oldLayout;
image_memory_barrier.newLayout = newLayout;
image_memory_barrier.image = scratch.image;
image_memory_barrier.subresourceRange = range;
image_memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
VkPipelineStageFlags dest_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
vkCmdPipelineBarrier(cmdBuf, src_stage_mask, dest_stage_mask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
};
VkExtent3D extent{dim, dim, 1};
nvvkhl_shaders::HdrPushBlock push_block{};
for(uint32_t mip = 0; mip < numMips; mip++)
{
for(uint32_t f = 0; f < 6; f++)
{
// Update shader push constant block
float roughness = static_cast<float>(mip) / static_cast<float>(numMips - 1);
push_block.roughness = roughness;
push_block.mvp = mv[f] * mat_pers;
push_block.size = glm::vec2(glm::uvec2(extent.width, extent.height));
push_block.numSamples = 1024 / (mip + 1);
vkCmdPushConstants(cmdBuf, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(nvvkhl_shaders::HdrPushBlock), &push_block);
// Execute compute shader
VkExtent2D group_counts = getGroupCounts({extent.width, extent.height});
vkCmdDispatch(cmdBuf, group_counts.width, group_counts.height, 1);
// Wait for compute to finish before copying
barrier(VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
// Copy region for transfer from framebuffer to cube face
VkImageCopy copy_region{};
copy_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_region.srcSubresource.baseArrayLayer = 0;
copy_region.srcSubresource.mipLevel = 0;
copy_region.srcSubresource.layerCount = 1;
copy_region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_region.dstSubresource.baseArrayLayer = f;
copy_region.dstSubresource.mipLevel = mip;
copy_region.dstSubresource.layerCount = 1;
copy_region.extent = extent;
vkCmdCopyImage(cmdBuf, scratch.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, target.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
// Transform scratch texture back to general
barrier(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
}
// Next mipmap level
if(extent.width > 1)
extent.width /= 2;
if(extent.height > 1)
extent.height /= 2;
}
nvvk::cmdBarrierImageLayout(cmdBuf, target.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, subresource_range);
}
} // namespace nvvkhl

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
//////////////////////////////////////////////////////////////////////////
#include <array>
#include <vector>
#include <glm/glm.hpp>
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/images_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
namespace nvvkhl {
class Context;
/** @DOC_START
# class nvvkhl::HdrEnvDome
> Use an environment image (HDR) and create the cubic textures for glossy reflection and diffuse illumination. It also has the ability to render the HDR environment, in the background of an image.
Using 4 compute shaders
- hdr_dome: to make the HDR as background
- hdr_integrate_brdf : generate the BRDF lookup table
- hdr_prefilter_diffuse : integrate the diffuse contribution in a cubemap
- hdr_prefilter_glossy : integrate the glossy reflection in a cubemap
@DOC_END */
class HdrEnvDome
{
public:
HdrEnvDome() = default;
HdrEnvDome(nvvk::Context* ctx, nvvk::ResourceAllocator* allocator, uint32_t queueFamilyIndex = 0U);
~HdrEnvDome() { destroy(); }
void setup(const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t familyIndex, nvvk::ResourceAllocator* allocator);
void create(VkDescriptorSet dstSet, VkDescriptorSetLayout dstSetLayout);
void setOutImage(const VkDescriptorImageInfo& outimage);
void draw(const VkCommandBuffer& cmdBuf, const glm::mat4& view, const glm::mat4& proj, const VkExtent2D& size, const float* color, float rotation = 0.F);
void destroy();
inline VkDescriptorSetLayout getDescLayout() const { return m_hdrLayout; }
inline VkDescriptorSet getDescSet() const { return m_hdrSet; }
private:
// Resources
VkDevice m_device{VK_NULL_HANDLE};
uint32_t m_familyIndex{0};
nvvk::ResourceAllocator* m_alloc{nullptr};
nvvk::DebugUtil m_debug;
// From HdrEnv
VkDescriptorSet m_hdrEnvSet{VK_NULL_HANDLE};
VkDescriptorSetLayout m_hdrEnvLayout{VK_NULL_HANDLE};
// To draw the HDR in image
VkDescriptorSet m_domeSet{VK_NULL_HANDLE};
VkDescriptorSetLayout m_domeLayout{VK_NULL_HANDLE};
VkDescriptorPool m_domePool{VK_NULL_HANDLE};
VkPipeline m_domePipeline{VK_NULL_HANDLE};
VkPipelineLayout m_domePipelineLayout{VK_NULL_HANDLE};
VkDescriptorSet m_hdrSet{VK_NULL_HANDLE};
VkDescriptorSetLayout m_hdrLayout{VK_NULL_HANDLE};
VkDescriptorPool m_hdrPool{VK_NULL_HANDLE};
struct Textures
{
nvvk::Texture lutBrdf;
nvvk::Texture diffuse;
nvvk::Texture glossy;
} m_textures;
void createDescriptorSetLayout();
void createDrawPipeline();
void integrateBrdf(uint32_t dimension, nvvk::Texture& target);
void prefilterHdr(uint32_t dim, nvvk::Texture& target, const VkShaderModule& module, bool doMipmap);
void renderToCube(const VkCommandBuffer& cmdBuf,
nvvk::Texture& target,
nvvk::Texture& scratch,
VkPipelineLayout pipelineLayout,
uint32_t dim,
uint32_t numMips);
};
} // namespace nvvkhl

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
namespace nvvkhl {
/** @DOC_START
# struct nvvkhl::PipelineContainer
> Small multi-pipeline container
@DOC_END */
struct PipelineContainer
{
std::vector<VkPipeline> plines;
VkPipelineLayout layout{VK_NULL_HANDLE};
void destroy(VkDevice device)
{
for(auto& p : plines)
{
vkDestroyPipeline(device, p, nullptr);
}
vkDestroyPipelineLayout(device, layout, nullptr);
plines.clear();
layout = VK_NULL_HANDLE;
}
};
} // namespace nvvkhl

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
// Various Application utilities
// - Display a menu with File/Quit
// - Display basic information in the window title
#include <filesystem>
#include "imgui/imgui_camera_widget.h"
#include "nvh/cameramanipulator.hpp"
#include "nvh/gltfscene.hpp"
namespace nvvkhl {
/** @DOC_START
# Function nvvkhl::setCameraFromScene
> Set the camera from the scene, if no camera is found, it will fit the camera to the scene.
@DOC_END */
static void setCameraFromScene(const std::string& m_filename, const nvh::GltfScene& m_scene)
{
ImGuiH::SetCameraJsonFile(std::filesystem::path(m_filename).stem().string());
if(!m_scene.m_cameras.empty())
{
const auto& c = m_scene.m_cameras[0];
CameraManip.setCamera({c.eye, c.center, c.up, static_cast<float>(glm::degrees(c.cam.perspective.yfov))});
ImGuiH::SetHomeCamera({c.eye, c.center, c.up, static_cast<float>(glm::degrees(c.cam.perspective.yfov))});
for(const auto& cam : m_scene.m_cameras)
{
ImGuiH::AddCamera({cam.eye, cam.center, cam.up, static_cast<float>(glm::degrees(cam.cam.perspective.yfov))});
}
}
else
{
// Re-adjusting camera to fit the new scene
CameraManip.fit(m_scene.m_dimensions.min, m_scene.m_dimensions.max, true);
ImGuiH::SetHomeCamera(CameraManip.getCamera());
}
CameraManip.setClipPlanes(glm::vec2(0.001F * m_scene.m_dimensions.radius, 100.0F * m_scene.m_dimensions.radius));
}
} // namespace nvvkhl

View file

@ -0,0 +1,63 @@
## Table of Contents
- [bsdf_functions.h](#bsdf_functionsh)
- [bsdf_structs.h](#bsdf_structsh)
- [dh_comp.h](#dh_comph)
- [dh_inspector.h](#dh_inspectorh)
- [dh_sky.h](#dh_skyh)
- [dh_tonemap.h](#dh_tonemaph)
## bsdf_functions.h
### Function absorptionCoefficient
> Compute the absorption coefficient of the material
### Function bsdfEvaluate
> Evaluate the BSDF for the given material
### Function bsdfSample
> Sample the BSDF for the given material
## bsdf_structs.h
### struct BsdfEvaluateData
> Data structure for evaluating a BSDF
### struct BsdfSampleData
> Data structure for sampling a BSDF
## dh_comp.h
### Function getGroupCounts
> Returns the number of workgroups needed to cover the size
This function is used to calculate the number of workgroups needed to cover a given size. It is used in the compute shader to calculate the number of workgroups needed to cover the size of the image.
## dh_inspector.h
### Function inspect32BitValue
> Inspect a 32-bit value at a given index
### Function inspectCustom32BitValue
> Inspect a 32-bit value at a given index
## dh_sky.h
### Function initSkyShaderParameters
> Initializes the sky shader parameters with default values
### Function proceduralSky
> Return the color of the procedural sky shader
## dh_tonemap.h
### Function tonemapFilmic
> Filmic tonemapping operator
http://filmicworlds.com/blog/filmic-tonemapping-operators/
### Function gammaCorrection
> Gamma correction
see https://www.teamten.com/lawrence/graphics/gamma/
### Function tonemapUncharted
> Uncharted tone map
see: http://filmicworlds.com/blog/filmic-tonemapping-operators/

View file

@ -0,0 +1,283 @@
/*
* Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "func.glsl" // cosineSampleHemisphere
#include "ggx.glsl" // brdfLambertian, ..
#include "bsdf_structs.h" // Bsdf*,
#include "pbr_mat_struct.h" // PbrMaterial
/** @DOC_START
# Function absorptionCoefficient
> Compute the absorption coefficient of the material
@DOC_END */
vec3 absorptionCoefficient(in PbrMaterial mat)
{
float tmp1 = mat.attenuationDistance;
return tmp1 <= 0.0F ? vec3(0.0F, 0.0F, 0.0F) :
-vec3(log(mat.attenuationColor.x), log(mat.attenuationColor.y), log(mat.attenuationColor.z)) / tmp1;
}
struct EvalData
{
float pdf;
vec3 bsdf;
};
void evalDiffuse(in BsdfEvaluateData data, in PbrMaterial mat, out EvalData eval)
{
// Diffuse reflection
float NdotL = clampedDot(mat.normal, data.k2);
eval.bsdf = brdfLambertian(mat.albedo.rgb, mat.metallic) * NdotL;
eval.pdf = M_1_OVER_PI;
}
void evalSpecular(in BsdfEvaluateData data, in PbrMaterial mat, out EvalData eval)
{
// Specular reflection
vec3 H = normalize(data.k1 + data.k2);
float alphaRoughness = mat.roughness * mat.roughness;
float NdotV = clampedDot(mat.normal, data.k1);
float NdotL = clampedDot(mat.normal, data.k2);
float VdotH = clampedDot(data.k1, H);
float NdotH = clampedDot(mat.normal, H);
float LdotH = clampedDot(data.k2, H);
vec3 f_specular = brdfSpecularGGX(mat.f0, mat.f90, alphaRoughness, VdotH, NdotL, NdotV, NdotH);
eval.bsdf = f_specular * NdotL;
eval.pdf = distributionGGX(NdotH, alphaRoughness) * NdotH / (4.0F * LdotH);
}
void evalTransmission(in BsdfEvaluateData data, in PbrMaterial mat, out EvalData eval)
{
eval.pdf = 0;
eval.bsdf = vec3(0.0F, 0.0F, 0.0F);
if(mat.transmissionFactor <= 0.0F)
return;
vec3 refractedDir;
bool totalInternalRefraction = refract(data.k2, mat.normal, mat.eta, refractedDir);
if(!totalInternalRefraction)
{
//eval.bsdf = mat.albedo.rgb * mat.transmissionFactor;
eval.pdf = abs(dot(refractedDir, mat.normal));
}
}
/** @DOC_START
# Function bsdfEvaluate
> Evaluate the BSDF for the given material.
@DOC_END */
void bsdfEvaluate(inout BsdfEvaluateData data, in PbrMaterial mat)
{
// Initialization
float diffuseRatio = 0.5F * (1.0F - mat.metallic);
float specularRatio = 1.0F - diffuseRatio;
float transmissionRatio = (1.0F - mat.metallic) * mat.transmissionFactor;
// Contribution
EvalData f_diffuse;
EvalData f_specular;
EvalData f_transmission;
evalDiffuse(data, mat, f_diffuse);
evalSpecular(data, mat, f_specular);
evalTransmission(data, mat, f_transmission);
// Combine the results
float brdfPdf = 0;
brdfPdf += f_diffuse.pdf * diffuseRatio;
brdfPdf += f_specular.pdf * specularRatio;
brdfPdf = mix(brdfPdf, f_transmission.pdf, transmissionRatio);
vec3 bsdfDiffuse = mix(f_diffuse.bsdf, f_transmission.bsdf, transmissionRatio);
vec3 bsdfGlossy = mix(f_specular.bsdf, f_transmission.bsdf, transmissionRatio);
// Return results
data.bsdf_diffuse = bsdfDiffuse;
data.bsdf_glossy = bsdfGlossy;
data.pdf = brdfPdf;
}
//-------------------------------------------------------------------------------------------------
vec3 sampleDiffuse(inout BsdfSampleData data, in PbrMaterial mat)
{
vec3 surfaceNormal = mat.normal;
vec3 tangent, bitangent;
orthonormalBasis(surfaceNormal, tangent, bitangent);
float r1 = data.xi.x;
float r2 = data.xi.y;
vec3 sampleDirection = cosineSampleHemisphere(r1, r2); // Diffuse
sampleDirection = tangent * sampleDirection.x + bitangent * sampleDirection.y + surfaceNormal * sampleDirection.z;
data.event_type = BSDF_EVENT_DIFFUSE;
return sampleDirection;
}
vec3 sampleSpecular(inout BsdfSampleData data, in PbrMaterial mat)
{
vec3 surfaceNormal = mat.normal;
vec3 tangent, bitangent;
orthonormalBasis(surfaceNormal, tangent, bitangent);
float alphaRoughness = mat.roughness * mat.roughness;
float r1 = data.xi.x;
float r2 = data.xi.y;
vec3 halfVector = ggxSampling(alphaRoughness, r1, r2); // Glossy
halfVector = tangent * halfVector.x + bitangent * halfVector.y + surfaceNormal * halfVector.z;
vec3 sampleDirection = reflect(-data.k1, halfVector);
data.event_type = BSDF_EVENT_SPECULAR;
return sampleDirection;
}
vec3 sampleThinTransmission(in BsdfSampleData data, in PbrMaterial mat)
{
vec3 incomingDir = data.k1;
float r1 = data.xi.x;
float r2 = data.xi.y;
float alphaRoughness = mat.roughness * mat.roughness;
vec3 halfVector = ggxSampling(alphaRoughness, r1, r2);
vec3 tangent, bitangent;
orthonormalBasis(incomingDir, tangent, bitangent);
vec3 transformedHalfVector = tangent * halfVector.x + bitangent * halfVector.y + incomingDir * halfVector.z;
vec3 refractedDir = -transformedHalfVector;
return refractedDir;
}
vec3 sampleSolidTransmission(inout BsdfSampleData data, in PbrMaterial mat, out bool refracted)
{
vec3 surfaceNormal = mat.normal;
if(mat.roughness > 0.0F)
{
vec3 tangent, bitangent;
orthonormalBasis(surfaceNormal, tangent, bitangent);
float alphaRoughness = mat.roughness * mat.roughness;
float r1 = data.xi.x;
float r2 = data.xi.y;
vec3 halfVector = ggxSampling(alphaRoughness, r1, r2); // Glossy
halfVector = tangent * halfVector.x + bitangent * halfVector.y + surfaceNormal * halfVector.z;
surfaceNormal = halfVector;
}
vec3 refractedDir;
refracted = refract(-data.k1, surfaceNormal, mat.eta, refractedDir);
return refractedDir;
}
void sampleTransmission(inout BsdfSampleData data, in PbrMaterial mat)
{
// Calculate transmission direction using Snell's law
vec3 refractedDir;
vec3 sampleDirection;
bool refracted = true;
float r4 = data.xi.w;
// Thin film approximation
if(mat.thicknessFactor == 0.0F && mat.roughness > 0.0F)
{
refractedDir = sampleThinTransmission(data, mat);
}
else
{
refractedDir = sampleSolidTransmission(data, mat, refracted);
}
// Fresnel term
float VdotH = dot(data.k1, mat.normal);
vec3 reflectance = fresnelSchlick(mat.f0, mat.f90, VdotH);
vec3 surfaceNormal = mat.normal;
if(!refracted || r4 < luminance(reflectance))
{
// Total internal reflection or reflection based on Fresnel term
sampleDirection = reflect(-data.k1, surfaceNormal); // Reflective direction
data.event_type = BSDF_EVENT_SPECULAR;
}
else
{
// Transmission
sampleDirection = refractedDir;
data.event_type = BSDF_EVENT_TRANSMISSION;
}
// Attenuate albedo for transmission
vec3 bsdf = mat.albedo.rgb; // * mat.transmissionFactor;
// Result
data.bsdf_over_pdf = bsdf;
data.pdf = abs(dot(surfaceNormal, sampleDirection)); //transmissionRatio;
data.k2 = sampleDirection;
}
/** @DOC_START
# Function bsdfSample
> Sample the BSDF for the given material
@DOC_END */
void bsdfSample(inout BsdfSampleData data, in PbrMaterial mat)
{
// Random numbers for importance sampling
float r3 = data.xi.z;
// Initialization
float diffuseRatio = 0.5F * (1.0F - mat.metallic);
float specularRatio = 1.0F - diffuseRatio;
float transmissionRatio = (1.0F - mat.metallic) * mat.transmissionFactor;
// Calculate if the ray goes through
if(r3 < transmissionRatio)
{
sampleTransmission(data, mat);
return;
}
// Choose between diffuse and glossy reflection
vec3 sampleDirection = vec3(0.0F, 0.0F, 0.0F);
if(r3 < diffuseRatio)
{
sampleDirection = sampleDiffuse(data, mat);
}
else
{
// Specular roughness
sampleDirection = sampleSpecular(data, mat);
}
// Evaluate the reflection coefficient with the new ray direction
BsdfEvaluateData evalData;
evalData.k1 = data.k1;
evalData.k2 = sampleDirection;
bsdfEvaluate(evalData, mat);
// Return values
data.pdf = evalData.pdf;
data.bsdf_over_pdf = (evalData.bsdf_diffuse + evalData.bsdf_glossy) / data.pdf;
data.k2 = sampleDirection;
// Avoid internal reflection
if(data.pdf <= 0.00001F || any(isnan(data.bsdf_over_pdf)))
data.event_type = BSDF_EVENT_ABSORB;
return;
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef BSDF_STRUCTS_H
#define BSDF_STRUCTS_H 1
#define BSDF_EVENT_ABSORB 0 // 0
#define BSDF_EVENT_DIFFUSE 1 // 1
#define BSDF_EVENT_GLOSSY (1 << 1) // 2
#define BSDF_EVENT_SPECULAR (1 << 2) // 4
#define BSDF_EVENT_REFLECTION (1 << 3) // 8
#define BSDF_EVENT_TRANSMISSION (1 << 4) // 16
#define BSDF_EVENT_DIFFUSE_REFLECTION (BSDF_EVENT_DIFFUSE | BSDF_EVENT_REFLECTION) // 9
#define BSDF_EVENT_DIFFUSE_TRANSMISSION (BSDF_EVENT_DIFFUSE | BSDF_EVENT_TRANSMISSION) // 17
#define BSDF_EVENT_GLOSSY_REFLECTION (BSDF_EVENT_GLOSSY | BSDF_EVENT_REFLECTION) // 10
#define BSDF_EVENT_GLOSSY_TRANSMISSION (BSDF_EVENT_GLOSSY | BSDF_EVENT_TRANSMISSION) // 18
#define BSDF_EVENT_SPECULAR_REFLECTION (BSDF_EVENT_SPECULAR | BSDF_EVENT_REFLECTION) // 12
#define BSDF_EVENT_SPECULAR_TRANSMISSION (BSDF_EVENT_SPECULAR | BSDF_EVENT_TRANSMISSION) // 20
#define BSDF_USE_MATERIAL_IOR (-1.0)
/** @DOC_START
# struct BsdfEvaluateData
> Data structure for evaluating a BSDF
@DOC_END */
struct BsdfEvaluateData
{
vec3 k1; // [in] Toward the incoming ray
vec3 k2; // [in] Toward the sampled light
vec3 bsdf_diffuse; // [out] Diffuse contribution
vec3 bsdf_glossy; // [out] Specular contribution
float pdf; // [out] PDF
};
/** @DOC_START
# struct BsdfSampleData
> Data structure for sampling a BSDF
@DOC_END */
struct BsdfSampleData
{
vec3 k1; // [in] Toward the incoming ray
vec3 k2; // [in] Toward the sampled light
vec4 xi; // [in] 4 random [0..1]
float pdf; // [out] PDF
vec3 bsdf_over_pdf; // [out] contribution / PDF
int event_type; // [out] one of the event above
};
#endif

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef CONSTANTS_GLSL
#define CONSTANTS_GLSL
precision highp float;
const float M_PI = 3.1415926535897F; // PI
const float M_TWO_PI = 6.2831853071795F; // 2*PI
const float M_PI_2 = 1.5707963267948F; // PI/2
const float M_PI_4 = 0.7853981633974F; // PI/4
const float M_1_OVER_PI = 0.3183098861837F; // 1/PI
const float M_2_OVER_PI = 0.6366197723675F; // 2/PI
const float INFINITE = 1e32F;
#endif // CONSTANTS_GLSL

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef DH_COMP_H
#define DH_COMP_H 1
#define WORKGROUP_SIZE 16 // Grid size used by compute shaders
#ifdef __cplusplus
/** @DOC_START
# Function getGroupCounts
> Returns the number of workgroups needed to cover the size
This function is used to calculate the number of workgroups needed to cover a given size. It is used in the compute shader to calculate the number of workgroups needed to cover the size of the image.
@DOC_END */
inline VkExtent2D getGroupCounts(const VkExtent2D& size, int workgroupSize = WORKGROUP_SIZE)
{
return VkExtent2D{(size.width + (workgroupSize - 1)) / workgroupSize, (size.height + (workgroupSize - 1)) / workgroupSize};
}
#endif // __cplusplus
#endif // DH_COMP_H

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/// @DOC_SKIP
#ifndef DH_HDR_H
#define DH_HDR_H 1
#ifndef WORKGROUP_SIZE
#define WORKGROUP_SIZE 16 // Grid size used by compute shaders
#endif
#ifdef __cplusplus
#include <glm/glm.hpp>
#include <cstdint>
namespace nvvkhl_shaders {
using uint = uint32_t;
using mat4 = glm::mat4;
using vec4 = glm::vec4;
using vec2 = glm::vec2;
#endif
// Environment acceleration structure - computed in hdr_env
struct EnvAccel
{
uint alias;
float q;
};
struct HdrPushBlock
{
mat4 mvp;
vec2 size;
float roughness;
uint numSamples;
};
struct HdrDomePushConstant
{
mat4 mvp;
vec4 multColor;
float rotation;
};
// clang-format off
#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL
#define START_BINDING(a) enum a {
#define END_BINDING() }
#define INLINE inline
#else
#define START_BINDING(a) const uint
#define END_BINDING()
#define INLINE
#endif
START_BINDING(EnvBindings)
eHdr = 0,
eImpSamples = 1
END_BINDING();
START_BINDING(EnvDomeBindings)
eHdrBrdf = 0,
eHdrDiffuse = 1,
eHdrSpecular = 2
END_BINDING();
START_BINDING(EnvDomeDraw)
eHdrImage = 0
END_BINDING();
// clang-format on
#ifdef __cplusplus
} // namespace nvvkhl_shaders
#endif
#endif // DH_HDR_H

View file

@ -0,0 +1,240 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
// Shader header for inspection shader variables
// Prior to including this header the following macros need to be defined
// Either INSPECTOR_MODE_COMPUTE or INSPECTOR_MODE_FRAGMENT
// If INSPECTOR_MODE_COMPUTE is defined the shader must expose invocation information (e.g. gl_LocalInvocationID).
// This typically applies to compute, task and mesh shaders
// If INSPECTOR_MODE_FRAGMENT is defined the shader must expose fragment information (e.g. gl_FragCoord).
// This applies to fragment shaders
//
// INSPECTOR_DESCRIPTOR_SET: the index of the descriptor set containing the Inspector buffers
// INSPECTOR_INSPECTION_DATA_BINDING: the binding index of the buffer containing the inspection information, as provided by ElementInspector::getComputeInspectionBuffer()
// INSPECTOR_METADATA_BINDING: the binding index of the buffer containing the inspection metadata, as provided by ElementInspector::getComputeMetadataBuffer()
#ifndef DH_INSPECTOR_H
#define DH_INSPECTOR_H
#ifdef __cplusplus
#include <cstdint>
namespace nvvkhl_shaders {
using uvec3 = glm::uvec3;
using uvec2 = glm::uvec2;
#else
#extension GL_EXT_shader_explicit_arithmetic_types : require
#extension GL_EXT_shader_atomic_int64 : require
#extension GL_KHR_shader_subgroup_basic : require
#endif
#define WARP_SIZE 32
#define WARP_2D_SIZE_X 8
#define WARP_2D_SIZE_Y 4
#define WARP_2D_SIZE_Z 1
struct InspectorComputeMetadata
{
uvec3 minBlock;
uint32_t u32PerThread;
uvec3 maxBlock;
uint32_t minWarpInBlock;
uint32_t maxWarpInBlock;
};
struct InspectorFragmentMetadata
{
uvec2 minFragment;
uvec2 maxFragment;
uvec2 renderSize;
uint32_t u32PerThread;
};
struct InspectorCustomMetadata
{
uvec3 minCoord;
uint32_t pad0;
uvec3 maxCoord;
uint32_t pad1;
uvec3 extent;
uint32_t u32PerThread;
};
#ifndef __cplusplus
#if !(defined INSPECTOR_MODE_COMPUTE) && !(defined INSPECTOR_MODE_FRAGMENT) && !(defined INSPECTOR_MODE_CUSTOM)
#error At least one inspector mode (INSPECTOR_MODE_COMPUTE, INSPECTOR_MODE_FRAGMENT, INSPECTOR_MODE_CUSTOM) must be defined before including this file
#endif
#if(defined INSPECTOR_MODE_COMPUTE) && (defined INSPECTOR_MODE_FRAGMENT)
#error Only one of inspector modes INSPECTOR_MODE_COMPUTE, INSPECTOR_MODE_FRAGMENT can be chosen
#endif
#ifndef INSPECTOR_DESCRIPTOR_SET
#error The descriptor set containing thread inspection data must be provided before including this file
#endif
#ifdef INSPECTOR_MODE_CUSTOM
#ifndef INSPECTOR_CUSTOM_INSPECTION_DATA_BINDING
#error The descriptor binding containing custom thread inspection data must be provided before including this file
#endif
#ifndef INSPECTOR_CUSTOM_METADATA_BINDING
#error The descriptor binding containing custom thread inspection metadata must be provided before including this file
#endif
#endif
#if(defined INSPECTOR_MODE_COMPUTE) || (defined INSPECTOR_MODE_FRAGMENT)
#ifndef INSPECTOR_INSPECTION_DATA_BINDING
#error The descriptor binding containing thread inspection data must be provided before including this file
#endif
#ifndef INSPECTOR_METADATA_BINDING
#error The descriptor binding containing thread inspection metadata must be provided before including this file
#endif
#endif
#ifdef INSPECTOR_MODE_COMPUTE
layout(set = INSPECTOR_DESCRIPTOR_SET, binding = INSPECTOR_INSPECTION_DATA_BINDING) buffer InspectorInspectionData
{
uint32_t inspectorInspectionData[];
};
layout(set = INSPECTOR_DESCRIPTOR_SET, binding = INSPECTOR_METADATA_BINDING) readonly buffer InspectorComputeInspectionMetadata
{
InspectorComputeMetadata inspectorMetadata;
};
/** @DOC_START
# Function inspect32BitValue
> Inspect a 32-bit value at a given index
@DOC_END */
void inspect32BitValue(uint32_t index, uint32_t v)
{
if(clamp(gl_WorkGroupID, inspectorMetadata.minBlock, inspectorMetadata.maxBlock) != gl_WorkGroupID)
{
return;
}
uint32_t warpIndex = gl_SubgroupID;
if(warpIndex < inspectorMetadata.minWarpInBlock || warpIndex > inspectorMetadata.maxWarpInBlock)
return;
uint32_t inspectedThreadsPerBlock = (inspectorMetadata.maxWarpInBlock - inspectorMetadata.minWarpInBlock + 1) * gl_SubgroupSize;
;
uint32_t blockIndex = gl_WorkGroupID.x + gl_NumWorkGroups.x * (gl_WorkGroupID.y + gl_NumWorkGroups.y * gl_WorkGroupID.z);
uint32_t minBlockIndex =
inspectorMetadata.minBlock.x
+ gl_NumWorkGroups.x * (inspectorMetadata.minBlock.y + gl_NumWorkGroups.y * inspectorMetadata.minBlock.z);
uint32_t blockStart = inspectedThreadsPerBlock * (blockIndex - minBlockIndex) * inspectorMetadata.u32PerThread;
uint32_t warpStart = (warpIndex - inspectorMetadata.minWarpInBlock) * inspectorMetadata.u32PerThread * gl_SubgroupSize;
uint32_t threadInWarpStart = gl_SubgroupInvocationID * inspectorMetadata.u32PerThread;
inspectorInspectionData[blockStart + warpStart + threadInWarpStart + index] = v;
}
#endif
#ifdef INSPECTOR_MODE_FRAGMENT
layout(set = INSPECTOR_DESCRIPTOR_SET, binding = INSPECTOR_INSPECTION_DATA_BINDING) buffer InspectorInspectionData
{
uint64_t inspectorInspectionData[];
};
layout(set = INSPECTOR_DESCRIPTOR_SET, binding = INSPECTOR_METADATA_BINDING) readonly buffer InspectorFragmentInspectionMetadata
{
InspectorFragmentMetadata inspectorMetadata;
};
void inspect32BitValue(uint32_t index, uint32_t v)
{
uvec2 fragment = uvec2(floor(gl_FragCoord.xy));
if(clamp(fragment, inspectorMetadata.minFragment, inspectorMetadata.maxFragment) != fragment)
{
return;
}
uvec2 localFragment = fragment - inspectorMetadata.minFragment;
uint32_t inspectionWidth = inspectorMetadata.maxFragment.x - inspectorMetadata.minFragment.x + 1;
uint32_t fragmentIndex = localFragment.x + inspectionWidth * localFragment.y;
// Atomically store the fragment depth along with the value so we always keep the fragment value
// with the lowest depth
float z = 1.f - clamp(gl_FragCoord.z, 0.f, 1.f);
uint64_t zUint = floatBitsToUint(z);
uint64_t value = (zUint << 32) | uint64_t(v);
atomicMax(inspectorInspectionData[fragmentIndex * inspectorMetadata.u32PerThread / 2 + index], value);
}
#endif
#ifdef INSPECTOR_MODE_CUSTOM
layout(set = INSPECTOR_DESCRIPTOR_SET, binding = INSPECTOR_CUSTOM_INSPECTION_DATA_BINDING) buffer InspectorCustomInspections
{
uint32_t inspectorCustomInspection[];
};
layout(set = INSPECTOR_DESCRIPTOR_SET, binding = INSPECTOR_CUSTOM_METADATA_BINDING) readonly buffer InspectorCustomInspectionMetadata
{
InspectorCustomMetadata inspectorCustomMetadata;
};
/** @DOC_START
# Function inspectCustom32BitValue
> Inspect a 32-bit value at a given index
@DOC_END */
void inspectCustom32BitValue(uint32_t index, uvec3 location, uint32_t v)
{
if(clamp(location, inspectorCustomMetadata.minCoord, inspectorCustomMetadata.maxCoord) != location)
{
return;
}
uvec3 localCoord = location - inspectorCustomMetadata.minCoord;
uint32_t inspectionWidth = inspectorCustomMetadata.maxCoord.x - inspectorCustomMetadata.minCoord.x + 1;
uint32_t inspectionHeight = inspectorCustomMetadata.maxCoord.y - inspectorCustomMetadata.minCoord.y + 1;
uint32_t coordIndex = localCoord.x + inspectionWidth * (localCoord.y + inspectionHeight * localCoord.z);
inspectorCustomInspection[coordIndex * inspectorCustomMetadata.u32PerThread + index] = v;
}
#endif
#endif
#ifdef __cplusplus
} // namespace nvvkhl_shaders
#endif
#endif // DH_INSPECTOR_H

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/// @DOC_SKIP
#ifndef DH_LIGHTING_H
#define DH_LIGHTING_H 1
#ifdef __cplusplus
namespace nvvkhl_shaders {
using vec3 = glm::vec3;
#endif // __cplusplus
const int eLightTypeNone = 0;
const int eLightTypeDirectional = 1;
const int eLightTypeSpot = 2;
const int eLightTypePoint = 3;
//-----------------------------------------------------------------------
// Use for light/env contribution
struct VisibilityContribution
{
vec3 radiance; // Radiance at the point if light is visible
vec3 lightDir; // Direction to the light, to shoot shadow ray
float lightDist; // Distance to the light (1e32 for infinite or sky)
bool visible; // true if in front of the face and should shoot shadow ray
};
struct LightContrib
{
vec3 incidentVector;
float halfAngularSize;
vec3 intensity;
};
struct Light
{
vec3 direction;
int type;
vec3 position;
float radius;
vec3 color;
float intensity; // illuminance (lm/m2) for directional lights, luminous intensity (lm/sr) for positional lights
float angularSizeOrInvRange; // angular size for directional lights, 1/range for spot and point lights
float innerAngle;
float outerAngle;
float outOfBoundsShadow;
};
#ifdef __cplusplus
inline Light defaultLight()
{
Light l;
l.position = vec3{5.0F, 5.F, 5.F};
l.direction = glm::normalize(vec3{0.0F, -.7F, -.7F});
l.type = eLightTypeDirectional;
l.angularSizeOrInvRange = glm::radians(0.53F);
l.color = {1.0F, 1.0F, 1.0F};
l.intensity = 0.F; // Dark
l.innerAngle = glm::radians(10.F);
l.outerAngle = glm::radians(30.F);
l.radius = 1.0F;
return l;
}
#endif //__cplusplus
#ifdef __cplusplus
} // namespace nvvkhl_shaders
#endif
#endif // DH_LIGHTING_H

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/// @DOC_SKIP
#ifndef DH_SCN_DESC_H
#define DH_SCN_DESC_H 1
#ifdef __cplusplus
namespace nvvkhl_shaders {
using mat4 = glm::mat4;
using mat3 = glm::mat3;
using vec4 = glm::vec4;
using vec3 = glm::vec3;
#endif // __cplusplus
struct InstanceInfo
{
mat4 objectToWorld;
mat4 worldToObject;
int materialID;
};
struct Vertex
{
vec4 position; // POS.xyz + UV.x
vec4 normal; // NRM.xyz + UV.y
vec4 tangent; // TNG.xyz + sign: 1, -1
};
struct PrimMeshInfo
{
uint64_t vertexAddress;
uint64_t indexAddress;
int materialIndex;
};
struct SceneDescription
{
uint64_t materialAddress;
uint64_t instInfoAddress;
uint64_t primInfoAddress;
};
// alphaMode
#define ALPHA_OPAQUE 0
#define ALPHA_MASK 1
#define ALPHA_BLEND 2
struct GltfShadeMaterial
{
// Core
vec4 pbrBaseColorFactor;
vec3 emissiveFactor;
int pbrBaseColorTexture;
int normalTexture;
float normalTextureScale;
int _pad0;
float pbrRoughnessFactor;
float pbrMetallicFactor;
int pbrMetallicRoughnessTexture;
int emissiveTexture;
int alphaMode;
float alphaCutoff;
// KHR_materials_transmission
float transmissionFactor;
int transmissionTexture;
// KHR_materials_ior
float ior;
// KHR_materials_volume
vec3 attenuationColor;
float thicknessFactor;
int thicknessTexture;
bool thinWalled;
float attenuationDistance;
// KHR_materials_clearcoat
float clearcoatFactor;
float clearcoatRoughness;
int clearcoatTexture;
int clearcoatRoughnessTexture;
int clearcoatNormalTexture;
// KHR_materials_specular
float specularFactor;
int specularTexture;
vec3 specularColorFactor;
int specularColorTexture;
// KHR_texture_transform
mat3 uvTransform;
};
#ifdef __cplusplus
} // namespace nvvkhl_shaders
#endif
#endif

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef DH_SKY_H
#define DH_SKY_H 1
#ifdef __cplusplus
#define inout // Not used
namespace nvvkhl_shaders {
using mat4 = glm::mat4;
using vec2 = glm::vec2;
using vec3 = glm::vec3;
#else
#define static
#define inline
#endif // __cplusplus
#ifndef WORKGROUP_SIZE
#define WORKGROUP_SIZE 16 // Grid size used by compute shaders
#endif
// clang-format off
#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL
#define START_BINDING(a) enum a {
#define END_BINDING() }
#else
#define START_BINDING(a) const uint
#define END_BINDING()
#endif
START_BINDING(SkyBindings)
eSkyOutImage = 0,
eSkyParam = 1
END_BINDING();
// clang-format on
struct ProceduralSkyShaderParameters
{
vec3 directionToLight;
float angularSizeOfLight;
vec3 lightColor;
float glowSize;
vec3 skyColor;
float glowIntensity;
vec3 horizonColor;
float horizonSize;
vec3 groundColor;
float glowSharpness;
vec3 directionUp;
float pad1;
};
struct SkyPushConstant
{
mat4 mvp;
};
/** @DOC_START
# Function initSkyShaderParameters
> Initializes the sky shader parameters with default values
@DOC_END */
inline ProceduralSkyShaderParameters initSkyShaderParameters()
{
ProceduralSkyShaderParameters p;
p.directionToLight = vec3(0.0F, 0.707F, 0.707F);
p.angularSizeOfLight = 0.059F;
p.lightColor = vec3(1.0F, 1.0F, 1.0F);
p.skyColor = vec3(0.17F, 0.37F, 0.65F);
p.horizonColor = vec3(0.50F, 0.70F, 0.92F);
p.groundColor = vec3(0.62F, 0.59F, 0.55F);
p.directionUp = vec3(0.F, 1.F, 0.F);
p.horizonSize = 0.5F; // +/- degrees
p.glowSize = 0.091F; // degrees, starting from the edge of the light disk
p.glowIntensity = 0.9F; // [0-1] relative to light intensity
p.glowSharpness = 4.F; // [1-10] is the glow power exponent
return p;
}
#ifndef __cplusplus
/** @DOC_START
# Function proceduralSky
> Return the color of the procedural sky shader
@DOC_END */
inline vec3 proceduralSky(ProceduralSkyShaderParameters params, vec3 direction, float angularSizeOfPixel)
{
float elevation = asin(clamp(dot(direction, params.directionUp), -1.0F, 1.0F));
float top = smoothstep(0.F, params.horizonSize, elevation);
float bottom = smoothstep(0.F, params.horizonSize, -elevation);
vec3 environment = mix(mix(params.horizonColor, params.groundColor, bottom), params.skyColor, top);
float angle_to_light = acos(clamp(dot(direction, params.directionToLight), 0.0F, 1.0F));
float half_angular_size = params.angularSizeOfLight * 0.5F;
float light_intensity =
clamp(1.0F - smoothstep(half_angular_size - angularSizeOfPixel * 2.0F, half_angular_size + angularSizeOfPixel * 2.0F, angle_to_light),
0.0F, 1.0F);
light_intensity = pow(light_intensity, 4.0F);
float glow_input =
clamp(2.0F * (1.0F - smoothstep(half_angular_size - params.glowSize, half_angular_size + params.glowSize, angle_to_light)),
0.0F, 1.0F);
float glow_intensity = params.glowIntensity * pow(glow_input, params.glowSharpness);
vec3 light = max(light_intensity, glow_intensity) * params.lightColor;
return environment + light;
}
#endif
#ifdef __cplusplus
} // namespace nvvkhl_shaders
#endif
#endif // DH_SKY_H

View file

@ -0,0 +1,169 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef DH_TONEMAMP_H
#define DH_TONEMAMP_H 1
#ifdef __cplusplus
namespace nvvkhl_shaders {
using vec3 = glm::vec3;
using vec2 = glm::vec2;
using uint = uint32_t;
#define INLINE inline
#else
#define INLINE
#endif
const int eTonemapFilmic = 0;
const int eTonemapUncharted = 1;
const int eTonemapGamma = 2;
// Tonemapper settings
struct Tonemapper
{
int method;
int isActive;
float exposure;
float brightness;
float contrast;
float saturation;
float vignette;
float gamma;
};
INLINE Tonemapper defaultTonemapper()
{
Tonemapper t;
t.method = 0;
t.isActive = 1;
t.exposure = 1.0F;
t.brightness = 1.0F;
t.contrast = 1.0F;
t.saturation = 1.0F;
t.vignette = 0.0F;
t.gamma = 2.2F;
return t;
}
// Bindings
const int eTonemapperInput = 0;
const int eTonemapperOutput = 1;
/** @DOC_START
# Function tonemapFilmic
> Filmic tonemapping operator
http://filmicworlds.com/blog/filmic-tonemapping-operators/
@DOC_END */
INLINE vec3 tonemapFilmic(vec3 color)
{
vec3 temp = max(vec3(0.0F), color - vec3(0.004F));
vec3 result = (temp * (vec3(6.2F) * temp + vec3(0.5F))) / (temp * (vec3(6.2F) * temp + vec3(1.7F)) + vec3(0.06F));
return result;
}
/** @DOC_START
# Function gammaCorrection
> Gamma correction
see https://www.teamten.com/lawrence/graphics/gamma/
@DOC_END */
INLINE vec3 gammaCorrection(vec3 color, float gamma)
{
return pow(color, vec3(1.0F / gamma));
}
/** @DOC_START
# Function tonemapUncharted
> Uncharted tone map
see: http://filmicworlds.com/blog/filmic-tonemapping-operators/
@DOC_END */
INLINE vec3 tonemapUncharted2Impl(vec3 color)
{
const float a = 0.15F;
const float b = 0.50F;
const float c = 0.10F;
const float d = 0.20F;
const float e = 0.02F;
const float f = 0.30F;
return ((color * (a * color + c * b) + d * e) / (color * (a * color + b) + d * f)) - e / f;
}
INLINE vec3 tonemapUncharted(vec3 color, float gamma)
{
const float W = 11.2F;
const float exposure_bias = 2.0F;
color = tonemapUncharted2Impl(color * exposure_bias);
vec3 white_scale = vec3(1.0F) / tonemapUncharted2Impl(vec3(W));
return gammaCorrection(color * white_scale, gamma);
}
INLINE vec3 applyTonemap(Tonemapper tm, vec3 color, vec2 uv)
{
// Exposure
color *= tm.exposure;
vec3 c;
// Tonemap
switch(tm.method)
{
case eTonemapFilmic:
c = tonemapFilmic(color);
break;
case eTonemapUncharted:
c = tonemapUncharted(color, tm.gamma);
break;
default:
c = gammaCorrection(color, tm.gamma);
break;
}
//contrast
c = clamp(mix(vec3(0.5F), c, vec3(tm.contrast)), vec3(0.F), vec3(1.F));
// brighness
c = pow(c, vec3(1.0F / tm.brightness));
// saturation
vec3 i = vec3(dot(c, vec3(0.299F, 0.587F, 0.114F)));
c = mix(i, c, tm.saturation);
// vignette
vec2 center_uv = ((uv)-vec2(0.5F)) * vec2(2.0F);
c *= 1.0F - dot(center_uv, center_uv) * tm.vignette;
return c;
}
// http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
INLINE vec3 toSrgb(vec3 rgb)
{
vec3 s1 = sqrt(rgb);
vec3 s2 = sqrt(s1);
vec3 s3 = sqrt(s2);
return vec3(0.662002687F) * s1 + vec3(0.684122060F) * s2 - vec3(0.323583601F) * s3 - vec3(0.0225411470F) * rgb;
}
INLINE vec3 toLinear(vec3 srgb)
{
return srgb * (srgb * (srgb * vec3(0.305306011F) + vec3(0.682171111F)) + vec3(0.012522878F));
}
#ifdef __cplusplus
} // namespace nvvkhl_shaders
#endif
#endif // DH_TONEMAMP_H

View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef FUNC_GLSL
#define FUNC_GLSL 1
#include "constants.glsl"
precision highp float;
float square(float x)
{
return x * x;
}
float saturate(float x)
{
return clamp(x, 0.0F, 1.0F);
}
vec3 saturate(vec3 x)
{
return clamp(x, vec3(0.0F), vec3(1.0F));
}
// Return the luminance of a color
float luminance(in vec3 color)
{
return color.x * 0.2126F + color.y * 0.7152F + color.z * 0.0722F;
}
vec3 slerp(vec3 a, vec3 b, float angle, float t)
{
t = saturate(t);
float sin1 = sin(angle * t);
float sin2 = sin(angle * (1.0F - t));
float ta = sin1 / (sin1 + sin2);
vec3 result = mix(a, b, ta);
return normalize(result);
}
float clampedDot(vec3 x, vec3 y)
{
return clamp(dot(x, y), 0.0F, 1.0F);
}
// Return the tangent and binormal from the incoming normal
void createCoordinateSystem(in vec3 normal, out vec3 tangent, out vec3 bitangent)
{
if(abs(normal.x) > abs(normal.y))
tangent = vec3(normal.z, 0.0F, -normal.x) / sqrt(normal.x * normal.x + normal.z * normal.z);
else
tangent = vec3(0.0F, -normal.z, normal.y) / sqrt(normal.y * normal.y + normal.z * normal.z);
bitangent = cross(normal, tangent);
}
//-----------------------------------------------------------------------
// Building an Orthonormal Basis, Revisited
// by Tom Duff, James Burgess, Per Christensen, Christophe Hery, Andrew Kensler, Max Liani, Ryusuke Villemin
// https://graphics.pixar.com/library/OrthonormalB/
//-----------------------------------------------------------------------
void orthonormalBasis(in vec3 normal, out vec3 tangent, out vec3 bitangent)
{
float sgn = normal.z > 0.0F ? 1.0F : -1.0F;
float a = -1.0F / (sgn + normal.z);
float b = normal.x * normal.y * a;
tangent = vec3(1.0f + sgn * normal.x * normal.x * a, sgn * b, -sgn * normal.x);
bitangent = vec3(b, sgn + normal.y * normal.y * a, -normal.y);
}
vec3 rotate(vec3 v, vec3 k, float theta)
{
float cos_theta = cos(theta);
float sin_theta = sin(theta);
return (v * cos_theta) + (cross(k, v) * sin_theta) + (k * dot(k, v)) * (1.0F - cos_theta);
}
//-----------------------------------------------------------------------
// Return the UV in a lat-long HDR map
//-----------------------------------------------------------------------
vec2 getSphericalUv(vec3 v)
{
float gamma = asin(-v.y);
float theta = atan(v.z, v.x);
vec2 uv = vec2(theta * M_1_OVER_PI * 0.5F, gamma * M_1_OVER_PI) + 0.5F;
return uv;
}
//-----------------------------------------------------------------------
// Return the interpolated value between 3 values and the barycentrics
//-----------------------------------------------------------------------
vec2 mixBary(vec2 a, vec2 b, vec2 c, vec3 bary)
{
return a * bary.x + b * bary.y + c * bary.z;
}
vec3 mixBary(vec3 a, vec3 b, vec3 c, vec3 bary)
{
return a * bary.x + b * bary.y + c * bary.z;
}
vec4 mixBary(vec4 a, vec4 b, vec4 c, vec3 bary)
{
return a * bary.x + b * bary.y + c * bary.z;
}
//-----------------------------------------------------------------------
// https://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.4.pdf
// 16.6.1 COSINE-WEIGHTED HEMISPHERE ORIENTED TO THE Z-AXIS
//-----------------------------------------------------------------------
vec3 cosineSampleHemisphere(float r1, float r2)
{
float r = sqrt(r1);
float phi = M_TWO_PI * r2;
vec3 dir;
dir.x = r * cos(phi);
dir.y = r * sin(phi);
dir.z = sqrt(max(0.0, 1.0 - dir.x * dir.x - dir.y * dir.y));
return dir;
}
#endif // FUNC_GLSL

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef GGX_GLSL
#define GGX_GLSL 1
#include "constants.glsl"
//-----------------------------------------------------------------------
// The following equation models the Fresnel reflectance term of the spec equation (aka F())
// Implementation of fresnel from [4], Equation 15
//-----------------------------------------------------------------------
vec3 fresnelSchlick(vec3 f0, vec3 f90, float VdotH)
{
return f0 + (f90 - f0) * pow(clamp(vec3(1.0F) - VdotH, vec3(0.0F), vec3(1.0F)), vec3(5.0F));
}
float fresnelSchlick(float f0, float f90, float VdotH)
{
return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0F, 1.0F), 5.0F);
}
//-----------------------------------------------------------------------
// Smith Joint GGX
// Note: Vis = G / (4 * NdotL * NdotV)
// see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3
// see Real-Time Rendering. Page 331 to 336.
// see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg)
//-----------------------------------------------------------------------
float smithJointGGX(float NdotL, float NdotV, float alphaRoughness)
{
float alphaRoughnessSq = max(alphaRoughness * alphaRoughness, 1e-07);
float ggxV = NdotL * sqrt(NdotV * NdotV * (1.0F - alphaRoughnessSq) + alphaRoughnessSq);
float ggxL = NdotV * sqrt(NdotL * NdotL * (1.0F - alphaRoughnessSq) + alphaRoughnessSq);
float ggx = ggxV + ggxL;
if(ggx > 0.0F)
{
return 0.5F / ggx;
}
return 0.0F;
}
//-----------------------------------------------------------------------
// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
//-----------------------------------------------------------------------
float distributionGGX(float NdotH, float alphaRoughness) // alphaRoughness = roughness * roughness;
{
float alphaSqr = max(alphaRoughness * alphaRoughness, 1e-07);
float NdotHSqr = NdotH * NdotH;
float denom = NdotHSqr * (alphaSqr - 1.0) + 1.0;
return alphaSqr / (M_PI * denom * denom);
}
//-----------------------------------------------------------------------
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
//-----------------------------------------------------------------------
vec3 brdfLambertian(vec3 diffuseColor, float metallic)
{
return (1.0F - metallic) * (diffuseColor / M_PI);
}
//-----------------------------------------------------------------------
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
//-----------------------------------------------------------------------
vec3 brdfSpecularGGX(vec3 f0, vec3 f90, float alphaRoughness, float VdotH, float NdotL, float NdotV, float NdotH)
{
vec3 f = fresnelSchlick(f0, f90, VdotH);
float vis = smithJointGGX(NdotL, NdotV, alphaRoughness); // Vis = G / (4 * NdotL * NdotV)
float d = distributionGGX(NdotH, alphaRoughness);
return f * vis * d;
}
//-----------------------------------------------------------------------
// Sample the GGX distribution
// - Return the half vector
//-----------------------------------------------------------------------
vec3 ggxSampling(float alphaRoughness, float r1, float r2)
{
float alphaSqr = max(alphaRoughness * alphaRoughness, 1e-07);
float phi = 2.0 * M_PI * r1;
float cosTheta = sqrt((1.0 - r2) / (1.0 + (alphaSqr - 1.0) * r2));
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
}
// Return false if it produce a total internal reflection
bool refract(vec3 incident, vec3 normal, float eta, out vec3 transmitted)
{
float cosTheta = dot(incident, normal);
float k = 1.0F - eta * eta * (1.0F - cosTheta * cosTheta);
if(k < 0.0F)
{
// Total internal reflection
return false;
}
else
{
transmitted = eta * incident - (eta * cosTheta + sqrt(k)) * normal;
return true;
}
}
#endif // GGX_H

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#version 450
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#include "dh_hdr.h"
#include "constants.glsl"
layout(local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;
layout(set = 0, binding = eHdrBrdf) writeonly uniform image2D gOutHdr;
layout(set = 1, binding = eHdr) uniform sampler2D gInHdr;
layout(push_constant) uniform SkyDomePushConstant_
{
HdrDomePushConstant pc;
};
vec2 getSphericalUv(vec3 v)
{
float gamma = asin(-v.y);
float theta = atan(v.z, v.x);
return vec2(theta * M_1_OVER_PI * 0.5, gamma * M_1_OVER_PI) + 0.5F;
}
vec3 rotate(vec3 v, vec3 k, float theta)
{
float cos_theta = cos(theta);
float sin_theta = sin(theta);
return (v * cos_theta) + (cross(k, v) * sin_theta) + (k * dot(k, v)) * (1.0F - cos_theta);
}
void main()
{
const vec2 pixel_center = vec2(gl_GlobalInvocationID.xy) + vec2(0.5);
const vec2 in_uv = pixel_center / vec2(imageSize(gOutHdr));
const vec2 d = in_uv * 2.0F - 1.0F;
vec3 direction = vec3(pc.mvp * vec4(d.x, d.y, 1.0F, 1.0F));
direction = rotate(direction, vec3(0.0F, 1.0F, 0.0F), -pc.rotation);
const vec2 uv = getSphericalUv(normalize(direction.xyz));
const vec3 color = texture(gInHdr, uv).rgb * pc.multColor.rgb;
imageStore(gOutHdr, ivec2(gl_GlobalInvocationID.xy), vec4(color, 1.0));
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
//-------------------------------------------------------------------------------------------------
// This file has the functions to sample the environment
#ifndef HDR_ENV_SAMPLING_GLSL
#define HDR_ENV_SAMPLING_GLSL 1
precision highp float;
#include "dh_hdr.h"
//-------------------------------------------------------------------------------------------------
// Environment Sampling (HDR)
// See: https://arxiv.org/pdf/1901.05423.pdf
//-------------------------------------------------------------------------------------------------
vec4 environmentSample(in sampler2D hdrTexture, in vec3 randVal, out vec3 toLight)
{
// Uniformly pick a texel index idx in the environment map
vec3 xi = randVal;
uvec2 tsize = uvec2(textureSize(hdrTexture, 0));
uint width = tsize.x;
uint height = tsize.y;
uint size = width * height;
uint idx = min(uint(xi.x * float(size)), size - 1U);
// Fetch the sampling data for that texel, containing the ratio q between its
// emitted radiance and the average of the environment map, the texel alias,
// the probability distribution function (PDF) values for that texel and its
// alias
EnvAccel sample_data = envSamplingData[idx];
uint env_idx;
if(xi.y < sample_data.q)
{
// If the random variable is lower than the intensity ratio q, we directly pick
// this texel, and renormalize the random variable for later use. The PDF is the
// one of the texel itself
env_idx = idx;
xi.y /= sample_data.q;
}
else
{
// Otherwise we pick the alias of the texel, renormalize the random variable and use
// the PDF of the alias
env_idx = sample_data.alias;
xi.y = (xi.y - sample_data.q) / (1.0f - sample_data.q);
}
// Compute the 2D integer coordinates of the texel
const uint px = env_idx % width;
uint py = env_idx / width;
// Uniformly sample the solid angle subtended by the pixel.
// Generate both the UV for texture lookup and a direction in spherical coordinates
const float u = float(px + xi.y) / float(width);
const float phi = u * (2.0f * M_PI) - M_PI;
float sin_phi = sin(phi);
float cos_phi = cos(phi);
const float step_theta = M_PI / float(height);
const float theta0 = float(py) * step_theta;
const float cos_theta = cos(theta0) * (1.0f - xi.z) + cos(theta0 + step_theta) * xi.z;
const float theta = acos(cos_theta);
const float sin_theta = sin(theta);
const float v = theta * M_1_OVER_PI;
// Convert to a light direction vector in Cartesian coordinates
toLight = vec3(cos_phi * sin_theta, cos_theta, sin_phi * sin_theta);
// Lookup the environment value using bilinear filtering
return texture(hdrTexture, vec2(u, v));
}
float powerHeuristic(float a, float b)
{
const float t = a * a;
return t / (b * b + t);
}
#endif // ENV_SAMPLING_GLSL

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#version 450
// This shader computes a glossy BRDF map to be used with the Unreal 4 PBR shading model as
// described in
//
// "Real Shading in Unreal Engine 4" by Brian Karis
// http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
//
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#include "dh_hdr.h"
layout(set = 0, binding = eHdrImage) writeonly uniform image2D gOutColor;
layout(local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;
const float M_PI = 3.14159265359F;
// See http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
float radinv(uint bits)
{
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}
vec2 hammersley2D(uint i, uint N)
{
return vec2(float(i) / float(N), radinv(i));
}
vec3 ggxSample(vec2 xi, vec3 normal, float alpha)
{
// compute half-vector in spherical coordinates
float phi = 2.0F * M_PI * xi.x;
float cos_theta = sqrt((1.0F - xi.y) / (1.0F + (alpha * alpha - 1.0F) * xi.y));
float sin_theta = sqrt(1.0F - cos_theta * cos_theta);
return vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);
}
float geometrySchlickGgx(float ndotv, float roughness)
{
// note that we use a different k for IBL
float a = roughness;
float k = (a * a) / 2.0F;
float nom = ndotv;
float denom = ndotv * (1.0F - k) + k;
return nom / denom;
}
float geometrySmith(vec3 normal, vec3 view, vec3 light, float roughness)
{
float ndotv = max(dot(normal, view), 0.0F);
float ndotl = max(dot(normal, light), 0.0F);
float g1 = geometrySchlickGgx(ndotv, roughness);
float g2 = geometrySchlickGgx(ndotl, roughness);
return g1 * g2;
}
vec2 integrateBrdf(float ndotv, float roughness)
{
vec3 view;
view.x = sqrt(1.0F - ndotv * ndotv); // sin
view.y = 0.0;
view.z = ndotv;
float A = 0.0F;
float B = 0.0F;
const vec3 normal = vec3(0.0F, 0.0F, 1.0F);
const uint nsamples = 1024u;
float alpha = roughness * roughness;
for(uint i = 0u; i < nsamples; ++i)
{
vec2 xi = hammersley2D(i, nsamples);
vec3 h0 = ggxSample(xi, normal, alpha);
vec3 h = vec3(h0.y, -h0.x, h0.z);
vec3 light = normalize(2.0F * dot(view, h) * h - view);
float ndotl = max(light.z, 0.0F);
float ndoth = max(h.z, 0.0F);
float vdoth = max(dot(view, h), 0.0F);
if(ndotl > 0.0)
{
float G = geometrySmith(normal, view, light, roughness);
float G_Vis = (G * vdoth) / (ndoth * ndotv);
float Fc = pow(1.0 - vdoth, 5.0F);
A += (1.0 - Fc) * G_Vis;
B += Fc * G_Vis;
}
}
A /= float(nsamples);
B /= float(nsamples);
return vec2(A, B);
}
void main()
{
const vec2 pixel_center = vec2(gl_GlobalInvocationID.xy) + vec2(0.5F);
const vec2 in_uv = pixel_center / vec2(imageSize(gOutColor));
vec2 brdf = integrateBrdf(in_uv.s, 1.0F - in_uv.t);
imageStore(gOutColor, ivec2(gl_GlobalInvocationID.xy), vec4(brdf, 0.0F, 0.0F));
}

View file

@ -0,0 +1,120 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#version 450
// This shader computes a diffuse irradiance IBL map using multiple importance sampling weighted
// hemisphere sampling and environment map importance sampling.
// varying inputs
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#extension GL_EXT_scalar_block_layout : enable
#include "dh_hdr.h"
#include "constants.glsl"
#include "func.glsl"
#include "random.glsl"
// clang-format off
layout(local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;
layout(set = 0, binding = eHdrImage) writeonly uniform image2D gOutColor;
layout(set = 1, binding = eImpSamples, scalar) buffer _EnvAccel { EnvAccel envSamplingData[]; };
layout(set = 1, binding = eHdr) uniform sampler2D hdrTexture;
layout(push_constant) uniform HdrPushBlock_ { HdrPushBlock pc; };
// clang-format on
#include "hdr_env_sampling.glsl"
void main()
{
// Finding the world direction
const vec2 pixelCenter = vec2(gl_GlobalInvocationID.xy) + vec2(0.5F);
const vec2 inUV = pixelCenter / pc.size;
const vec2 d = inUV * 2.0F - 1.0F;
vec3 direction = vec3(pc.mvp * vec4(d.x, d.y, 1.0F, 1.0F));
// Getting axis
vec3 tangent, bitangent;
vec3 normal = normalize(vec3(direction.x, -direction.y, direction.z)); // Flipping Y
orthonormalBasis(normal, tangent, bitangent);
// Random seed
uint seed = xxhash32(uvec3(gl_GlobalInvocationID.xyz));
vec3 result = vec3(0.0f);
uint nsamples = 512u;
float inv_samples = 1.0f / float(nsamples);
for(uint i = 0u; i < nsamples; ++i)
{
// Importance sample diffuse BRDF.
{
float xi0 = (float(i) + 0.5f) * inv_samples;
float xi1 = rand(seed);
float phi = 2.0f * M_PI * xi0;
float sin_phi = sin(phi);
float cos_phi = cos(phi);
float sin_theta = sqrt(1.0f - xi1);
float cos_theta = sqrt(xi1);
vec3 d = vec3(sin_theta * cos_phi, sin_theta * sin_phi, cos_theta);
vec3 direction = d.x * tangent + d.y * bitangent + d.z * normal;
vec2 uv = getSphericalUv(direction);
float p_brdf_sqr = cos_theta * cos_theta * (M_1_OVER_PI * M_1_OVER_PI);
vec4 rad_pdf = texture(hdrTexture, uv);
float p_env = rad_pdf.a;
float w = p_brdf_sqr / (p_brdf_sqr + p_env * p_env);
result += rad_pdf.rgb * w * M_PI;
}
// Importance sample environment.
{
vec3 dir;
vec3 rand_val = vec3(rand(seed), rand(seed), rand(seed));
vec4 rad_pdf = environmentSample(hdrTexture, rand_val, dir);
float pdf = rad_pdf.a;
vec3 value = rad_pdf.rgb / pdf;
float cosine = dot(dir, normal);
float p_brdf_sqr = cosine * cosine * (M_1_OVER_PI * M_1_OVER_PI);
float p_env_sqr = pdf * pdf;
if(cosine > 0.0f)
{
float w = p_env_sqr / (p_env_sqr + p_brdf_sqr);
result += w * value * cosine;
}
}
}
vec4 frag_color = vec4(result * (1.0f / float(nsamples)) / M_PI, 1.0f);
imageStore(gOutColor, ivec2(gl_GlobalInvocationID.xy), frag_color);
}

View file

@ -0,0 +1,166 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#version 450
// This shader computes a glossy IBL map to be used with the Unreal 4 PBR shading model as
// described in
//
// "Real Shading in Unreal Engine 4" by Brian Karis
// http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
//
// As an extension to the original it uses multiple importance sampling weighted BRDF importance
// sampling and environment map importance sampling to yield good results for high dynamic range
// lighting.
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#extension GL_EXT_scalar_block_layout : enable
#include "dh_hdr.h"
// clang-format off
layout(local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;
layout(set = 0, binding = eHdrImage) writeonly uniform image2D g_outColor;
layout(set = 1, binding = eImpSamples, scalar) buffer _EnvAccel { EnvAccel envSamplingData[]; };
layout(set = 1, binding = eHdr) uniform sampler2D hdrTexture;
layout(push_constant) uniform HdrPushBlock_ { HdrPushBlock pc; };
// clang-format on
#include "constants.glsl"
#include "func.glsl"
#include "random.glsl"
#include "hdr_env_sampling.glsl"
// Importance sample a GGX microfacet distribution.
vec3 ggxSample(vec2 xi, float alpha)
{
float phi = 2.0F * M_PI * xi.x;
float cos_theta = sqrt((1.0F - xi.y) / (1.0F + (alpha * alpha - 1.0F) * xi.y));
float sin_theta = sqrt(1.0F - cos_theta * cos_theta);
return vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);
}
// Evaluate a GGX microfacet distribution.
float ggxEval(float alpha, float nh)
{
float a2 = alpha * alpha;
float nh2 = nh * nh;
float tan2 = (1.0f - nh2) / nh2;
float f = a2 + tan2;
return a2 / (f * f * M_PI * nh2 * nh);
}
struct EnvmapSampleValue
{
vec3 dir;
vec3 value;
float pdf;
};
void main()
{
const vec2 pixel_center = vec2(gl_GlobalInvocationID.xy) + vec2(0.5F);
const vec2 in_uv = pixel_center / vec2(pc.size);
const vec2 d = in_uv * 2.0F - 1.0F;
vec3 direction = vec3(pc.mvp * vec4(d.x, d.y, 1.0F, 1.0F));
vec3 tangent, bitangent;
vec3 normal = normalize(vec3(direction.x, -direction.y, direction.z)); // Flipping Y
orthonormalBasis(normal, tangent, bitangent);
float alpha = pc.roughness;
uint nsamples = alpha > 0.0F ? 512u : 1u;
uint state = xxhash32(uvec3(gl_GlobalInvocationID.xy, pc.roughness * 10.0F));
// The integrals are additionally weighted by the cosine and normalized using the average cosine of
// the importance sampled BRDF directions (as in the Unreal publication).
float weight_sum = 0.0f;
vec3 result = vec3(0.0F);
float inv_nsamples = 1.0F / float(nsamples);
for(uint i = 0u; i < nsamples; ++i)
{
// Importance sample BRDF.
{
float xi0 = (float(i) + 0.5F) * inv_nsamples;
float xi1 = rand(state);
vec3 h0 = alpha > 0.0f ? ggxSample(vec2(xi0, xi1), alpha) : vec3(0.0F, 0.0F, 1.0F);
vec3 h = tangent * h0.x + bitangent * h0.y + normal * h0.z;
vec3 direction = normalize(2.0 * dot(normal, h) * h - normal);
float cos_theta = dot(normal, direction);
if(cos_theta > 0.0F)
{
vec2 uv = getSphericalUv(direction);
float w = 1.0F;
if(alpha > 0.0F)
{
float pdf_brdf_sqr = ggxEval(alpha, h0.z) * 0.25F / dot(direction, h);
pdf_brdf_sqr *= pdf_brdf_sqr;
float pdf_env = texture(hdrTexture, uv).a;
w = pdf_brdf_sqr / (pdf_brdf_sqr + pdf_env * pdf_env);
}
result += w * texture(hdrTexture, uv).rgb * cos_theta;
weight_sum += cos_theta;
}
}
// Importance sample environment.
if(alpha > 0.0f)
{
vec3 randVal = vec3(rand(state), rand(state), rand(state));
EnvmapSampleValue val;
vec4 radPdf = environmentSample(hdrTexture, randVal, val.dir);
val.pdf = radPdf.a;
val.value = radPdf.rgb / val.pdf;
vec3 h = normalize(normal + val.dir);
float nh = dot(h, normal);
float kh = dot(val.dir, h);
float nk = dot(val.dir, normal);
if(kh > 0.0F && nh > 0.0F && nk > 0.0F)
{
float pdf_env_sqr = val.pdf * val.pdf;
float pdf_brdf = ggxEval(alpha, nh) * 0.25F / kh;
float w = pdf_env_sqr / (pdf_env_sqr + pdf_brdf * pdf_brdf);
result += w * val.value * pdf_brdf * nk * nk;
}
}
}
vec4 result_color = vec4(result / float(weight_sum), 1.0F);
imageStore(g_outColor, ivec2(gl_GlobalInvocationID.xy), result_color);
}

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LIGHT_CONTRIB_H
#define LIGHT_CONTRIB_H 1
#include "func.glsl"
#include "dh_lighting.h"
LightContrib singleLightContribution(in Light light, in vec3 surfacePos, in vec3 surfaceNormal, in vec3 viewIncident, in vec2 randVal)
{
LightContrib contrib;
contrib.incidentVector = vec3(0.0F);
contrib.halfAngularSize = 0.0F;
contrib.intensity = vec3(0.0F);
float irradiance = 0.0F;
if(light.type == eLightTypeDirectional)
{
contrib.incidentVector = light.direction;
contrib.halfAngularSize = light.angularSizeOrInvRange * 0.5F;
irradiance = light.intensity;
}
else if(light.type == eLightTypeSpot || light.type == eLightTypePoint)
{
vec3 light_to_surface = surfacePos - light.position;
float distance = sqrt(dot(light_to_surface, light_to_surface));
float r_distance = 1.0F / distance;
contrib.incidentVector = light_to_surface * r_distance;
float attenuation = 1.F;
if(light.angularSizeOrInvRange > 0.0F)
{
attenuation = square(saturate(1.0F - square(square(distance * light.angularSizeOrInvRange))));
if(attenuation == 0.0F)
return contrib;
}
float spotlight = 1.0F;
if(light.type == eLightTypeSpot)
{
float lDotD = dot(contrib.incidentVector, light.direction);
float direction_angle = acos(lDotD);
spotlight = 1.0F - smoothstep(light.innerAngle, light.outerAngle, direction_angle);
if(spotlight == 0.0F)
return contrib;
}
if(light.radius > 0.0F)
{
contrib.halfAngularSize = atan(min(light.radius * r_distance, 1.0F));
// A good enough approximation for 2 * (1 - cos(halfAngularSize)), numerically more accurate for small angular sizes
float solidAngleOverPi = square(contrib.halfAngularSize);
float radianceTimesPi = light.intensity / square(light.radius);
irradiance = radianceTimesPi * solidAngleOverPi;
}
else
{
irradiance = light.intensity * square(r_distance);
}
irradiance *= spotlight * attenuation;
}
contrib.intensity = irradiance * light.color;
if(contrib.halfAngularSize > 0.0F)
{ // <----- Sampling area lights
float angular_size = contrib.halfAngularSize;
// section 34 https://people.cs.kuleuven.be/~philip.dutre/GI/TotalCompendium.pdf
vec3 dir;
float tmp = (1.0F - randVal.y * (1.0F - cos(angular_size)));
float tmp2 = tmp * tmp;
float tetha = sqrt(1.0F - tmp2);
dir.x = cos(M_TWO_PI * randVal.x) * tetha;
dir.y = sin(M_TWO_PI * randVal.x) * tetha;
dir.z = tmp;
vec3 light_dir = -contrib.incidentVector;
vec3 tangent, binormal;
orthonormalBasis(light_dir, tangent, binormal);
mat3 tbn = mat3(tangent, binormal, light_dir);
light_dir = normalize(tbn * dir);
contrib.incidentVector = -light_dir;
}
return contrib;
}
// Version without random values
LightContrib singleLightContribution(in Light light, in vec3 surfacePos, in vec3 surfaceNormal, in vec3 viewIncident)
{
return singleLightContribution(light, surfacePos, surfaceNormal, viewIncident, vec2(0.0F));
}
#endif

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#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);
}

View file

@ -0,0 +1,195 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
//-------------------------------------------------------------------------------------------------
// This file takes the incoming GltfShadeMaterial (material uploaded in a buffer) and
// evaluates it, basically sample the textures and return the struct PbrMaterial
// which is used by the Bsdf functions to evaluate and sample the material
//
#ifndef MAT_EVAL_H
#define MAT_EVAL_H 1
#include "pbr_mat_struct.h"
// This is the list of all textures
#ifndef MAT_EVAL_TEXTURE_ARRAY
#define MAT_EVAL_TEXTURE_ARRAY texturesMap
#endif
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// MATERIAL FOR EVALUATION
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
const float g_min_reflectance = 0.04F;
//-----------------------------------------------------------------------
// sRGB to linear approximation, see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
vec4 srgbToLinear(in vec4 sRgb)
{
//return vec4(pow(sRgb.xyz, vec3(2.2f)), sRgb.w);
vec3 rgb = sRgb.xyz * (sRgb.xyz * (sRgb.xyz * 0.305306011F + 0.682171111F) + 0.012522878F);
return vec4(rgb, sRgb.a);
}
//-----------------------------------------------------------------------
// From the incoming material return the material for evaluating PBR
//-----------------------------------------------------------------------
PbrMaterial evaluateMaterial(in GltfShadeMaterial material, in vec3 normal, in vec3 tangent, in vec3 bitangent, in vec2 texCoord, in bool isInside)
{
float perceptual_roughness = 0.0F;
float metallic = 0.0F;
vec3 f0 = vec3(0.0F);
vec3 f90 = vec3(1.0F);
vec4 baseColor = vec4(0.0F, 0.0F, 0.0F, 1.0F);
// KHR_texture_transform
texCoord = vec2(vec3(texCoord, 1) * material.uvTransform);
// Normal Map
if(material.normalTexture > -1)
{
mat3 tbn = mat3(tangent, bitangent, normal);
vec3 normal_vector = texture(MAT_EVAL_TEXTURE_ARRAY[nonuniformEXT(material.normalTexture)], texCoord).xyz;
normal_vector = normal_vector * 2.0F - 1.0F;
normal_vector *= vec3(material.normalTextureScale, material.normalTextureScale, 1.0F);
normal = normalize(tbn * normal_vector);
}
// Metallic-Roughness
{
perceptual_roughness = material.pbrRoughnessFactor;
metallic = material.pbrMetallicFactor;
if(material.pbrMetallicRoughnessTexture > -1.0F)
{
// Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel.
vec4 mr_sample = texture(MAT_EVAL_TEXTURE_ARRAY[nonuniformEXT(material.pbrMetallicRoughnessTexture)], texCoord);
perceptual_roughness *= mr_sample.g;
metallic *= mr_sample.b;
}
// The albedo may be defined from a base texture or a flat color
baseColor = material.pbrBaseColorFactor;
if(material.pbrBaseColorTexture > -1.0F)
{
baseColor *= texture(MAT_EVAL_TEXTURE_ARRAY[nonuniformEXT(material.pbrBaseColorTexture)], texCoord);
}
vec3 specular_color = mix(vec3(g_min_reflectance), vec3(baseColor), float(metallic));
f0 = specular_color;
}
// Protection
metallic = clamp(metallic, 0.0F, 1.0F);
// Emissive term
vec3 emissive = material.emissiveFactor;
if(material.emissiveTexture > -1.0F)
{
emissive *= vec3(texture(MAT_EVAL_TEXTURE_ARRAY[material.emissiveTexture], texCoord));
}
// KHR_materials_specular
// https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular
vec4 specularColorTexture = vec4(1.0F);
if(material.specularColorTexture > -1)
{
specularColorTexture = textureLod(texturesMap[nonuniformEXT(material.specularColorTexture)], texCoord, 0);
}
float specularTexture = 1.0F;
if(material.specularTexture > -1)
{
specularTexture = textureLod(texturesMap[nonuniformEXT(material.specularTexture)], texCoord, 0).a;
}
// Dielectric Specular
float ior1 = 1.0F;
float ior2 = material.ior;
if(isInside)
{
ior1 = material.ior;
ior2 = 1.0F;
}
float iorRatio = ((ior1 - ior2) / (ior1 + ior2));
float iorRatioSqr = iorRatio * iorRatio;
vec3 dielectricSpecularF0 = material.specularColorFactor * specularColorTexture.rgb;
float dielectricSpecularF90 = material.specularFactor * specularTexture;
f0 = mix(min(iorRatioSqr * dielectricSpecularF0, vec3(1.0F)) * dielectricSpecularF0, baseColor.rgb, metallic);
f90 = vec3(mix(dielectricSpecularF90, 1.0F, metallic));
// Material Evaluated
PbrMaterial pbrMat;
pbrMat.albedo = baseColor;
pbrMat.f0 = f0;
pbrMat.f90 = f90;
pbrMat.roughness = perceptual_roughness;
pbrMat.metallic = metallic;
pbrMat.emissive = max(vec3(0.0F), emissive);
pbrMat.normal = normal;
pbrMat.eta = (material.thicknessFactor == 0.0F) ? 1.0F : ior1 / ior2;
// KHR_materials_transmission
pbrMat.transmissionFactor = material.transmissionFactor;
if(material.transmissionTexture > -1)
{
pbrMat.transmissionFactor *= textureLod(texturesMap[nonuniformEXT(material.transmissionTexture)], texCoord, 0).r;
}
// KHR_materials_ior
pbrMat.ior = material.ior;
// KHR_materials_volume
pbrMat.attenuationColor = material.attenuationColor;
pbrMat.attenuationDistance = material.attenuationDistance;
pbrMat.thicknessFactor = material.thicknessFactor;
// KHR_materials_clearcoat
pbrMat.clearcoatFactor = material.clearcoatFactor;
pbrMat.clearcoatRoughness = material.clearcoatRoughness;
if(material.clearcoatTexture > -1)
{
pbrMat.clearcoatFactor *= textureLod(texturesMap[nonuniformEXT(material.clearcoatTexture)], texCoord, 0).r;
}
if(material.clearcoatRoughnessTexture > -1)
{
pbrMat.clearcoatRoughness *= textureLod(texturesMap[nonuniformEXT(material.clearcoatRoughnessTexture)], texCoord, 0).g;
}
pbrMat.clearcoatRoughness = max(pbrMat.clearcoatRoughness, 0.001F);
return pbrMat;
}
PbrMaterial evaluateMaterial(in GltfShadeMaterial material, in vec3 normal, in vec3 tangent, in vec3 bitangent, in vec2 texCoord)
{
return evaluateMaterial(material, normal, tangent, bitangent, texCoord, false);
}
#endif // MAT_EVAL_H

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/// @DOC_SKIP
#ifndef PBR_MAT_STRUCT_H
#define PBR_MAT_STRUCT_H 1
struct PbrMaterial
{
vec4 albedo; // base color
float roughness; // 0 = smooth, 1 = rough
float metallic; // 0 = dielectric, 1 = metallic
vec3 normal; // shading normal
vec3 emissive; // emissive color
vec3 f0; // full reflectance color (n incidence angle)
vec3 f90; // reflectance color at grazing angle
float eta; // index of refraction
float specularWeight; // product of specularFactor and specularTexture.a
float transmissionFactor; // KHR_materials_transmission
float ior; // KHR_materials_ior
vec3 attenuationColor; // KHR_materials_volume
float attenuationDistance; // KHR_materials_volume
float thicknessFactor; // KHR_materials_volume
float clearcoatFactor; // KHR_materials_clearcoat
float clearcoatRoughness; // KHR_materials_clearcoat
vec3 clearcoatNormal; // KHR_materials_clearcoat
};
#endif

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef RANDOM_GLSL
#define RANDOM_GLSL 1
precision highp float;
// Generate a seed for the random generator.
// Input - pixel.x, pixel.y, frame_nb
// From https://github.com/Cyan4973/xxHash, https://www.shadertoy.com/view/XlGcRh
uint xxhash32(uvec3 p)
{
const uvec4 primes = uvec4(2246822519U, 3266489917U, 668265263U, 374761393U);
uint h32;
h32 = p.z + primes.w + p.x * primes.y;
h32 = primes.z * ((h32 << 17) | (h32 >> (32 - 17)));
h32 += p.y * primes.y;
h32 = primes.z * ((h32 << 17) | (h32 >> (32 - 17)));
h32 = primes.x * (h32 ^ (h32 >> 15));
h32 = primes.y * (h32 ^ (h32 >> 13));
return h32 ^ (h32 >> 16);
}
//-----------------------------------------------------------------------
// https://www.pcg-random.org/
//-----------------------------------------------------------------------
uint pcg(inout uint state)
{
uint prev = state * 747796405u + 2891336453u;
uint word = ((prev >> ((prev >> 28u) + 4u)) ^ prev) * 277803737u;
state = prev;
return (word >> 22u) ^ word;
}
//-----------------------------------------------------------------------
// Generate a random float in [0, 1) given the previous RNG state
//-----------------------------------------------------------------------
float rand(inout uint seed)
{
uint r = pcg(seed);
return float(r) * (1.F / float(0xffffffffu));
}
#endif // RANDOM_GLSL

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef RAY_UTIL_H
#define RAY_UTIL_H 1
precision highp float;
//-------------------------------------------------------------------------------------------------
// Avoiding self intersections
//-----------------------------------------------------------------------
vec3 offsetRay(in vec3 p, in vec3 n)
{
// Smallest epsilon that can be added without losing precision is 1.19209e-07, but we play safe
const float epsilon = 1.0f / 65536.0f; // Safe epsilon
float magnitude = length(p);
float offset = epsilon * magnitude;
// multiply the direction vector by the smallest offset
vec3 offsetVector = n * offset;
// add the offset vector to the starting point
vec3 offsetPoint = p + offsetVector;
return offsetPoint;
}
// Hacking the shadow terminator
// https://jo.dreggn.org/home/2021_terminator.pdf
// p : point of intersection
// p[a..c]: position of the triangle
// n[a..c]: normal of the triangle
// bary: barycentric coordinate of the hit position
// return the offset position
vec3 pointOffset(vec3 p, vec3 pa, vec3 pb, vec3 pc, vec3 na, vec3 nb, vec3 nc, vec3 bary)
{
vec3 tmpu = p - pa;
vec3 tmpv = p - pb;
vec3 tmpw = p - pc;
float dotu = min(0.0F, dot(tmpu, na));
float dotv = min(0.0F, dot(tmpv, nb));
float dotw = min(0.0F, dot(tmpw, nc));
tmpu -= dotu * na;
tmpv -= dotv * nb;
tmpw -= dotw * nc;
vec3 pP = p + tmpu * bary.x + tmpv * bary.y + tmpw * bary.z;
return pP;
}
#endif

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#version 450
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#include "dh_sky.h"
layout(local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;
layout(set = 0, binding = eSkyOutImage) writeonly uniform image2D g_out_hdr;
layout(set = 0, binding = eSkyParam) uniform SkyInfo_
{
ProceduralSkyShaderParameters skyInfo;
};
layout(push_constant) uniform SkyDomePushConstant_
{
SkyPushConstant pc;
};
void main()
{
const vec2 pixel_center = vec2(gl_GlobalInvocationID.xy) + vec2(0.5F);
const vec2 in_uv = pixel_center / vec2(imageSize(g_out_hdr));
const vec2 d = in_uv * 2.0 - 1.0;
vec3 direction = normalize(vec3(pc.mvp * vec4(d.x, d.y, 1.0F, 1.0F)));
vec3 color = proceduralSky(skyInfo, direction, 0.0F);
imageStore(g_out_hdr, ivec2(gl_GlobalInvocationID.xy), vec4(color, 1.0F));
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#version 450
#extension GL_GOOGLE_include_directive : enable
#include "dh_tonemap.h"
#include "dh_comp.h"
layout(set = 0, binding = eTonemapperInput) uniform sampler2D g_image;
layout(set = 0, binding = eTonemapperOutput) writeonly uniform image2D g_out_image;
layout(push_constant) uniform shaderInformation
{
Tonemapper tm;
};
layout(local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;
void main()
{
if(gl_GlobalInvocationID.xy != clamp(gl_GlobalInvocationID.xy, vec2(0.0F), imageSize(g_out_image)))
return;
const vec2 pixel_center = vec2(gl_GlobalInvocationID.xy) + vec2(0.5F);
const vec2 i_uv = pixel_center / vec2(imageSize(g_out_image));
vec4 R = texture(g_image, i_uv);
if(tm.isActive == 1)
R.xyz = applyTonemap(tm, R.xyz, i_uv);
R.a = 1.0F; // No alpha, or it will blend with ImGui black viewport
imageStore(g_out_image, ivec2(gl_GlobalInvocationID.xy), R);
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#version 450
#extension GL_GOOGLE_include_directive : enable
#include "dh_tonemap.h"
layout(location = 0) in vec2 i_uv;
layout(location = 0) out vec4 o_color;
layout(set = 0, binding = eTonemapperInput) uniform sampler2D g_image;
layout(push_constant) uniform shaderInformation
{
Tonemapper tm;
};
void main()
{
vec4 R = texture(g_image, i_uv);
if(tm.isActive == 1)
R.xyz = applyTonemap(tm, R.xyz, i_uv);
o_color = R;
}

View file

@ -0,0 +1,166 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "imgui/imgui_helper.h"
#include "nvh/nvprint.hpp"
#include "nvvk/shaders_vk.hpp"
#include "nvvk/descriptorsets_vk.hpp"
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/context_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
#include <glm/gtc/constants.hpp>
using namespace glm;
#include "sky.hpp"
#include "nvvkhl/shaders/dh_comp.h"
#include "_autogen/sky.comp.h"
namespace nvvkhl {
SkyDome::SkyDome(nvvk::Context* ctx, nvvk::ResourceAllocator* allocator)
{
setup(ctx->m_device, allocator);
}
void SkyDome::setup(const VkDevice& device, nvvk::ResourceAllocator* allocator)
{
m_device = device;
m_alloc = allocator;
m_debug.setup(device);
m_skyInfoBuf = m_alloc->createBuffer(sizeof(nvvkhl_shaders::ProceduralSkyShaderParameters),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
NAME2_VK(m_skyInfoBuf.buffer, "SkyInfo");
// Descriptor: the output image and parameters
nvvk::DescriptorSetBindings bind;
bind.addBinding(nvvkhl_shaders::SkyBindings::eSkyOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT);
bind.addBinding(nvvkhl_shaders::SkyBindings::eSkyParam, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL);
m_skyDLayout = bind.createLayout(m_device);
m_skyDPool = bind.createPool(m_device);
m_skyDSet = nvvk::allocateDescriptorSet(m_device, m_skyDPool, m_skyDLayout);
// Write parameters information
std::vector<VkWriteDescriptorSet> writes = {};
VkDescriptorBufferInfo buf_info{m_skyInfoBuf.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(bind.makeWrite(m_skyDSet, nvvkhl_shaders::SkyBindings::eSkyParam, &buf_info));
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
// Creating the pipeline layout
VkPushConstantRange push_constant_ranges = {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(nvvkhl_shaders::SkyPushConstant)};
std::vector<VkDescriptorSetLayout> layouts = {m_skyDLayout};
VkPipelineLayoutCreateInfo create_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
create_info.setLayoutCount = static_cast<uint32_t>(layouts.size());
create_info.pSetLayouts = layouts.data();
create_info.pushConstantRangeCount = 1;
create_info.pPushConstantRanges = &push_constant_ranges;
vkCreatePipelineLayout(m_device, &create_info, nullptr, &m_skyPipelineLayout);
NAME_VK(m_skyPipelineLayout);
// HDR Dome compute shader
VkPipelineShaderStageCreateInfo stage_info{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage_info.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stage_info.module = nvvk::createShaderModule(m_device, sky_comp, sizeof(sky_comp));
stage_info.pName = "main";
VkComputePipelineCreateInfo comp_info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
comp_info.layout = m_skyPipelineLayout;
comp_info.stage = stage_info;
vkCreateComputePipelines(m_device, {}, 1, &comp_info, nullptr, &m_skyPipeline);
NAME_VK(m_skyPipeline);
// Clean up
vkDestroyShaderModule(m_device, comp_info.stage.module, nullptr);
}
void SkyDome::setOutImage(const VkDescriptorImageInfo& outimage)
{
VkWriteDescriptorSet wds{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
wds.dstSet = m_skyDSet;
wds.dstBinding = 0;
wds.descriptorCount = 1;
wds.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
wds.pImageInfo = &outimage;
vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr);
}
void SkyDome::draw(const VkCommandBuffer& cmd, const glm::mat4& view, const glm::mat4& proj, const VkExtent2D& size)
{
LABEL_SCOPE_VK(cmd);
// Information to the compute shader
nvvkhl_shaders::SkyPushConstant pc{};
pc.mvp = glm::inverse(view) * glm::inverse(proj); // This will be to have a world direction vector pointing to the pixel
// Execution
std::vector<VkDescriptorSet> dst_sets = {m_skyDSet};
vkCmdPushConstants(cmd, m_skyPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(nvvkhl_shaders::SkyPushConstant), &pc);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_skyPipelineLayout, 0,
static_cast<uint32_t>(dst_sets.size()), dst_sets.data(), 0, nullptr);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_skyPipeline);
VkExtent2D group_counts = getGroupCounts(size);
vkCmdDispatch(cmd, group_counts.width, group_counts.height, 1);
}
void SkyDome::destroy()
{
m_alloc->destroy(m_skyInfoBuf);
vkDestroyPipeline(m_device, m_skyPipeline, nullptr);
vkDestroyPipelineLayout(m_device, m_skyPipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_skyDLayout, nullptr);
vkDestroyDescriptorPool(m_device, m_skyDPool, nullptr);
}
void SkyDome::updateParameterBuffer(VkCommandBuffer cmd) const
{
nvvkhl_shaders::ProceduralSkyShaderParameters output = fillSkyShaderParameters(m_skyParams);
vkCmdUpdateBuffer(cmd, m_skyInfoBuf.buffer, 0, sizeof(nvvkhl_shaders::ProceduralSkyShaderParameters), &output);
// Make sure the buffer is available when using it
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
mb.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
mb.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &mb, 0, nullptr, 0, nullptr);
}
nvvkhl_shaders::Light SkyDome::getSun() const
{
nvvkhl_shaders::Light sun{};
sun.type = nvvkhl_shaders::eLightTypeDirectional;
sun.angularSizeOrInvRange = m_skyParams.angularSize;
sun.direction = m_skyParams.direction;
sun.color = m_skyParams.color;
sun.intensity = m_skyParams.intensity;
return sun;
}
bool SkyDome::onUI()
{
return skyParametersUI(m_skyParams);
}
} // namespace nvvkhl

View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <glm/glm.hpp>
#include <cstdint> // so uint32_t is available for device_host.h below
#include "shaders/dh_sky.h"
#include "imgui/imgui_helper.h"
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
#include "shaders/dh_lighting.h"
#include "vulkan/vulkan_core.h"
//////////////////////////////////////////////////////////////////////////
namespace nvvk {
class Context;
}
namespace nvvkhl {
struct SkyParameters
{
glm::vec3 skyColor{0.17F, 0.37F, 0.65F};
glm::vec3 horizonColor{0.50F, 0.70F, 0.92F};
glm::vec3 groundColor{0.62F, 0.59F, 0.55F};
glm::vec3 directionUp{0.F, 1.F, 0.F};
float brightness = 0.3F; // scaler for sky brightness
float horizonSize = 30.F; // +/- degrees
float glowSize = 5.F; // degrees, starting from the edge of the light disk
float glowIntensity = 0.1F; // [0-1] relative to light intensity
float glowSharpness = 4.F; // [1-10] is the glow power exponent
float maxLightRadiance = 100.F; // clamp for light radiance derived from its angular size, 0 = no clamp
// Sun
float angularSize = glm::radians(0.53F);
float intensity = 1.0F;
glm::vec3 direction = glm::normalize(glm::vec3{0.0F, -.7F, -.7F});
glm::vec3 color = {1.0F, 1.0F, 1.0F};
};
inline nvvkhl_shaders::ProceduralSkyShaderParameters fillSkyShaderParameters(const SkyParameters& input)
{
nvvkhl_shaders::ProceduralSkyShaderParameters output{};
auto square = [](auto a) { return a * a; };
float light_angular_size = glm::clamp(input.angularSize, glm::radians(0.1F), glm::radians(90.F));
float light_solid_angle = 4.0F * glm::pi<float>() * square(sinf(light_angular_size * 0.5F));
float light_radiance = input.intensity / light_solid_angle;
if(input.maxLightRadiance > 0.F)
{
light_radiance = std::min(light_radiance, input.maxLightRadiance);
}
output.directionToLight = glm::normalize(-input.direction);
output.angularSizeOfLight = light_angular_size;
output.lightColor = light_radiance * input.color;
output.glowSize = glm::radians(glm::clamp(input.glowSize, 0.F, 90.F));
output.skyColor = input.skyColor * input.brightness;
output.glowIntensity = glm::clamp(input.glowIntensity, 0.F, 1.F);
output.horizonColor = input.horizonColor * input.brightness;
output.horizonSize = glm::radians(glm::clamp(input.horizonSize, 0.F, 90.F));
output.groundColor = input.groundColor * input.brightness;
output.glowSharpness = glm::clamp(input.glowSharpness, 1.F, 10.F);
output.directionUp = normalize(input.directionUp);
return output;
}
inline bool skyParametersUI(SkyParameters& skyParams)
{
using PE = ImGuiH::PropertyEditor;
bool changed{false};
glm::vec3 dir = skyParams.direction;
changed |= ImGuiH::azimuthElevationSliders(dir, true, skyParams.directionUp.y == 1.0F);
skyParams.direction = dir;
// clang-format off
changed |= PE::entry("Color", [&]() { return ImGui::ColorEdit3("##1", &skyParams.color.x, ImGuiColorEditFlags_Float); });
changed |= PE::entry("Irradiance", [&]() { return ImGui::SliderFloat("##1", &skyParams.intensity, 0.F, 100.F, "%.2f", ImGuiSliderFlags_Logarithmic); });
changed |= PE::entry("Angular Size", [&]() { return ImGui::SliderAngle("##1", &skyParams.angularSize, 0.1F, 20.F); });
// clang-format on
if(PE::treeNode("Extra"))
{
// clang-format off
changed |= PE::entry("Brightness", [&]() { return ImGui::SliderFloat("Brightness", &skyParams.brightness, 0.F, 1.F); });
changed |= PE::entry("Glow Size", [&]() { return ImGui::SliderFloat("Glow Size", &skyParams.glowSize, 0.F, 90.F); });
changed |= PE::entry("Glow Sharpness", [&]() { return ImGui::SliderFloat("Glow Sharpness", &skyParams.glowSharpness, 1.F, 10.F); });
changed |= PE::entry("Glow Intensity", [&]() { return ImGui::SliderFloat("Glow Intensity", &skyParams.glowIntensity, 0.F, 1.F); });
changed |= PE::entry("Horizon Size", [&]() { return ImGui::SliderFloat("Horizon Size", &skyParams.horizonSize, 0.F, 90.F); });
changed |= PE::entry("Sky Color", [&]() { return ImGui::ColorEdit3("Sky Color", &skyParams.skyColor.x, ImGuiColorEditFlags_Float); });
changed |= PE::entry("Horizon Color", [&]() { return ImGui::ColorEdit3("Horizon Color", &skyParams.horizonColor.x, ImGuiColorEditFlags_Float); });
changed |= PE::entry("Ground Color", [&]() { return ImGui::ColorEdit3("Ground Color", &skyParams.groundColor.x, ImGuiColorEditFlags_Float); });
// clang-format on
PE::treePop();
}
return changed;
}
/** @DOC_START
# class nvvkhl::SkyDome
> This class is responsible for the sky dome.
This class can render a sky dome with a sun, for both the rasterizer and the ray tracer.
The `draw` method is responsible for rendering the sky dome for the rasterizer. For ray tracing, there is no need to call this method, as the sky dome is part of the ray tracing shaders (see shaders/dh_sky.h).
@DOC_END */
class SkyDome
{
public:
SkyDome(nvvk::Context* ctx, nvvk::ResourceAllocator* allocator);
void setup(const VkDevice& device, nvvk::ResourceAllocator* allocator);
void setOutImage(const VkDescriptorImageInfo& outimage);
void draw(const VkCommandBuffer& cmd, const glm::mat4& view, const glm::mat4& proj, const VkExtent2D& size);
void destroy();
void updateParameterBuffer(VkCommandBuffer cmd) const;
bool onUI();
nvvkhl_shaders::Light getSun() const;
VkDescriptorSetLayout getDescriptorSetLayout() const { return m_skyDLayout; };
VkDescriptorSet getDescriptorSet() const { return m_skyDSet; };
SkyParameters& skyParams() { return m_skyParams; }
private:
// Resources
VkDevice m_device{VK_NULL_HANDLE};
nvvk::ResourceAllocator* m_alloc{nullptr};
nvvk::DebugUtil m_debug;
// To draw the Sky in image
VkDescriptorSet m_skyDSet{VK_NULL_HANDLE};
VkDescriptorSetLayout m_skyDLayout{VK_NULL_HANDLE};
VkDescriptorPool m_skyDPool{VK_NULL_HANDLE};
VkPipeline m_skyPipeline{VK_NULL_HANDLE};
VkPipelineLayout m_skyPipelineLayout{VK_NULL_HANDLE};
nvvk::Buffer m_skyInfoBuf; // Device-Host of Sky Params
SkyParameters m_skyParams{};
};
} // namespace nvvkhl

View file

@ -0,0 +1,203 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include <vulkan/vulkan_core.h>
#include "imgui/imgui_helper.h"
#include "nvvk/context_vk.hpp"
#include "nvvk/descriptorsets_vk.hpp"
#include "nvvk/pipeline_vk.hpp"
#include "nvvk/shaders_vk.hpp"
#include "nvvk/debug_util_vk.hpp"
#include "shaders/dh_comp.h"
#include "_autogen/passthrough.vert.h"
#include "_autogen/tonemapper.frag.h"
#include "_autogen/tonemapper.comp.h"
#include "tonemap_postprocess.hpp"
namespace nvvkhl {
TonemapperPostProcess::TonemapperPostProcess(nvvk::Context* ctx, nvvk::ResourceAllocator* alloc)
: m_ctx(ctx)
, m_dutil(std::make_unique<nvvk::DebugUtil>(ctx->m_device))
, m_dsetGraphics(std::make_unique<nvvk::DescriptorSetContainer>(ctx->m_device))
, m_dsetCompute(std::make_unique<nvvk::DescriptorSetContainer>(ctx->m_device))
{
m_settings = nvvkhl_shaders::defaultTonemapper();
}
TonemapperPostProcess::~TonemapperPostProcess()
{
vkDestroyPipeline(m_ctx->m_device, m_graphicsPipeline, nullptr);
vkDestroyPipeline(m_ctx->m_device, m_computePipeline, nullptr);
m_dsetGraphics->deinit();
m_dsetCompute->deinit();
}
void TonemapperPostProcess::createGraphicPipeline(VkFormat colorFormat, VkFormat depthFormat)
{
m_mode = TmMode::eGraphic;
auto& d = m_dsetGraphics;
d->addBinding(nvvkhl_shaders::eTonemapperInput, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
d->initLayout(VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR);
m_dutil->DBG_NAME(d->getLayout());
VkPushConstantRange push_constant_ranges = {VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(nvvkhl_shaders::Tonemapper)};
d->initPipeLayout(1, &push_constant_ranges);
m_dutil->DBG_NAME(d->getPipeLayout());
VkPipelineRenderingCreateInfo prend_info{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
prend_info.colorAttachmentCount = 1;
prend_info.pColorAttachmentFormats = &colorFormat;
prend_info.depthAttachmentFormat = depthFormat;
// Creating the Pipeline
nvvk::GraphicsPipelineState pstate;
pstate.rasterizationState.cullMode = VK_CULL_MODE_NONE;
nvvk::GraphicsPipelineGenerator pgen(m_ctx->m_device, d->getPipeLayout(), prend_info, pstate);
pgen.addShader(std::vector<uint32_t>{std::begin(passthrough_vert), std::end(passthrough_vert)}, VK_SHADER_STAGE_VERTEX_BIT);
pgen.addShader(std::vector<uint32_t>{std::begin(tonemapper_frag), std::end(tonemapper_frag)}, VK_SHADER_STAGE_FRAGMENT_BIT);
m_graphicsPipeline = pgen.createPipeline();
m_dutil->DBG_NAME(m_graphicsPipeline);
pgen.clearShaders();
}
void TonemapperPostProcess::createComputePipeline()
{
m_mode = TmMode::eCompute;
nvvk::DebugUtil dbg(m_ctx->m_device);
auto& d = m_dsetCompute;
d->addBinding(nvvkhl_shaders::eTonemapperInput, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT);
d->addBinding(nvvkhl_shaders::eTonemapperOutput, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT);
d->initLayout(VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR);
m_dutil->DBG_NAME(d->getLayout());
VkPushConstantRange push_constant_ranges = {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(nvvkhl_shaders::Tonemapper)};
d->initPipeLayout(1, &push_constant_ranges);
m_dutil->DBG_NAME(d->getPipeLayout());
VkPipelineShaderStageCreateInfo stage_info{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage_info.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stage_info.module = nvvk::createShaderModule(m_ctx->m_device, tonemapper_comp, sizeof(tonemapper_comp));
stage_info.pName = "main";
VkComputePipelineCreateInfo comp_info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
comp_info.layout = d->getPipeLayout();
comp_info.stage = stage_info;
vkCreateComputePipelines(m_ctx->m_device, {}, 1, &comp_info, nullptr, &m_computePipeline);
m_dutil->DBG_NAME(m_computePipeline);
// Clean up
vkDestroyShaderModule(m_ctx->m_device, comp_info.stage.module, nullptr);
}
void TonemapperPostProcess::updateGraphicDescriptorSets(VkDescriptorImageInfo inImage)
{
assert(m_mode == TmMode::eGraphic);
m_iimage = std::make_unique<VkDescriptorImageInfo>(inImage);
m_writes.clear();
m_writes.emplace_back(m_dsetGraphics->makeWrite(0, nvvkhl_shaders::eTonemapperInput, m_iimage.get()));
}
void TonemapperPostProcess::updateComputeDescriptorSets(VkDescriptorImageInfo inImage, VkDescriptorImageInfo outImage)
{
assert(m_mode == TmMode::eCompute);
m_iimage = std::make_unique<VkDescriptorImageInfo>(inImage);
m_oimage = std::make_unique<VkDescriptorImageInfo>(outImage);
m_writes.clear();
m_writes.emplace_back(m_dsetCompute->makeWrite(0, nvvkhl_shaders::eTonemapperInput, m_iimage.get()));
m_writes.emplace_back(m_dsetCompute->makeWrite(0, nvvkhl_shaders::eTonemapperOutput, m_oimage.get()));
}
void TonemapperPostProcess::runGraphic(VkCommandBuffer cmd)
{
assert(m_mode == TmMode::eGraphic);
auto sdbg = m_dutil->DBG_SCOPE(cmd);
vkCmdPushConstants(cmd, m_dsetGraphics->getPipeLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(nvvkhl_shaders::Tonemapper), &m_settings);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_graphicsPipeline);
vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_dsetGraphics->getPipeLayout(), 0,
static_cast<uint32_t>(m_writes.size()), m_writes.data());
vkCmdDraw(cmd, 3, 1, 0, 0);
}
void TonemapperPostProcess::runCompute(VkCommandBuffer cmd, const VkExtent2D& size)
{
assert(m_mode == TmMode::eCompute);
auto sdbg = m_dutil->DBG_SCOPE(cmd);
vkCmdPushConstants(cmd, m_dsetCompute->getPipeLayout(), VK_SHADER_STAGE_COMPUTE_BIT, 0,
sizeof(nvvkhl_shaders::Tonemapper), &m_settings);
vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_dsetCompute->getPipeLayout(), 0,
static_cast<uint32_t>(m_writes.size()), m_writes.data());
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_computePipeline);
VkExtent2D group_counts = getGroupCounts(size);
vkCmdDispatch(cmd, group_counts.width, group_counts.height, 1);
}
bool TonemapperPostProcess::onUI()
{
bool changed{false};
const char* items[] = {"Filmic", "Uncharted", "Gamma"};
using namespace ImGuiH;
PropertyEditor::begin();
changed |= PropertyEditor::entry("Method", [&]() {
return ImGui::Combo("combo", &m_settings.method, items, IM_ARRAYSIZE(items));
});
changed |= PropertyEditor::entry("Active", [&]() {
return ImGui::Checkbox("##1", reinterpret_cast<bool*>(&m_settings.isActive));
});
changed |=
PropertyEditor::entry("Exposure", [&]() { return ImGui::SliderFloat("##1", &m_settings.exposure, 0.001F, 5.0F); });
changed |=
PropertyEditor::entry("Brightness", [&]() { return ImGui::SliderFloat("##1", &m_settings.brightness, 0.0F, 2.0F); });
changed |=
PropertyEditor::entry("Contrast", [&]() { return ImGui::SliderFloat("##1", &m_settings.contrast, 0.0F, 2.0F); });
changed |=
PropertyEditor::entry("Saturation", [&]() { return ImGui::SliderFloat("##1", &m_settings.saturation, 0.0F, 2.0F); });
changed |=
PropertyEditor::entry("Vignette", [&]() { return ImGui::SliderFloat("##1", &m_settings.vignette, 0.0F, 1.0F); });
ImGui::BeginDisabled(m_settings.method == nvvkhl_shaders::eTonemapFilmic);
changed |= PropertyEditor::entry("Gamma", [&]() { return ImGui::SliderFloat("##1", &m_settings.gamma, 1.0F, 2.2F); });
ImGui::EndDisabled();
if(PropertyEditor::entry(
" ", [&]() { return ImGui::SmallButton("reset"); }, "Resetting to the original values"))
{
m_settings = nvvkhl_shaders::defaultTonemapper();
changed = true;
}
PropertyEditor::end();
return changed;
}
} // namespace nvvkhl

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <memory>
#include "shaders/dh_tonemap.h"
#include "nvvk/descriptorsets_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
/** @DOC_START
# class nvvkhl::TonemapperPostProcess
> This class is meant to be use for displaying the final image rendered in linear space (sRGB).
There are two ways to use it, one which is graphic, the other is compute.
- The graphic will render a full screen quad with the input image. It is to the
application to set the rendering target ( -> G-Buffer0 )
- The compute takes an image as input and write to an another one using a compute shader
- It is either one or the other, both rendering aren't needed to post-process. If both are provided
it is for convenience.
Note: It is important in any cases to place a barrier if there is a transition from
fragment to compute and compute to fragment to avoid missing results.
@DOC_END */
// Forward declarations
namespace nvvk {
class Context;
class DebugUtil;
} // namespace nvvk
namespace nvvkhl {
struct TonemapperPostProcess
{
TonemapperPostProcess(nvvk::Context* ctx, nvvk::ResourceAllocator* alloc);
~TonemapperPostProcess();
void createGraphicPipeline(VkFormat colorFormat, VkFormat depthFormat);
void createComputePipeline();
// It sets the in and out images used by the compute and graphic shaders.
// The inImage, is the image you want to tonemap, the outImage is the image
// in which the compute shader will be writing to. In graphics, you have to attach
// a framebuffer, then the rendering will be done in the attached framebuffer image.
void updateGraphicDescriptorSets(VkDescriptorImageInfo inImage);
void updateComputeDescriptorSets(VkDescriptorImageInfo inImage, VkDescriptorImageInfo outImage);
void runGraphic(VkCommandBuffer cmd);
void runCompute(VkCommandBuffer cmd, const VkExtent2D& size);
bool onUI(); // Display UI of the tonemapper
void setSettings(const nvvkhl_shaders::Tonemapper& settings) { m_settings = settings; }
nvvkhl_shaders::Tonemapper& settings() { return m_settings; }; // returning access to setting values
private:
nvvk::Context* m_ctx{nullptr};
std::unique_ptr<nvvk::DebugUtil> m_dutil;
std::unique_ptr<nvvk::DescriptorSetContainer> m_dsetGraphics; // Holding the descriptor set information
std::unique_ptr<nvvk::DescriptorSetContainer> m_dsetCompute; // Holding the descriptor set information
VkPipeline m_graphicsPipeline{VK_NULL_HANDLE}; // The graphic pipeline to render
VkPipeline m_computePipeline{VK_NULL_HANDLE}; // The graphic pipeline to render
nvvkhl_shaders::Tonemapper m_settings;
// To use VK_KHR_push_descriptor
std::unique_ptr<VkDescriptorImageInfo> m_iimage;
std::unique_ptr<VkDescriptorImageInfo> m_oimage;
std::vector<VkWriteDescriptorSet> m_writes;
enum class TmMode
{
eNone,
eGraphic,
eCompute
} m_mode{TmMode::eNone};
};
} // namespace nvvkhl