MDL SDK API nvidia_logo_transpbg.gif Up
Example for Compiled Distribution Functions (GLSL)
[Previous] [Up] [Next]

This example illustrates generating target code for BSDFs using the GLSL backend and using it to implement a small physically-based path tracer in Vulkan using a compute shader. The renderer illuminates a sphere with a HDR environment map and supports MDL materials using the following expressions:

  • scattering.surface
  • scattering.backface
  • scattering.emission
  • thin_walled
  • volume.absorption
  • geometry.cutout_opacity

New Topics

  • Generating target code for distribution functions (GLSL)
  • Using generated distribution functions (GLSL)

Detailed Description

Generating target code for distribution functions (GLSL)


Generating GLSL code for distribution functions involves the following steps:

  • Load the module in which the material is contained
  • Instantiate a material instance and compile it
  • Using the GLSL backend to generate target code from the compiled material

The first two steps have been covered in previous examples, so let's focus on generating the target code.

Before generating GLSL code the backend has to be obtained and configured using mi::neuraylib::IMdl_backend::set_option(). For a list of possible options (e.g. enabling auxiliary outputs) see the documentation of the set_option method. For a more in depth guide on how to configure the backend for Vulkan please refer to Detailed Description.

mdl_backend_api->get_backend(mi::neuraylib::IMdl_backend_api::MB_GLSL));
be_glsl->set_option("glsl_version", "450");
be_glsl->set_option("glsl_place_uniforms_into_ssbo", "on");
be_glsl->set_option("glsl_max_const_data", "0");
be_glsl->set_option("glsl_uniform_ssbo_binding", "1");
be_glsl->set_option("glsl_uniform_ssbo_set", "0");
...
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
@ MB_GLSL
Generate GLSL code.
Definition: imdl_backend_api.h:63

The functions can be generated with mi::neuraylib::IMdl_backend::translate_material_df(), mi::neuraylib::ILink_unit::add_material_df() or mi::neuraylib::ILink_unit::add_material(). When generating code for multiple materials or simply many DFs, it's generally advisable to use a link unit so no duplicate code is generated and the compiler can employ better optimizations. Using mi::neuraylib::ILink_unit::add_material() provides the most information on success, such as argument block and function indices which are, however, not needed here, so either of the link unit's functions are fine in this example.

GLSL does not support function pointers, so instead of keeping track of different BSDF functions with indices as in previous examples, we must choose unique names for each subexpression that will be used in the renderer's GLSL code to call the generated functions. By default these function base names are "lambda0", "lambda1", and so on. We will choose more descriptive names to make the renderer's code more readable by specifying base_fname. The final function names that are generated will be suffixed by _init, _sample, _evaluate, and _pdf. For expressions that are not DFs, such as thin_walled and geometry.cutout_opacity the base_fname is simply the final name of the generated function.

Lastly, the three functions have a parameter include_geometry_normal that can be specified using the execution context to make the initialization function replace state->normal by the result of the expression connected to geometry.normal of the material. By default include_geometry_normal is set to true.

std::vector<mi::neuraylib::Target_function_description> function_descs;
function_descs.emplace_back("surface.scattering", "mdl_bsdf"); // Generate BSDF
function_descs.emplace_back("surface.emission.emission", "mdl_emission"); // Generate EDF
function_descs.emplace_back("thin_walled", "mdl_thin_walled"); // non-DF expression
...
context->set_option("include_geometry_normal", true); // Optional, defaults to true anyway
link_unit->add_material(compiled_material, function_descs.data(), function_descs.size(), context);
be_glsl->translate_link_unit(link_unit.get(), context));

Using generated distribution functions (GLSL)


The generated code will contain the generated functions, for example mdl_bsdf_init, mdl_bsdf_sample, mdl_bsdf_evaluate, and mdl_bsdf_pdf, which can be called directly in the renderer's shader:

// Init
mdl_bsdf_init(state);
// Sample
Bsdf_sample_data bsdf_sample_data;
bsdf_sample_data.ior1 = ior1;
bsdf_sample_data.ior2 = ior2;
bsdf_sample_data.k1 = -ray_state.dir;
bsdf_sample_data.xi = vec4(rnd(seed), rnd(seed), rnd(seed), rnd(seed));
mdl_bsdf_sample(bsdf_sample_data, state);
// Evaluate
Bsdf_evaluate_data bsdf_eval_data;
bsdf_eval_data.ior1 = ior1;
bsdf_eval_data.ior2 = ior2;
bsdf_eval_data.k1 = -ray_state.dir;
bsdf_eval_data.k2 = to_light;
mdl_bsdf_evaluate(bsdf_eval_data, state);

Each function takes in the current material shading state and a data structure that contains input values as well as the computed output values (the init function only takes the shading state).

As an example the Bsdf_sample_data and Bsdf_evaluate_data structures are defined as follows:

struct Bsdf_sample_data
{
/*Input*/ vec3 ior1; // IOR current med
/*Input*/ vec3 ior2; // IOR other side
/*Input*/ vec3 k1; // outgoing direction
/*Output*/ vec3 k2; // incoming direction
/*Input*/ vec4 xi; // pseudo-random sample number
/*Output*/ float pdf; // pdf (non-projected hemisphere)
/*Output*/ vec3 bsdf_over_pdf; // bsdf * dot(normal, k2) / pdf
/*Output*/ int event_type; // the type of event for the generated sample
/*Output*/ int handle; // handle of the sampled elemental BSDF (lobe)
};
struct Bsdf_evaluate_data
{
/*Input*/ vec3 ior1; // IOR current medium
/*Input*/ vec3 ior2; // IOR other side
/*Input*/ vec3 k1; // outgoing direction
/*Input*/ vec3 k2; // incoming direction
/*Output*/ vec3 bsdf_diffuse; // bsdf_diffuse * dot(normal, k2)
/*Output*/ vec3 bsdf_glossy; // bsdf_glossy * dot(normal, k2)
/*Output*/ float pdf; // pdf (non-projected hemisphere)
};

For the definitions of all structures and function prototypes see the top of examples/mdl_sdk/df_vulkan/mdl_runtime.glsl or look at the generated GLSL code for your material. Also for more information on how to use the distribution functions refer to Detailed Description.

Example Source

To compile the source code, you require a recent version of the Vulkan SDK and GLFW. For detailed instructions, please refer to the Getting Started section.

Source Code Location: examples/mdl_sdk/df_vulkan/example_df_vulkan.cpp

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/df_vulkan/example_df_vulkan.cpp
//
// Simple Vulkan renderer using compiled BSDFs with a material parameter editor GUI.
#include "example_shared.h"
#include "example_vulkan_shared.h"
#include <vulkan/vulkan.h>
#include <GLFW/glfw3.h>
#include <numeric>
#define _USE_MATH_DEFINES
#include <math.h>
#include <cassert>
static const VkFormat g_accumulation_texture_format = VK_FORMAT_R32G32B32A32_SFLOAT;
// Local group size for the path tracing compute shader
static const uint32_t g_local_size_x = 16;
static const uint32_t g_local_size_y = 8;
// Descriptor set bindings. Used as a define in the shaders.
static const uint32_t g_binding_beauty_buffer = 0;
static const uint32_t g_binding_aux_albedo_buffer = 1;
static const uint32_t g_binding_aux_normal_buffer = 2;
static const uint32_t g_binding_render_params = 3;
static const uint32_t g_binding_environment_map = 4;
static const uint32_t g_binding_environment_sampling_data = 5;
static const uint32_t g_binding_material_textures_indices = 6;
static const uint32_t g_binding_material_textures_2d = 7;
static const uint32_t g_binding_material_textures_3d = 8;
static const uint32_t g_binding_ro_data_buffer = 9;
static const uint32_t g_binding_argument_block_buffer = 10;
static const uint32_t g_set_ro_data_buffer = 0;
static const uint32_t g_set_argument_block_buffer = 0;
static const uint32_t g_set_material_textures = 0;
// Command line options structure.
struct Options
{
bool no_window = false;
std::string output_file = "output.exr";
uint32_t res_x = 1024;
uint32_t res_y = 1024;
uint32_t num_images = 3;
uint32_t samples_per_pixel = 4096;
uint32_t samples_per_iteration = 8;
uint32_t max_path_length = 4;
float cam_fov = 96.0f;
mi::Float32_3 cam_pos = { 0.0f, 0.0f, 3.0f };
mi::Float32_3 light_pos = { 10.0f, 0.0f, 5.0f };
mi::Float32_3 light_intensity = { 0.0f, 0.0f, 0.0f };
std::string hdr_file = "nvidia/sdk_examples/resources/environment.hdr";
float hdr_intensity = 1.0f;
bool use_class_compilation = true;
bool enable_ro_segment = false;
bool disable_ssbo = false;
uint32_t max_const_data = 1024;
std::string material_name = "::nvidia::sdk_examples::tutorials::example_df";
bool enable_validation_layers = false;
bool dump_glsl = false;
bool enable_bsdf_flags = false;
mi::neuraylib::Df_flags allowed_scatter_mode =
mi::neuraylib::DF_FLAGS_ALLOW_REFLECT_AND_TRANSMIT;
};
struct Vulkan_texture
{
VkImage image = nullptr;
VkImageView image_view = nullptr;
VkDeviceMemory device_memory = nullptr;
void destroy(VkDevice device)
{
vkDestroyImageView(device, image_view, nullptr);
vkDestroyImage(device, image, nullptr);
vkFreeMemory(device, device_memory, nullptr);
}
};
struct Vulkan_buffer
{
VkBuffer buffer = nullptr;
VkDeviceMemory device_memory = nullptr;
void* mapped_data = nullptr; // unused for device-local buffers
void destroy(VkDevice device)
{
if (mapped_data)
{
vkUnmapMemory(device, device_memory);
mapped_data = nullptr;
}
vkDestroyBuffer(device, buffer, nullptr);
vkFreeMemory(device, device_memory, nullptr);
}
};
Vulkan_buffer create_storage_buffer(
VkDevice device,
VkPhysicalDevice physical_device,
VkQueue queue,
VkCommandPool command_pool,
const void* buffer_data,
mi::Size buffer_size)
{
Vulkan_buffer storage_buffer;
{ // Create the storage buffer in device local memory (VRAM)
VkBufferCreateInfo buffer_create_info = {};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.size = buffer_size;
buffer_create_info.usage
= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VK_CHECK(vkCreateBuffer(
device, &buffer_create_info, nullptr, &storage_buffer.buffer));
// Allocate device memory for the buffer.
storage_buffer.device_memory = mi::examples::vk::allocate_and_bind_buffer_memory(
device, physical_device, storage_buffer.buffer,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
{
mi::examples::vk::Staging_buffer staging_buffer(
device, physical_device, buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
// Memcpy the data into the staging buffer
void* mapped_data = staging_buffer.map_memory();
std::memcpy(mapped_data, buffer_data, buffer_size);
staging_buffer.unmap_memory();
// Upload the data from the staging buffer into the storage buffer
mi::examples::vk::Temporary_command_buffer command_buffer(device, command_pool);
command_buffer.begin();
VkBufferCopy copy_region = {};
copy_region.size = buffer_size;
vkCmdCopyBuffer(command_buffer.get(),
staging_buffer.get(), storage_buffer.buffer, 1, &copy_region);
command_buffer.end_and_submit(queue);
}
return storage_buffer;
}
//------------------------------------------------------------------------------
// MDL-Vulkan resource interop
//------------------------------------------------------------------------------
// Creates the storage buffer for the material's read-only data.
Vulkan_buffer create_ro_data_buffer(
VkDevice device,
VkPhysicalDevice physical_device,
VkQueue queue,
VkCommandPool command_pool,
const mi::neuraylib::ITarget_code* target_code)
{
mi::Size num_segments = target_code->get_ro_data_segment_count();
if (num_segments == 0)
return {}; // empty buffer
if (num_segments > 1)
{
std::cerr << "Multiple data segments (SSBOs) are defined for read-only data."
<< " This should not be the case if a storage buffer is used.\n";
terminate();
}
return create_storage_buffer(device, physical_device, queue, command_pool,
target_code->get_ro_data_segment_data(0), target_code->get_ro_data_segment_size(0));
}
// Creates the storage buffer for the material's argument block. This buffer is used
// for the material's dynamic parameters when using class compilation.
Vulkan_buffer create_argument_block_buffer(
VkDevice device,
VkPhysicalDevice physical_device,
VkQueue queue,
VkCommandPool command_pool,
const mi::neuraylib::ITarget_code* target_code,
mi::Size argument_block_index)
{
if (target_code->get_argument_block_count() == 0)
return {}; // empty buffer
target_code->get_argument_block(argument_block_index));
// We create a device-local storage buffer for the argument block for simplicity since in
// this example we don't have the possibility to change the material parameters.
// This choice is not meant to be a recommendation. The appropriate memory properties and
// buffer update strategies depend on the application.
return create_storage_buffer(device, physical_device, queue, command_pool,
argument_block->get_data(), argument_block->get_size());
}
// Creates the image and image view for the given texture index.
Vulkan_texture create_material_texture(
VkDevice device,
VkPhysicalDevice physical_device,
VkQueue queue,
VkCommandPool command_pool,
const mi::neuraylib::ITarget_code* target_code,
mi::Size texture_index)
{
// Get access to the texture data by the texture database name from the target code.
transaction->access<mi::neuraylib::ITexture>(target_code->get_texture(texture_index)));
transaction->access<mi::neuraylib::IImage>(texture->get_image()));
mi::base::Handle<const mi::neuraylib::ICanvas> canvas(image->get_canvas(0, 0, 0));
mi::Uint32 tex_width = canvas->get_resolution_x();
mi::Uint32 tex_height = canvas->get_resolution_y();
mi::Uint32 tex_layers = canvas->get_layers_size();
char const* image_type = image->get_type(0, 0);
if (image->is_uvtile() || image->is_animated())
{
std::cerr << "The example does not support uvtile and/or animated textures!" << std::endl;
terminate();
}
// For simplicity, the texture access functions are only implemented for float4 and gamma
// is pre-applied here (all images are converted to linear space).
// Convert to linear color space if necessary
if (texture->get_effective_gamma(0, 0) != 1.0f)
{
// Copy/convert to float4 canvas and adjust gamma from "effective gamma" to 1.
image_api->convert(canvas.get(), "Color"));
gamma_canvas->set_gamma(texture->get_effective_gamma(0, 0));
image_api->adjust_gamma(gamma_canvas.get(), 1.0f);
canvas = gamma_canvas;
}
else if (strcmp(image_type, "Color") != 0 && strcmp(image_type, "Float32<4>") != 0)
{
// Convert to expected format
canvas = image_api->convert(canvas.get(), "Color");
}
// Create the Vulkan image
VkImageCreateInfo image_create_info = {};
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
// This example supports only 2D and 3D textures (no PTEX or cube)
= target_code->get_texture_shape(texture_index);
{
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.extent.width = tex_width;
image_create_info.extent.height = tex_height;
image_create_info.extent.depth = 1;
image_create_info.arrayLayers = 1;
image_create_info.mipLevels = 1;
}
{
image_create_info.imageType = VK_IMAGE_TYPE_3D;
image_create_info.extent.width = tex_width;
image_create_info.extent.height = tex_height;
image_create_info.extent.depth = tex_layers;
image_create_info.arrayLayers = 1;
image_create_info.mipLevels = 1;
}
else
{
std::cerr << "Unsupported texture shape!" << std::endl;
terminate();
}
Vulkan_texture material_texture;
VK_CHECK(vkCreateImage(device, &image_create_info, nullptr,
&material_texture.image));
// Allocate device memory for the texture.
material_texture.device_memory = mi::examples::vk::allocate_and_bind_image_memory(
device, physical_device, material_texture.image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
{
size_t layer_size = tex_width * tex_height * sizeof(float) * 4; // RGBA32F
size_t staging_buffer_size = layer_size * tex_layers;
mi::examples::vk::Staging_buffer staging_buffer(device, physical_device,
staging_buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
// Memcpy the read-only data into the staging buffer
uint8_t* mapped_data = static_cast<uint8_t*>(staging_buffer.map_memory());
for (mi::Uint32 layer = 0; layer < tex_layers; layer++)
{
mi::base::Handle<const mi::neuraylib::ITile> tile(canvas->get_tile(layer));
std::memcpy(mapped_data, tile->get_data(), layer_size);
mapped_data += layer_size;
}
staging_buffer.unmap_memory();
// Upload the read-only data from the staging buffer into the storage buffer
mi::examples::vk::Temporary_command_buffer command_buffer(device, command_pool);
command_buffer.begin();
{
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = 0;
image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = material_texture.image;
image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(command_buffer.get(),
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
}
VkBufferImageCopy copy_region = {};
copy_region.bufferOffset = 0;
copy_region.bufferRowLength = 0;
copy_region.bufferImageHeight = 0;
copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_region.imageSubresource.mipLevel = 0;
copy_region.imageSubresource.baseArrayLayer = 0;
copy_region.imageSubresource.layerCount = 1;
copy_region.imageOffset = { 0, 0, 0 };
copy_region.imageExtent = { tex_width, tex_height, tex_layers };
vkCmdCopyBufferToImage(command_buffer.get(), staging_buffer.get(),
material_texture.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
{
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = material_texture.image;
image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(command_buffer.get(),
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
}
command_buffer.end_and_submit(queue);
}
// Create the image view
VkImageViewCreateInfo image_view_create_info = {};
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.image = material_texture.image;
image_view_create_info.viewType = (image_create_info.imageType == VK_IMAGE_TYPE_2D)
? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_3D;
image_view_create_info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.baseMipLevel = 0;
image_view_create_info.subresourceRange.levelCount = 1;
image_view_create_info.subresourceRange.baseArrayLayer = 0;
image_view_create_info.subresourceRange.layerCount = 1;
VK_CHECK(vkCreateImageView(
device, &image_view_create_info, nullptr, &material_texture.image_view));
return material_texture;
}
//------------------------------------------------------------------------------
// Application and rendering logic
//------------------------------------------------------------------------------
class Df_vulkan_app : public mi::examples::vk::Vulkan_example_app
{
public:
Df_vulkan_app(
mi::Size argument_block_index,
const Options& options)
: Vulkan_example_app(mdl_impexp_api.get(), image_api.get())
, m_transaction(transaction)
, m_target_code(target_code)
, m_argument_block_index(argument_block_index)
, m_options(options)
{
}
virtual void init_resources() override;
virtual void cleanup_resources() override;
// All framebuffer size dependent resources need to be recreated
// when the swapchain is recreated due to not being optimal anymore
// or because the window was resized.
virtual void recreate_size_dependent_resources() override;
// Updates the application logic. This is called right before the
// next frame is rendered.
virtual void update(float elapsed_seconds, uint32_t frame_index) override;
// Populates the current frame's command buffer. The base application's
// render pass has already been started at this point.
virtual void render(VkCommandBuffer command_buffer, uint32_t frame_index, uint32_t image_index) override;
// Window event handlers.
virtual void key_callback(int key, int action) override;
virtual void mouse_button_callback(int button, int action) override;
virtual void mouse_scroll_callback(float offset_x, float offset_y) override;
virtual void mouse_move_callback(float pos_x, float pos_y) override;
virtual void resized_callback(uint32_t width, uint32_t height) override;
private:
struct Camera_state
{
float base_distance;
float theta;
float phi;
float zoom;
};
struct Render_params
{
alignas(16) mi::Float32_3 cam_pos;
alignas(16) mi::Float32_3 cam_dir;
alignas(16) mi::Float32_3 cam_right;
alignas(16) mi::Float32_3 cam_up;
float cam_focal;
alignas(16) mi::Float32_3 point_light_pos;
alignas(16) mi::Float32_3 point_light_color;
float point_light_intensity;
float environment_intensity_factor;
float environment_inv_integral;
uint32_t max_path_length;
uint32_t samples_per_iteration;
uint32_t progressive_iteration;
uint32_t bsdf_data_flags;
};
private:
void update_camera_render_params(const Camera_state& cam_state);
void create_material_textures_index_buffer(const std::vector<uint32_t>& indices);
void create_accumulation_images();
VkShaderModule create_path_trace_shader_module();
// Creates the descriptors set layout which is used to create the
// pipeline layout. Here the number of material resources is declared.
void create_descriptor_set_layouts();
// Create the pipeline layout and state for rendering a fullscreen triangle.
void create_pipeline_layouts();
void create_pipelines();
void create_render_params_buffers();
void create_environment_map();
// Creates the descriptor pool and set that hold enough space for all
// material resources, and are used during rendering to access the
// the resources.
void create_descriptor_pool_and_sets();
void update_accumulation_image_descriptors();
private:
mi::Size m_argument_block_index;
Options m_options;
Vulkan_texture m_beauty_texture;
Vulkan_texture m_auxiliary_albedo_texture;
Vulkan_texture m_auxiliary_normal_texture;
VkSampler m_linear_sampler = nullptr;
VkRenderPass m_path_trace_render_pass = nullptr;
VkPipelineLayout m_path_trace_pipeline_layout = nullptr;
VkPipelineLayout m_display_pipeline_layout = nullptr;
VkPipeline m_path_trace_pipeline = nullptr;
VkPipeline m_display_pipeline = nullptr;
VkDescriptorSetLayout m_path_trace_descriptor_set_layout = nullptr;
VkDescriptorSetLayout m_display_descriptor_set_layout = nullptr;
VkDescriptorPool m_descriptor_pool = nullptr;
std::vector<VkDescriptorSet> m_path_trace_descriptor_sets;
VkDescriptorSet m_display_descriptor_set;
std::vector<Vulkan_buffer> m_render_params_buffers;
Vulkan_texture m_environment_map;
Vulkan_buffer m_environment_sampling_data_buffer;
VkSampler m_environment_sampler;
// Material resources
Vulkan_buffer m_ro_data_buffer;
Vulkan_buffer m_argument_block_buffer;
Vulkan_buffer m_material_textures_index_buffer;
std::vector<Vulkan_texture> m_material_textures_2d;
std::vector<Vulkan_texture> m_material_textures_3d;
Render_params m_render_params;
bool m_camera_moved = true; // Force a clear in first frame
uint32_t m_display_buffer_index = 0; // Which buffer to display
// Camera movement
Camera_state m_camera_state;
mi::Float32_2 m_mouse_start;
bool m_camera_moving = false;
};
void Df_vulkan_app::init_resources()
{
glslang::InitializeProcess();
m_linear_sampler = mi::examples::vk::create_linear_sampler(m_device);
// Create the render resources for the material
m_ro_data_buffer = create_ro_data_buffer(m_device, m_physical_device,
m_graphics_queue, m_command_pool, m_target_code.get());
bool is_class_compiled = (m_argument_block_index != mi::Size(-1));
if (is_class_compiled)
{
m_argument_block_buffer = create_argument_block_buffer(m_device, m_physical_device,
m_graphics_queue, m_command_pool, m_target_code.get(), m_argument_block_index);
}
// Record the indices of each texture in their respective array
// e.g. the indices of 2D textures in the m_material_textures_2d array
std::vector<uint32_t> material_textures_indices;
// Create the textures for the material
if (m_target_code->get_texture_count() > 0)
{
// The first texture (index = 0) is always the invalid texture in MDL
material_textures_indices.reserve(m_target_code->get_texture_count() - 1);
for (mi::Size i = 1; i < m_target_code->get_texture_count(); i++)
{
Vulkan_texture texture = create_material_texture(
m_device, m_physical_device, m_graphics_queue, m_command_pool,
m_transaction.get(), m_image_api.get(), m_target_code.get(), i);
switch (m_target_code->get_texture_shape(i))
{
material_textures_indices.push_back(static_cast<uint32_t>(m_material_textures_2d.size()));
m_material_textures_2d.push_back(texture);
break;
material_textures_indices.push_back(static_cast<uint32_t>(m_material_textures_3d.size()));
m_material_textures_3d.push_back(texture);
break;
default:
std::cerr << "Unsupported texture shape!" << std::endl;
terminate();
break;
}
}
}
create_material_textures_index_buffer(material_textures_indices);
create_descriptor_set_layouts();
create_pipeline_layouts();
create_accumulation_images();
create_pipelines();
create_render_params_buffers();
create_environment_map();
create_descriptor_pool_and_sets();
// Initialize render parameters
m_render_params.progressive_iteration = 0;
m_render_params.max_path_length = m_options.max_path_length;
m_render_params.samples_per_iteration = m_options.samples_per_iteration;
m_render_params.bsdf_data_flags = m_options.allowed_scatter_mode;
m_render_params.point_light_pos = m_options.light_pos;
m_render_params.point_light_intensity
= std::max(std::max(m_options.light_intensity.x, m_options.light_intensity.y), m_options.light_intensity.z);
m_render_params.point_light_color = m_render_params.point_light_intensity > 0.0f
? m_options.light_intensity / m_render_params.point_light_intensity
: mi::Float32_3(0.0f, 0.0f, 0.0f);
m_render_params.environment_intensity_factor = m_options.hdr_intensity;
const float fov = m_options.cam_fov;
const float to_radians = static_cast<float>(M_PI / 180.0);
m_render_params.cam_focal = 1.0f / mi::math::tan(fov / 2.0f * to_radians);
// Setup camera
const mi::Float32_3 camera_pos = m_options.cam_pos;
mi::Float32_3 inv_dir = camera_pos / mi::math::length(camera_pos);
m_camera_state.base_distance = mi::math::length(camera_pos);
m_camera_state.phi = mi::math::atan2(inv_dir.x, inv_dir.z);
m_camera_state.theta = mi::math::acos(inv_dir.y);
m_camera_state.zoom = 0;
update_camera_render_params(m_camera_state);
}
void Df_vulkan_app::cleanup_resources()
{
// In headless mode we output the accumulation buffers to files
if (m_options.no_window)
{
std::string filename_base = m_options.output_file;
std::string filename_ext;
size_t dot_pos = m_options.output_file.rfind('.');
if (dot_pos != std::string::npos) {
filename_base = m_options.output_file.substr(0, dot_pos);
filename_ext = m_options.output_file.substr(dot_pos);
}
VkImage output_images[] = {
m_beauty_texture.image,
m_auxiliary_albedo_texture.image,
m_auxiliary_normal_texture.image
};
std::string output_filenames[] = {
filename_base + filename_ext,
filename_base + "_albedo" + filename_ext,
filename_base + "_normal" + filename_ext
};
for (uint32_t i = 0; i < 3; i++)
{
uint32_t bpp = mi::examples::vk::get_image_format_bpp(g_accumulation_texture_format);
std::vector<uint8_t> pixels = mi::examples::vk::copy_image_to_buffer(
m_device, m_physical_device, m_command_pool, m_graphics_queue,
output_images[i], m_image_width, m_image_height, bpp,
VK_IMAGE_LAYOUT_GENERAL, false);
m_image_api->create_canvas("Color", m_image_width, m_image_height));
mi::base::Handle<mi::neuraylib::ITile> tile(canvas->get_tile());
std::memcpy(tile->get_data(), pixels.data(), pixels.size());
canvas = m_image_api->convert(canvas.get(), "Rgb_fp");
m_mdl_impexp_api->export_canvas(output_filenames[i].c_str(), canvas.get());
}
}
// Cleanup resources
m_material_textures_index_buffer.destroy(m_device);
for (Vulkan_texture& texture : m_material_textures_3d)
texture.destroy(m_device);
for (Vulkan_texture& texture : m_material_textures_2d)
texture.destroy(m_device);
m_ro_data_buffer.destroy(m_device);
m_argument_block_buffer.destroy(m_device);
m_environment_sampling_data_buffer.destroy(m_device);
m_environment_map.destroy(m_device);
for (Vulkan_buffer& buffer : m_render_params_buffers)
buffer.destroy(m_device);
vkDestroyDescriptorPool(m_device, m_descriptor_pool, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_display_descriptor_set_layout, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_path_trace_descriptor_set_layout, nullptr);
vkDestroyPipelineLayout(m_device, m_display_pipeline_layout, nullptr);
vkDestroyPipelineLayout(m_device, m_path_trace_pipeline_layout, nullptr);
vkDestroyPipeline(m_device, m_path_trace_pipeline, nullptr);
vkDestroyPipeline(m_device, m_display_pipeline, nullptr);
vkDestroyRenderPass(m_device, m_path_trace_render_pass, nullptr);
vkDestroySampler(m_device, m_linear_sampler, nullptr);
m_auxiliary_normal_texture.destroy(m_device);
m_auxiliary_albedo_texture.destroy(m_device);
m_beauty_texture.destroy(m_device);
vkDestroySampler(m_device, m_environment_sampler, nullptr);
glslang::FinalizeProcess();
}
// All framebuffer size dependent resources need to be recreated
// when the swapchain is recreated due to not being optimal anymore
// or because the window was resized.
void Df_vulkan_app::recreate_size_dependent_resources()
{
vkDestroyPipeline(m_device, m_path_trace_pipeline, nullptr);
vkDestroyPipeline(m_device, m_display_pipeline, nullptr);
vkDestroyRenderPass(m_device, m_path_trace_render_pass, nullptr);
m_auxiliary_normal_texture.destroy(m_device);
m_auxiliary_albedo_texture.destroy(m_device);
m_beauty_texture.destroy(m_device);
create_accumulation_images();
create_pipelines();
update_accumulation_image_descriptors();
}
// Updates the application logic. This is called right before the
// next frame is rendered.
void Df_vulkan_app::update(float elapsed_seconds, uint32_t frame_index)
{
if (m_camera_moved)
m_render_params.progressive_iteration = 0;
// Update current frame's render params uniform buffer
std::memcpy(m_render_params_buffers[frame_index].mapped_data,
&m_render_params, sizeof(Render_params));
m_render_params.progressive_iteration += m_options.samples_per_iteration;
if (!m_options.no_window)
{
std::string window_title = "MDL SDK DF Vulkan Example | Press keys 1 - 3 for output buffers | Iteration: ";
window_title += std::to_string(m_render_params.progressive_iteration);
glfwSetWindowTitle(m_window, window_title.c_str());
}
}
// Populates the current frame's command buffer. The base application's
// render pass has already been started at this point.
void Df_vulkan_app::render(VkCommandBuffer command_buffer, uint32_t frame_index, uint32_t image_index)
{
const VkImage accum_images[] = {
m_beauty_texture.image,
m_auxiliary_albedo_texture.image,
m_auxiliary_normal_texture.image
};
// Path trace compute pass
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, m_path_trace_pipeline);
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE,
m_path_trace_pipeline_layout, 0, 1, &m_path_trace_descriptor_sets[frame_index],
0, nullptr);
if (m_camera_moved)
{
m_camera_moved = false;
for (VkImage image : accum_images)
{
VkClearColorValue clear_color = { 0.0f, 0.0f, 0.0f, 0.0f };
VkImageSubresourceRange clear_range;
clear_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
clear_range.baseMipLevel = 0;
clear_range.levelCount = 1;
clear_range.baseArrayLayer = 0;
clear_range.layerCount = 1;
vkCmdClearColorImage(command_buffer,
image, VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &clear_range);
}
}
uint32_t group_count_x = (m_image_width + g_local_size_x - 1) / g_local_size_x;
uint32_t group_count_y = (m_image_width + g_local_size_y - 1) / g_local_size_y;
vkCmdDispatch(command_buffer, group_count_x, group_count_y, 1);
for (VkImage image : accum_images)
{
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image;
image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(command_buffer,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
}
// Display render pass
VkRenderPassBeginInfo render_pass_begin_info = {};
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin_info.renderPass = m_main_render_pass;
render_pass_begin_info.framebuffer = m_framebuffers[image_index];
render_pass_begin_info.renderArea = { {0, 0}, {m_image_width, m_image_height} };
VkClearValue clear_values[2];
clear_values[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
clear_values[1].depthStencil = { 1.0f, 0 };
render_pass_begin_info.clearValueCount = std::size(clear_values);
render_pass_begin_info.pClearValues = clear_values;
vkCmdBeginRenderPass(
command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(
command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_display_pipeline);
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_display_pipeline_layout, 0, 1, &m_display_descriptor_set, 0, nullptr);
vkCmdPushConstants(command_buffer, m_display_pipeline_layout,
VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(uint32_t), &m_display_buffer_index);
vkCmdDraw(command_buffer, 3, 1, 0, 0);
vkCmdEndRenderPass(command_buffer);
}
// Handles keyboard input from the window.
void Df_vulkan_app::key_callback(int key, int action)
{
// Handle only key press events
if (action != GLFW_PRESS)
return;
if (key == GLFW_KEY_ENTER)
request_screenshot();
if (key == GLFW_KEY_SPACE)
m_camera_moved = true;
if (key >= GLFW_KEY_1 && key <= GLFW_KEY_3)
m_display_buffer_index = key - GLFW_KEY_1;
}
void Df_vulkan_app::mouse_button_callback(int button, int action)
{
if (button == GLFW_MOUSE_BUTTON_LEFT)
{
m_camera_moving = (action == GLFW_PRESS);
double mouse_x, mouse_y;
glfwGetCursorPos(m_window, &mouse_x, &mouse_y);
m_mouse_start.x = static_cast<float>(mouse_x);
m_mouse_start.y = static_cast<float>(mouse_y);
}
}
void Df_vulkan_app::mouse_scroll_callback(float offset_x, float offset_y)
{
if (offset_y < 0.0f)
m_camera_state.zoom -= 1.0f;
else if (offset_y > 0.0f)
m_camera_state.zoom += 1.0f;
update_camera_render_params(m_camera_state);
m_camera_moved = true;
}
void Df_vulkan_app::mouse_move_callback(float pos_x, float pos_y)
{
if (m_camera_moving)
{
float dx = pos_x - m_mouse_start.x;
float dy = pos_y - m_mouse_start.y;
m_mouse_start.x = pos_x;
m_mouse_start.y = pos_y;
m_camera_state.phi -= static_cast<float>(dx * 0.001f * M_PI);
m_camera_state.theta -= static_cast<float>(dy * 0.001f * M_PI);
m_camera_state.theta = mi::math::max(
m_camera_state.theta, static_cast<float>(0.0f * M_PI));
m_camera_state.theta = mi::math::min(
m_camera_state.theta, static_cast<float>(1.0f * M_PI));
update_camera_render_params(m_camera_state);
m_camera_moved = true;
}
}
// Gets called when the window is resized.
void Df_vulkan_app::resized_callback(uint32_t width, uint32_t height)
{
m_camera_moved = true;
}
void Df_vulkan_app::update_camera_render_params(const Camera_state& cam_state)
{
m_render_params.cam_dir.x = -mi::math::sin(cam_state.phi) * mi::math::sin(cam_state.theta);
m_render_params.cam_dir.y = -mi::math::cos(cam_state.theta);
m_render_params.cam_dir.z = -mi::math::cos(cam_state.phi) * mi::math::sin(cam_state.theta);
m_render_params.cam_right.x = mi::math::cos(cam_state.phi);
m_render_params.cam_right.y = 0.0f;
m_render_params.cam_right.z = -mi::math::sin(cam_state.phi);
m_render_params.cam_up.x = -mi::math::sin(cam_state.phi) * mi::math::cos(cam_state.theta);
m_render_params.cam_up.y = mi::math::sin(cam_state.theta);
m_render_params.cam_up.z = -mi::math::cos(cam_state.phi) * mi::math::cos(cam_state.theta);
const float dist = cam_state.base_distance * mi::math::pow(0.95f, cam_state.zoom);
m_render_params.cam_pos.x = -m_render_params.cam_dir.x * dist;
m_render_params.cam_pos.y = -m_render_params.cam_dir.y * dist;
m_render_params.cam_pos.z = -m_render_params.cam_dir.z * dist;
}
void Df_vulkan_app::create_material_textures_index_buffer(const std::vector<uint32_t>& indices)
{
if (indices.empty())
return;
// The uniform buffer has std140 layout which means each array entry must be the size of a vec4 (16 byte)
std::vector<uint32_t> buffer_data(indices.size() * 4);
for (size_t i = 0; i < indices.size(); i++)
buffer_data[i * 4] = indices[i];
const size_t num_buffer_data_bytes = buffer_data.size() * sizeof(uint32_t);
{ // Create the uniform buffer in device local memory (VRAM)
VkBufferCreateInfo buffer_create_info = {};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.size = num_buffer_data_bytes;
buffer_create_info.usage
= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VK_CHECK(vkCreateBuffer(
m_device, &buffer_create_info, nullptr, &m_material_textures_index_buffer.buffer));
// Allocate device memory for the buffer.
m_material_textures_index_buffer.device_memory = mi::examples::vk::allocate_and_bind_buffer_memory(
m_device, m_physical_device, m_material_textures_index_buffer.buffer,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
{
mi::examples::vk::Staging_buffer staging_buffer(m_device, m_physical_device,
num_buffer_data_bytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
// Memcpy the read-only data into the staging buffer
void* mapped_data = staging_buffer.map_memory();
std::memcpy(mapped_data, buffer_data.data(), num_buffer_data_bytes);
staging_buffer.unmap_memory();
// Upload the read-only data from the staging buffer into the storage buffer
mi::examples::vk::Temporary_command_buffer command_buffer(m_device, m_command_pool);
command_buffer.begin();
VkBufferCopy copy_region = {};
copy_region.size = num_buffer_data_bytes;
vkCmdCopyBuffer(command_buffer.get(),
staging_buffer.get(), m_material_textures_index_buffer.buffer, 1, &copy_region);
command_buffer.end_and_submit(m_graphics_queue);
}
}
void Df_vulkan_app::create_accumulation_images()
{
VkImageCreateInfo image_create_info = {};
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = g_accumulation_texture_format;
image_create_info.extent.width = m_image_width;
image_create_info.extent.height = m_image_height;
image_create_info.extent.depth = 1;
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_STORAGE_BIT
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VK_CHECK(vkCreateImage(m_device, &image_create_info, nullptr, &m_beauty_texture.image));
VK_CHECK(vkCreateImage(m_device, &image_create_info, nullptr, &m_auxiliary_albedo_texture.image));
VK_CHECK(vkCreateImage(m_device, &image_create_info, nullptr, &m_auxiliary_normal_texture.image));
m_beauty_texture.device_memory = mi::examples::vk::allocate_and_bind_image_memory(
m_device, m_physical_device, m_beauty_texture.image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
m_auxiliary_albedo_texture.device_memory = mi::examples::vk::allocate_and_bind_image_memory(
m_device, m_physical_device, m_auxiliary_albedo_texture.image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
m_auxiliary_normal_texture.device_memory = mi::examples::vk::allocate_and_bind_image_memory(
m_device, m_physical_device, m_auxiliary_normal_texture.image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
{ // Transition image layout
mi::examples::vk::Temporary_command_buffer command_buffer(m_device, m_command_pool);
command_buffer.begin();
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = 0;
image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = 1;
const VkImage images_to_transition[] = {
m_beauty_texture.image,
m_auxiliary_albedo_texture.image,
m_auxiliary_normal_texture.image
};
for (VkImage image : images_to_transition)
{
image_memory_barrier.image = image;
vkCmdPipelineBarrier(command_buffer.get(),
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
}
command_buffer.end_and_submit(m_graphics_queue);
}
VkImageViewCreateInfo image_view_create_info = {};
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = g_accumulation_texture_format;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.baseMipLevel = 0;
image_view_create_info.subresourceRange.levelCount = 1;
image_view_create_info.subresourceRange.baseArrayLayer = 0;
image_view_create_info.subresourceRange.layerCount = 1;
image_view_create_info.image = m_beauty_texture.image;
VK_CHECK(vkCreateImageView(
m_device, &image_view_create_info, nullptr, &m_beauty_texture.image_view));
image_view_create_info.image = m_auxiliary_albedo_texture.image;
VK_CHECK(vkCreateImageView(
m_device, &image_view_create_info, nullptr, &m_auxiliary_albedo_texture.image_view));
image_view_create_info.image = m_auxiliary_normal_texture.image;
VK_CHECK(vkCreateImageView(
m_device, &image_view_create_info, nullptr, &m_auxiliary_normal_texture.image_view));
}
VkShaderModule Df_vulkan_app::create_path_trace_shader_module()
{
std::string df_glsl_source = m_target_code->get_code();
std::string path_trace_shader_source = mi::examples::io::read_text_file(
mi::examples::io::get_executable_folder() + "/" + "path_trace.comp");
std::vector<std::string> defines;
defines.push_back(";LOCAL_SIZE_X=" + std::to_string(g_local_size_x));
defines.push_back(";LOCAL_SIZE_Y=" + std::to_string(g_local_size_y));
defines.push_back(";BINDING_RENDER_PARAMS=" + std::to_string(g_binding_render_params));
defines.push_back(";BINDING_ENV_MAP=" + std::to_string(g_binding_environment_map));
defines.push_back(";BINDING_ENV_MAP_SAMPLING_DATA=" + std::to_string(g_binding_environment_sampling_data));
defines.push_back(";BINDING_BEAUTY_BUFFER=" + std::to_string(g_binding_beauty_buffer));
defines.push_back(";BINDING_AUX_ALBEDO_BUFFER=" + std::to_string(g_binding_aux_albedo_buffer));
defines.push_back(";BINDING_AUX_NORMAL_BUFFER=" + std::to_string(g_binding_aux_normal_buffer));
defines.push_back(";NUM_MATERIAL_TEXTURES_2D=" + std::to_string(m_material_textures_2d.size()));
defines.push_back(";NUM_MATERIAL_TEXTURES_3D=" + std::to_string(m_material_textures_3d.size()));
defines.push_back(";SET_MATERIAL_TEXTURES_INDICES=" + std::to_string(g_set_material_textures));
defines.push_back(";SET_MATERIAL_TEXTURES_2D=" + std::to_string(g_set_material_textures));
defines.push_back(";SET_MATERIAL_TEXTURES_3D=" + std::to_string(g_set_material_textures));
defines.push_back(";SET_MATERIAL_ARGUMENT_BLOCK=" + std::to_string(g_set_argument_block_buffer));
defines.push_back(";SET_MATERIAL_RO_DATA_SEGMENT=" + std::to_string(g_set_ro_data_buffer));
defines.push_back(";BINDING_MATERIAL_TEXTURES_INDICES=" + std::to_string(g_binding_material_textures_indices));
defines.push_back(";BINDING_MATERIAL_TEXTURES_2D=" + std::to_string(g_binding_material_textures_2d));
defines.push_back(";BINDING_MATERIAL_TEXTURES_3D=" + std::to_string(g_binding_material_textures_3d));
defines.push_back(";BINDING_MATERIAL_ARGUMENT_BLOCK=" + std::to_string(g_binding_argument_block_buffer));
defines.push_back(";BINDING_MATERIAL_RO_DATA_SEGMENT=" + std::to_string(g_binding_ro_data_buffer));
if (m_options.enable_ro_segment)
defines.push_back(";USE_RO_DATA_SEGMENT");
// Check if functions for backface were generated
for (mi::Size i = 0; i < m_target_code->get_callable_function_count(); i++)
{
const char* fname = m_target_code->get_callable_function(i);
if (std::strcmp(fname, "mdl_backface_bsdf_sample") == 0)
defines.push_back(";HAS_BACKFACE_BSDF");
else if (std::strcmp(fname, "mdl_backface_edf_sample") == 0)
defines.push_back(";HAS_BACKFACE_EDF");
else if (std::strcmp(fname, "mdl_backface_emission_intensity") == 0)
defines.push_back(";HAS_BACKFACE_EMISSION_INTENSITY");
}
return mi::examples::vk::create_shader_module_from_sources(m_device,
{ df_glsl_source, path_trace_shader_source }, EShLangCompute, defines);
}
// Creates the descriptors set layout which is used to create the
// pipeline layout. Here the number of material resources is declared.
void Df_vulkan_app::create_descriptor_set_layouts()
{
{
VkDescriptorSetLayoutBinding render_params_layout_binding = {};
render_params_layout_binding.binding = g_binding_render_params;
render_params_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
render_params_layout_binding.descriptorCount = 1;
render_params_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding env_map_layout_binding = {};
env_map_layout_binding.binding = g_binding_environment_map;
env_map_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
env_map_layout_binding.descriptorCount = 1;
env_map_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding env_sampling_data_layout_binding = {};
env_sampling_data_layout_binding.binding = g_binding_environment_sampling_data;
env_sampling_data_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
env_sampling_data_layout_binding.descriptorCount = 1;
env_sampling_data_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding textures_indices_layout_binding = {};
textures_indices_layout_binding.binding = g_binding_material_textures_indices;
textures_indices_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
textures_indices_layout_binding.descriptorCount = 1;
textures_indices_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding textures_2d_layout_binding = {};
textures_2d_layout_binding.binding = g_binding_material_textures_2d;
textures_2d_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
textures_2d_layout_binding.descriptorCount
= static_cast<uint32_t>(m_material_textures_2d.size());
textures_2d_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding textures_3d_layout_binding = {};
textures_3d_layout_binding.binding = g_binding_material_textures_3d;
textures_3d_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
textures_3d_layout_binding.descriptorCount
= static_cast<uint32_t>(m_material_textures_3d.size());
textures_3d_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding beauty_buffer_layout_binding = {};
beauty_buffer_layout_binding.binding = g_binding_beauty_buffer;
beauty_buffer_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
beauty_buffer_layout_binding.descriptorCount = 1;
beauty_buffer_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding aux_albedo_buffer_layout_binding
= beauty_buffer_layout_binding;
aux_albedo_buffer_layout_binding.binding = g_binding_aux_albedo_buffer;
VkDescriptorSetLayoutBinding aux_albedo_normal_layout_binding
= beauty_buffer_layout_binding;
aux_albedo_normal_layout_binding.binding = g_binding_aux_normal_buffer;
VkDescriptorSetLayoutBinding ro_data_buffer_layout_binding = {};
ro_data_buffer_layout_binding.binding = g_binding_ro_data_buffer;
ro_data_buffer_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
ro_data_buffer_layout_binding.descriptorCount = 1;
ro_data_buffer_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutBinding argument_block_buffer_layout_binding = {};
argument_block_buffer_layout_binding.binding = g_binding_argument_block_buffer;
argument_block_buffer_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
argument_block_buffer_layout_binding.descriptorCount = 1;
argument_block_buffer_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
const VkDescriptorSetLayoutBinding bindings[] = {
render_params_layout_binding,
env_map_layout_binding,
env_sampling_data_layout_binding,
textures_indices_layout_binding,
textures_2d_layout_binding,
textures_3d_layout_binding,
beauty_buffer_layout_binding,
aux_albedo_buffer_layout_binding,
aux_albedo_normal_layout_binding,
ro_data_buffer_layout_binding,
argument_block_buffer_layout_binding
};
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = {};
descriptor_set_layout_create_info.sType
= VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_set_layout_create_info.bindingCount = std::size(bindings);
descriptor_set_layout_create_info.pBindings = bindings;
VK_CHECK(vkCreateDescriptorSetLayout(
m_device, &descriptor_set_layout_create_info, nullptr, &m_path_trace_descriptor_set_layout));
}
{
VkDescriptorSetLayoutBinding layout_bindings[3];
for (uint32_t i = 0; i < 3; i++)
{
layout_bindings[i].binding = i;
layout_bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
layout_bindings[i].descriptorCount = 1;
layout_bindings[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
layout_bindings[i].pImmutableSamplers = nullptr;
}
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = {};
descriptor_set_layout_create_info.sType
= VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_set_layout_create_info.bindingCount = std::size(layout_bindings);
descriptor_set_layout_create_info.pBindings = layout_bindings;
VK_CHECK(vkCreateDescriptorSetLayout(
m_device, &descriptor_set_layout_create_info, nullptr, &m_display_descriptor_set_layout));
}
}
void Df_vulkan_app::create_pipeline_layouts()
{
{ // Path trace compute pipeline layout
VkPipelineLayoutCreateInfo pipeline_layout_create_info = {};
pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_create_info.setLayoutCount = 1;
pipeline_layout_create_info.pSetLayouts = &m_path_trace_descriptor_set_layout;
pipeline_layout_create_info.pushConstantRangeCount = 0;
pipeline_layout_create_info.pPushConstantRanges = nullptr;
VK_CHECK(vkCreatePipelineLayout(
m_device, &pipeline_layout_create_info, nullptr, &m_path_trace_pipeline_layout));
}
{ // Display graphics pipeline layout
VkPushConstantRange push_constant_range;
push_constant_range.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
push_constant_range.offset = 0;
push_constant_range.size = sizeof(uint32_t);
VkPipelineLayoutCreateInfo pipeline_layout_create_info = {};
pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_create_info.setLayoutCount = 1;
pipeline_layout_create_info.pSetLayouts = &m_display_descriptor_set_layout;
pipeline_layout_create_info.pushConstantRangeCount = 1;
pipeline_layout_create_info.pPushConstantRanges = &push_constant_range;
VK_CHECK(vkCreatePipelineLayout(
m_device, &pipeline_layout_create_info, nullptr, &m_display_pipeline_layout));
}
}
void Df_vulkan_app::create_pipelines()
{
{ // Create path trace compute pipeline
VkShaderModule path_trace_compute_shader = create_path_trace_shader_module();
VkPipelineShaderStageCreateInfo compute_shader_stage = {};
compute_shader_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
compute_shader_stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
compute_shader_stage.module = path_trace_compute_shader;
compute_shader_stage.pName = "main";
VkComputePipelineCreateInfo pipeline_create_info = {};
pipeline_create_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
pipeline_create_info.stage = compute_shader_stage;
pipeline_create_info.layout = m_path_trace_pipeline_layout;
vkCreateComputePipelines(
m_device, nullptr, 1, &pipeline_create_info, nullptr, &m_path_trace_pipeline);
vkDestroyShaderModule(m_device, path_trace_compute_shader, nullptr);
}
{ // Create display graphics pipeline
VkShaderModule fullscreen_triangle_vertex_shader
= mi::examples::vk::create_shader_module_from_file(
m_device, "display.vert", EShLangVertex);
VkShaderModule display_fragment_shader
= mi::examples::vk::create_shader_module_from_file(
m_device, "display.frag", EShLangFragment);
VkPipelineVertexInputStateCreateInfo vertex_input_state = {};
vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {};
input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
input_assembly_state.primitiveRestartEnable = false;
VkViewport viewport = { 0.0f, 0.0f, (float)m_image_width, (float)m_image_height, 0.0f, 1.0f };
VkRect2D scissor_rect = { {0, 0}, {m_image_width, m_image_height} };
VkPipelineViewportStateCreateInfo viewport_state = {};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport_state.viewportCount = 1;
viewport_state.pViewports = &viewport;
viewport_state.scissorCount = 1;
viewport_state.pScissors = &scissor_rect;
VkPipelineRasterizationStateCreateInfo rasterization_state = {};
rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization_state.depthClampEnable = false;
rasterization_state.rasterizerDiscardEnable = false;
rasterization_state.polygonMode = VK_POLYGON_MODE_FILL;
rasterization_state.cullMode = VK_CULL_MODE_BACK_BIT;
rasterization_state.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterization_state.depthBiasEnable = false;
rasterization_state.lineWidth = 1.0f;
VkPipelineDepthStencilStateCreateInfo depth_stencil_state = {};
depth_stencil_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil_state.depthTestEnable = false;
depth_stencil_state.depthWriteEnable = false;
depth_stencil_state.depthBoundsTestEnable = false;
depth_stencil_state.stencilTestEnable = false;
VkPipelineMultisampleStateCreateInfo multisample_state = {};
multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisample_state.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineShaderStageCreateInfo vertex_shader_stage_info = {};
vertex_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertex_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertex_shader_stage_info.module = fullscreen_triangle_vertex_shader;
vertex_shader_stage_info.pName = "main";
VkPipelineShaderStageCreateInfo fragment_shader_stage_info = {};
fragment_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragment_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragment_shader_stage_info.module = display_fragment_shader;
fragment_shader_stage_info.pName = "main";
const VkPipelineShaderStageCreateInfo shader_stages[] = {
vertex_shader_stage_info,
fragment_shader_stage_info
};
VkPipelineColorBlendAttachmentState color_blend_attachment = {};
color_blend_attachment.blendEnable = false;
color_blend_attachment.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
| VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo color_blend_state = {};
color_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
color_blend_state.logicOpEnable = false;
color_blend_state.attachmentCount = 1;
color_blend_state.pAttachments = &color_blend_attachment;
VkGraphicsPipelineCreateInfo pipeline_create_info = {};
pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_create_info.stageCount = std::size(shader_stages);
pipeline_create_info.pStages = shader_stages;
pipeline_create_info.pVertexInputState = &vertex_input_state;
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
pipeline_create_info.pViewportState = &viewport_state;
pipeline_create_info.pRasterizationState = &rasterization_state;
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
pipeline_create_info.pMultisampleState = &multisample_state;
pipeline_create_info.pColorBlendState = &color_blend_state;
pipeline_create_info.layout = m_display_pipeline_layout;
pipeline_create_info.renderPass = m_main_render_pass;
pipeline_create_info.subpass = 0;
VK_CHECK(vkCreateGraphicsPipelines(
m_device, nullptr, 1, &pipeline_create_info, nullptr, &m_display_pipeline));
vkDestroyShaderModule(m_device, fullscreen_triangle_vertex_shader, nullptr);
vkDestroyShaderModule(m_device, display_fragment_shader, nullptr);
}
}
void Df_vulkan_app::create_render_params_buffers()
{
m_render_params_buffers.resize(m_image_count);
for (uint32_t i = 0; i < m_image_count; i++)
{
VkBufferCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
create_info.size = sizeof(Render_params);
create_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_CHECK(vkCreateBuffer(
m_device, &create_info, nullptr, &m_render_params_buffers[i].buffer));
m_render_params_buffers[i].device_memory = mi::examples::vk::allocate_and_bind_buffer_memory(
m_device, m_physical_device, m_render_params_buffers[i].buffer,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VK_CHECK(vkMapMemory(m_device, m_render_params_buffers[i].device_memory,
0, sizeof(Render_params), 0, &m_render_params_buffers[i].mapped_data));
}
}
void Df_vulkan_app::create_environment_map()
{
// Load environment texture
m_transaction->create<mi::neuraylib::IImage>("Image"));
check_success(image->reset_file(m_options.hdr_file.c_str()) == 0);
mi::base::Handle<const mi::neuraylib::ICanvas> canvas(image->get_canvas(0, 0, 0));
const mi::Uint32 res_x = canvas->get_resolution_x();
const mi::Uint32 res_y = canvas->get_resolution_y();
// Check, whether we need to convert the image
char const* image_type = image->get_type(0, 0);
if (strcmp(image_type, "Color") != 0 && strcmp(image_type, "Float32<4>") != 0)
canvas = m_image_api->convert(canvas.get(), "Color");
// Create the Vulkan texture
VkImageCreateInfo image_create_info = {};
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
image_create_info.extent = { res_x, res_y, 1 };
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VK_CHECK(vkCreateImage(m_device, &image_create_info, nullptr, &m_environment_map.image));
m_environment_map.device_memory = mi::examples::vk::allocate_and_bind_image_memory(
m_device, m_physical_device, m_environment_map.image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VkImageViewCreateInfo image_view_create_info = {};
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.image = m_environment_map.image;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = image_create_info.format;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.baseMipLevel = 0;
image_view_create_info.subresourceRange.levelCount = 1;
image_view_create_info.subresourceRange.baseArrayLayer = 0;
image_view_create_info.subresourceRange.layerCount = 1;
VK_CHECK(vkCreateImageView(
m_device, &image_view_create_info, nullptr, &m_environment_map.image_view));
{ // Upload image data to the GPU
size_t staging_buffer_size = res_x * res_y * sizeof(float) * 4; // RGBA32F
mi::examples::vk::Staging_buffer staging_buffer(m_device, m_physical_device,
staging_buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
// Memcpy the read-only data into the staging buffer
void* mapped_data = staging_buffer.map_memory();
std::memcpy(mapped_data, tile->get_data(), staging_buffer_size);
staging_buffer.unmap_memory();
// Upload the read-only data from the staging buffer into the storage buffer
mi::examples::vk::Temporary_command_buffer command_buffer(m_device, m_command_pool);
command_buffer.begin();
{
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = 0;
image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = m_environment_map.image;
image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(command_buffer.get(),
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
}
VkBufferImageCopy copy_region = {};
copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_region.imageSubresource.layerCount = 1;
copy_region.imageExtent = { res_x, res_y, 1 };
vkCmdCopyBufferToImage(
command_buffer.get(), staging_buffer.get(), m_environment_map.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
{
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = m_environment_map.image;
image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(command_buffer.get(),
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
}
command_buffer.end_and_submit(m_graphics_queue);
}
// Create alias map
struct Env_accel {
uint32_t alias;
float q;
};
auto build_alias_map = [](
const std::vector<float>& data,
std::vector<Env_accel>& accel) -> float
{
// Create qs (normalized)
float sum = std::accumulate(data.begin(), data.end(), 0.0f);
uint32_t size = static_cast<uint32_t>(data.size());
for (uint32_t i = 0; i < size; i++)
accel[i].q = static_cast<float>(size) * data[i] / sum;
// Create partition table
std::vector<uint32_t> partition_table(size);
uint32_t s = 0;
uint32_t large = size;
for (uint32_t i = 0; i < size; i++)
partition_table[(accel[i].q < 1.0f) ? (s++) : (--large)] = accel[i].alias = i;
// Create alias map
for (s = 0; s < large && large < size; ++s)
{
uint32_t j = partition_table[s];
uint32_t k = partition_table[large];
accel[j].alias = k;
accel[k].q += accel[j].q - 1.0f;
large = (accel[k].q < 1.0f) ? (large + 1) : large;
}
return sum;
};
// Create importance sampling data
const float* pixels = static_cast<const float*>(tile->get_data());
std::vector<Env_accel> env_accel_data(res_x * res_y);
std::vector<float> importance_data(res_x * res_y);
float cos_theta0 = 1.0f;
const float step_phi = static_cast<float>(2.0 * M_PI / res_x);
const float step_theta = static_cast<float>(M_PI / res_y);
for (uint32_t y = 0; y < res_y; 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;
cos_theta0 = cos_theta1;
for (uint32_t x = 0; x < res_x; x++) {
const uint32_t idx = y * res_x + x;
const uint32_t idx4 = idx * 4;
const float max_channel
= std::max(pixels[idx4], std::max(pixels[idx4 + 1], pixels[idx4 + 2]));
importance_data[idx] = area * max_channel;
}
}
float integral = build_alias_map(importance_data, env_accel_data);
m_render_params.environment_inv_integral = 1.0f / integral;
// Create Vulkan buffer for importance sampling data
m_environment_sampling_data_buffer = create_storage_buffer(m_device, m_physical_device,
m_graphics_queue, m_command_pool, env_accel_data.data(), env_accel_data.size() * sizeof(Env_accel));
// Create sampler
VkSamplerCreateInfo sampler_create_info = {};
sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sampler_create_info.magFilter = VK_FILTER_LINEAR;
sampler_create_info.minFilter = VK_FILTER_LINEAR;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_create_info.unnormalizedCoordinates = false;
VK_CHECK(vkCreateSampler(m_device, &sampler_create_info, nullptr, &m_environment_sampler));
}
// Creates the descriptor pool and set that hold enough space for all
// material resources, and are used during rendering to access the
// the resources.
void Df_vulkan_app::create_descriptor_pool_and_sets()
{
// Reserve enough space. This is way too much, but sizing them perfectly
// would make the code less readable.
VkDescriptorPoolSize uniform_buffer_pool_size;
uniform_buffer_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uniform_buffer_pool_size.descriptorCount = 100;
VkDescriptorPoolSize texture_pool_size;
texture_pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
texture_pool_size.descriptorCount = 100;
VkDescriptorPoolSize storage_buffer_pool_size;
storage_buffer_pool_size.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
storage_buffer_pool_size.descriptorCount = 100;
VkDescriptorPoolSize storage_image_pool_size;
storage_image_pool_size.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
storage_image_pool_size.descriptorCount = 100;
const VkDescriptorPoolSize pool_sizes[] = {
uniform_buffer_pool_size,
texture_pool_size,
storage_buffer_pool_size,
storage_image_pool_size
};
VkDescriptorPoolCreateInfo descriptor_pool_create_info = {};
descriptor_pool_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_create_info.maxSets = m_image_count + 1; // img_cnt for path_trace + 1 set for display
descriptor_pool_create_info.poolSizeCount = std::size(pool_sizes);
descriptor_pool_create_info.pPoolSizes = pool_sizes;
VK_CHECK(vkCreateDescriptorPool(
m_device, &descriptor_pool_create_info, nullptr, &m_descriptor_pool));
// Allocate descriptor set
{
std::vector<VkDescriptorSetLayout> set_layouts(
m_image_count, m_path_trace_descriptor_set_layout);
VkDescriptorSetAllocateInfo descriptor_set_alloc_info = {};
descriptor_set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptor_set_alloc_info.descriptorPool = m_descriptor_pool;
descriptor_set_alloc_info.descriptorSetCount = m_image_count;
descriptor_set_alloc_info.pSetLayouts = set_layouts.data();
m_path_trace_descriptor_sets.resize(m_image_count);
VK_CHECK(vkAllocateDescriptorSets(
m_device, &descriptor_set_alloc_info, m_path_trace_descriptor_sets.data()));
}
{
VkDescriptorSetAllocateInfo descriptor_set_alloc_info = {};
descriptor_set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptor_set_alloc_info.descriptorPool = m_descriptor_pool;
descriptor_set_alloc_info.descriptorSetCount = 1;
descriptor_set_alloc_info.pSetLayouts = &m_display_descriptor_set_layout;
VK_CHECK(vkAllocateDescriptorSets(
m_device, &descriptor_set_alloc_info, &m_display_descriptor_set));
}
// Populate descriptor sets
std::vector<VkDescriptorBufferInfo> descriptor_buffer_infos;
std::vector<VkDescriptorImageInfo> descriptor_image_infos;
std::vector<VkWriteDescriptorSet> descriptor_writes;
// Reserve enough space. This is way too much, but sizing them perfectly
// would make the code less readable.
descriptor_buffer_infos.reserve(100);
descriptor_image_infos.reserve(100);
for (uint32_t i = 0; i < m_image_count; i++)
{
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
{ // Render params buffer
VkDescriptorBufferInfo descriptor_buffer_info = {};
descriptor_buffer_info.buffer = m_render_params_buffers[i].buffer;
descriptor_buffer_info.range = sizeof(Render_params);
descriptor_buffer_infos.push_back(descriptor_buffer_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = g_binding_render_params;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptor_write.pBufferInfo = &descriptor_buffer_infos.back();
descriptor_writes.push_back(descriptor_write);
}
{ // Environment map
VkDescriptorImageInfo descriptor_image_info = {};
descriptor_image_info.sampler = m_environment_sampler;
descriptor_image_info.imageView = m_environment_map.image_view;
descriptor_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
descriptor_image_infos.push_back(descriptor_image_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = g_binding_environment_map;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
}
{ // Environment map sampling data
VkDescriptorBufferInfo descriptor_buffer_info = {};
descriptor_buffer_info.buffer = m_environment_sampling_data_buffer.buffer;
descriptor_buffer_info.range = VK_WHOLE_SIZE;
descriptor_buffer_infos.push_back(descriptor_buffer_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = g_binding_environment_sampling_data;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_write.pBufferInfo = &descriptor_buffer_infos.back();
descriptor_writes.push_back(descriptor_write);
}
// Material textures index buffer
if (m_material_textures_index_buffer.buffer)
{
VkDescriptorBufferInfo descriptor_buffer_info = {};
descriptor_buffer_info.buffer = m_material_textures_index_buffer.buffer;
descriptor_buffer_info.range = VK_WHOLE_SIZE;
descriptor_buffer_infos.push_back(descriptor_buffer_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = g_binding_material_textures_indices;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptor_write.pBufferInfo = &descriptor_buffer_infos.back();
descriptor_writes.push_back(descriptor_write);
}
// Material textures
const std::vector<Vulkan_texture>* material_textures_arrays[] = {
&m_material_textures_2d,
&m_material_textures_3d
};
const uint32_t material_textures_bindings[] = {
g_binding_material_textures_2d,
g_binding_material_textures_3d
};
for (size_t dim = 0; dim < 2; dim++)
{
const auto& textures = *material_textures_arrays[dim];
const uint32_t binding = material_textures_bindings[dim];
for (size_t tex = 0; tex < textures.size(); tex++)
{
VkDescriptorImageInfo descriptor_image_info = {};
descriptor_image_info.sampler = m_linear_sampler;
descriptor_image_info.imageView = textures[tex].image_view;
descriptor_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
descriptor_image_infos.push_back(descriptor_image_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = binding;
descriptor_write.dstArrayElement = tex;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
}
}
// Read-only data buffer
if (m_ro_data_buffer.buffer)
{
VkDescriptorBufferInfo descriptor_buffer_info = {};
descriptor_buffer_info.buffer = m_ro_data_buffer.buffer;
descriptor_buffer_info.range = VK_WHOLE_SIZE;
descriptor_buffer_infos.push_back(descriptor_buffer_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = g_binding_ro_data_buffer;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_write.pBufferInfo = &descriptor_buffer_infos.back();
descriptor_writes.push_back(descriptor_write);
}
// Material argument block buffer
if (m_argument_block_buffer.buffer)
{
VkDescriptorBufferInfo descriptor_buffer_info = {};
descriptor_buffer_info.buffer = m_argument_block_buffer.buffer;
descriptor_buffer_info.range = VK_WHOLE_SIZE;
descriptor_buffer_infos.push_back(descriptor_buffer_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = g_binding_argument_block_buffer;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_write.pBufferInfo = &descriptor_buffer_infos.back();
descriptor_writes.push_back(descriptor_write);
}
}
vkUpdateDescriptorSets(
m_device, static_cast<uint32_t>(descriptor_writes.size()),
descriptor_writes.data(), 0, nullptr);
update_accumulation_image_descriptors();
}
void Df_vulkan_app::update_accumulation_image_descriptors()
{
std::vector<VkWriteDescriptorSet> descriptor_writes;
std::vector<VkDescriptorImageInfo> descriptor_image_infos;
descriptor_image_infos.reserve(m_image_count * 3 + 3);
for (uint32_t i = 0; i < m_image_count; i++)
{
VkDescriptorImageInfo descriptor_image_info = {};
descriptor_image_info.imageView = m_beauty_texture.image_view;
descriptor_image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
descriptor_image_infos.push_back(descriptor_image_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_path_trace_descriptor_sets[i];
descriptor_write.dstBinding = g_binding_beauty_buffer;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
descriptor_image_info.imageView = m_auxiliary_albedo_texture.image_view;
descriptor_image_infos.push_back(descriptor_image_info);
descriptor_write.dstBinding = g_binding_aux_albedo_buffer;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
descriptor_image_info.imageView = m_auxiliary_normal_texture.image_view;
descriptor_image_infos.push_back(descriptor_image_info);
descriptor_write.dstBinding = g_binding_aux_normal_buffer;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
}
VkDescriptorImageInfo descriptor_info = {};
descriptor_info.imageView = m_beauty_texture.image_view;
descriptor_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
descriptor_image_infos.push_back(descriptor_info);
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = m_display_descriptor_set;
descriptor_write.dstBinding = 0;
descriptor_write.dstArrayElement = 0;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
descriptor_info.imageView = m_auxiliary_albedo_texture.image_view;
descriptor_image_infos.push_back(descriptor_info);
descriptor_write.dstBinding = 1;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
descriptor_info.imageView = m_auxiliary_normal_texture.image_view;
descriptor_image_infos.push_back(descriptor_info);
descriptor_write.dstBinding = 2;
descriptor_write.pImageInfo = &descriptor_image_infos.back();
descriptor_writes.push_back(descriptor_write);
vkUpdateDescriptorSets(
m_device, static_cast<uint32_t>(descriptor_writes.size()),
descriptor_writes.data(), 0, nullptr);
}
//------------------------------------------------------------------------------
// MDL material compilation helpers
//------------------------------------------------------------------------------
mi::neuraylib::IFunction_call* create_material_instance(
const std::string& material_name)
{
// Split material name into module and simple material name
std::string module_name, material_simple_name;
mi::examples::mdl::parse_cmd_argument_material_name(
material_name, module_name, material_simple_name);
// Load module
mdl_impexp_api->load_module(transaction, module_name.c_str(), context);
if (!print_messages(context))
exit_failure("Loading module '%s' failed.", module_name.c_str());
// Get the database name for the module we loaded and check if
// the module exists in the database.
mdl_factory->get_db_definition_name(module_name.c_str()));
transaction->access<mi::neuraylib::IModule>(module_db_name->get_c_str()));
if (!module)
exit_failure("Failed to access the loaded module.");
// To access the material in the database we need to know the exact material
// signature, so we append the arguments to the full name (with module).
std::string material_db_name
= std::string(module_db_name->get_c_str()) + "::" + material_simple_name;
material_db_name = mi::examples::mdl::add_missing_material_signature(
module.get(), material_db_name.c_str());
transaction->access<mi::neuraylib::IFunction_definition>(material_db_name.c_str()));
if (!material_definition)
exit_failure("Failed to access material definition '%s'.", material_db_name.c_str());
// Create material instance
mi::Sint32 result;
material_definition->create_function_call(nullptr, &result));
if (result != 0)
exit_failure("Failed to instantiate material '%s'.", material_db_name.c_str());
material_instance->retain();
return material_instance.get();
}
mi::neuraylib::ICompiled_material* compile_material_instance(
mi::neuraylib::IFunction_call* material_instance,
bool class_compilation)
{
mi::Uint32 compile_flags = class_compilation
// convert to target type SID_MATERIAL
mdl_factory->create_type_factory(transaction));
tf->get_predefined_struct(mi::neuraylib::IType_struct::SID_MATERIAL));
context->set_option("target_type", standard_material_type.get());
material_instance2->create_compiled_material(compile_flags, context));
check_success(print_messages(context));
compiled_material->retain();
return compiled_material.get();
}
const mi::neuraylib::ITarget_code* generate_glsl_code(
const Options& options,
mi::Size& argument_block_index)
{
// Add compiled material to link unit
check_success(be_glsl->set_option("glsl_version", "450") == 0);
if (!options.disable_ssbo && !options.enable_ro_segment)
{
check_success(be_glsl->set_option("glsl_place_uniforms_into_ssbo", "on") == 0);
check_success(be_glsl->set_option("glsl_max_const_data",
std::to_string(options.max_const_data).c_str()) == 0);
check_success(be_glsl->set_option("glsl_uniform_ssbo_binding",
std::to_string(g_binding_ro_data_buffer).c_str()) == 0);
check_success(be_glsl->set_option("glsl_uniform_ssbo_set",
std::to_string(g_set_ro_data_buffer).c_str()) == 0);
}
if (options.enable_ro_segment)
{
check_success(be_glsl->set_option("enable_ro_segment", "on") == 0);
check_success(be_glsl->set_option("max_const_data",
std::to_string(options.max_const_data).c_str()) == 0);
}
check_success(be_glsl->set_option("num_texture_spaces", "1") == 0);
check_success(be_glsl->set_option("num_texture_results", "16") == 0);
check_success(be_glsl->set_option("enable_auxiliary", "on") == 0);
check_success(be_glsl->set_option("df_handle_slot_mode", "none") == 0);
check_success(be_glsl->set_option("libbsdf_flags_in_bsdf_data",
options.enable_bsdf_flags ? "on" : "off") == 0);
be_glsl->create_link_unit(transaction, context));
// Specify which functions to generate
std::vector<mi::neuraylib::Target_function_description> function_descs;
function_descs.emplace_back("thin_walled", "mdl_thin_walled");
function_descs.emplace_back("surface.scattering", "mdl_bsdf");
function_descs.emplace_back("surface.emission.emission", "mdl_edf");
function_descs.emplace_back("surface.emission.intensity", "mdl_emission_intensity");
function_descs.emplace_back("volume.absorption_coefficient", "mdl_absorption_coefficient");
function_descs.emplace_back("geometry.cutout_opacity", "mdl_cutout_opacity");
// Try to determine if the material is thin walled so we can check
// if backface functions need to be generated.
bool is_thin_walled_function = true;
bool thin_walled_value = false;
compiled_material->lookup_sub_expression("thin_walled"));
if (thin_walled_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT)
{
thin_walled_expr->get_interface<const mi::neuraylib::IExpression_constant>());
thin_walled_const->get_value<mi::neuraylib::IValue_bool>());
is_thin_walled_function = false;
thin_walled_value = thin_walled_bool->get_value();
}
// Back faces could be different for thin walled materials
bool need_backface_bsdf = false;
bool need_backface_edf = false;
bool need_backface_emission_intensity = false;
if (is_thin_walled_function || thin_walled_value)
{
// First, backfacs DFs are only considered for thin_walled materials
// Second, we only need to generate new code if surface and backface are different
need_backface_bsdf =
need_backface_edf =
need_backface_emission_intensity =
// Third, either the bsdf or the edf need to be non-default (black)
compiled_material->lookup_sub_expression("backface.scattering"));
compiled_material->lookup_sub_expression("backface.emission.emission"));
if (scattering_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT
&& emission_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT)
{
scattering_expr->get_interface<mi::neuraylib::IExpression_constant>());
scattering_expr_constant->get_value());
emission_expr->get_interface<mi::neuraylib::IExpression_constant>());
emission_expr_constant->get_value());
if (scattering_value->get_kind() == mi::neuraylib::IValue::VK_INVALID_DF
&& emission_value->get_kind() == mi::neuraylib::IValue::VK_INVALID_DF)
{
need_backface_bsdf = false;
need_backface_edf = false;
need_backface_emission_intensity = false;
}
}
}
if (need_backface_bsdf)
function_descs.emplace_back("backface.scattering", "mdl_backface_bsdf");
if (need_backface_edf)
function_descs.emplace_back("backface.emission.emission", "mdl_backface_edf");
if (need_backface_emission_intensity)
function_descs.emplace_back("backface.emission.intensity", "mdl_backface_emission_intensity");
link_unit->add_material(
compiled_material, function_descs.data(), function_descs.size(), context);
check_success(print_messages(context));
// In class compilation mode each material has an argument block which contains the dynamic parameters.
// We need the material's argument block index later when creating the matching Vulkan buffer for
// querying the correct argument block from the target code. The target code contains an argument block
// for each material, so we need the index to query the correct one.
// In this example we only support a single material at a time.
argument_block_index = function_descs[0].argument_block_index;
// Generate GLSL code
be_glsl->translate_link_unit(link_unit.get(), context));
check_success(print_messages(context));
check_success(target_code);
target_code->retain();
return target_code.get();
}
//------------------------------------------------------------------------------
// Command line helpers
//------------------------------------------------------------------------------
void print_usage(char const* prog_name)
{
std::cout
<< "Usage: " << prog_name << " [options] [<material_name|full_mdle_path>]\n"
<< "Options:\n"
<< " -h|--help print this text and exit\n"
<< " -v|--version print the MDL SDK version string and exit\n"
<< " --nowin don't show interactive display\n"
<< " --res <res_x> <res_y> resolution (default: 1024x768)\n"
<< " --numimg <n> swapchain image count (default: 3)\n"
<< " -o|--output <outputfile> image file to write result in nowin mode (default: output.exr)\n"
<< " --spp <num> samples per pixel, only used for --nowin (default: 4096)\n"
<< " --spi <num> samples per render loop iteration (default: 8)\n"
<< " --max_path_length <num> maximum path length (default: 4)\n"
<< " -f|--fov <fov> the camera field of view in degrees (default: 96.0)\n"
<< " --cam <x> <y> <z> set the camera position (default: 0 0 3).\n"
<< " The camera will always look towards (0, 0, 0)\n"
<< " -l|--light <x> <y> <z> adds an omnidirectional light with the given position\n"
<< " <r> <g> <b> and intensity\n"
<< " --hdr <path> hdr image file used for the environment map\n"
<< " (default: nvidia/sdk_examples/resources/environment.hdr)\n"
<< " --hdr_intensity <value> intensity of the environment map (default: 1.0)\n"
<< " --nocc don't compile the material using class compilation\n"
<< " --enable_ro_segment enable the read-only data segment\n"
<< " --disable_ssbo disable use of an ssbo for constants\n"
<< " --max_const_data <size> set the maximum size of constants in bytes in the\n"
<< " generated code (requires read-only data segment or\n"
<< " ssbo, default 1024)\n"
<< " -p|--mdl_path <path> additional MDL search path, can occur multiple times\n"
<< " --vkdebug enable the Vulkan validation layers\n"
<< " --dump_glsl outputs the generated GLSL target code to a file\n"
<< " --allowed_scatter_mode <m> limits the allowed scatter mode to \"none\", \"reflect\",\n"
<< " \"transmit\" or \"reflect_and_transmit\"\n"
<< " (default: restriction disabled)"
<< std::endl;
exit(EXIT_FAILURE);
}
void parse_command_line(int argc, char* argv[], Options& options,
bool& print_version_and_exit, mi::examples::mdl::Configure_options& mdl_configure_options)
{
for (int i = 1; i < argc; ++i)
{
std::string arg(argv[i]);
if (arg[0] == '-')
{
if (arg == "-v" || arg == "--version")
print_version_and_exit = true;
else if (arg == "--nowin")
options.no_window = true;
else if (arg == "--res" && i < argc - 2)
{
options.res_x = std::max(atoi(argv[++i]), 1);
options.res_y = std::max(atoi(argv[++i]), 1);
}
else if (arg == "--numimg" && i < argc - 1)
options.num_images = std::max(atoi(argv[++i]), 2);
else if (arg == "-o" && i < argc - 1)
options.output_file = argv[++i];
else if (arg == "--spp" && i < argc - 1)
options.samples_per_pixel = std::atoi(argv[++i]);
else if (arg == "--spi" && i < argc - 1)
options.samples_per_iteration = std::atoi(argv[++i]);
else if (arg == "--max_path_length" && i < argc - 1)
options.max_path_length = std::atoi(argv[++i]);
else if ((arg == "-f" || arg == "--fov") && i < argc - 1)
options.cam_fov = static_cast<float>(std::atof(argv[++i]));
else if (arg == "--cam" && i < argc - 3)
{
options.cam_pos.x = static_cast<float>(std::atof(argv[++i]));
options.cam_pos.y = static_cast<float>(std::atof(argv[++i]));
options.cam_pos.z = static_cast<float>(std::atof(argv[++i]));
}
else if ((arg == "-l" || arg == "--light") && i < argc - 6)
{
options.light_pos.x = static_cast<float>(std::atof(argv[++i]));
options.light_pos.y = static_cast<float>(std::atof(argv[++i]));
options.light_pos.z = static_cast<float>(std::atof(argv[++i]));
options.light_intensity.x = static_cast<float>(std::atof(argv[++i]));
options.light_intensity.y = static_cast<float>(std::atof(argv[++i]));
options.light_intensity.z = static_cast<float>(std::atof(argv[++i]));
}
else if (arg == "--hdr" && i < argc - 1)
options.hdr_file = argv[++i];
else if (arg == "--hdr_intensity" && i < argc - 1)
options.hdr_intensity = static_cast<float>(std::atof(argv[++i]));
else if ((arg == "-p" || arg == "--mdl_path") && i < argc - 1)
mdl_configure_options.additional_mdl_paths.push_back(argv[++i]);
else if (arg == "--nocc")
options.use_class_compilation = false;
else if (arg == "--enable_ro_segment")
options.enable_ro_segment = true;
else if (arg == "--disable_ssbo")
options.disable_ssbo = true;
else if (arg == "--max_const_data")
options.max_const_data = uint32_t(std::atoi(argv[++i]));
else if (arg == "--vkdebug")
options.enable_validation_layers = true;
else if (arg == "--dump_glsl")
options.dump_glsl = true;
else if (arg == "--allowed_scatter_mode" && i < argc - 1)
{
options.enable_bsdf_flags = true;
std::string mode(argv[++i]);
if (mode == "none")
options.allowed_scatter_mode = mi::neuraylib::DF_FLAGS_NONE;
else if (mode == "reflect")
options.allowed_scatter_mode = mi::neuraylib::DF_FLAGS_ALLOW_REFLECT;
else if (mode == "transmit")
options.allowed_scatter_mode = mi::neuraylib::DF_FLAGS_ALLOW_TRANSMIT;
else if (mode == "reflect_and_transmit")
options.allowed_scatter_mode =
mi::neuraylib::DF_FLAGS_ALLOW_REFLECT_AND_TRANSMIT;
else
{
std::cout << "Unknown allowed_scatter_mode: \"" << mode << "\"" << std::endl;
print_usage(argv[0]);
}
}
else
{
if (arg != "-h" && arg != "--help")
std::cout << "Unknown option: \"" << arg << "\"" << std::endl;
print_usage(argv[0]);
}
}
else
options.material_name = arg;
}
}
//------------------------------------------------------------------------------
// Main function
//------------------------------------------------------------------------------
int MAIN_UTF8(int argc, char* argv[])
{
Options options;
bool print_version_and_exit = false;
mi::examples::mdl::Configure_options configure_options;
parse_command_line(argc, argv, options, print_version_and_exit, configure_options);
// Access the MDL SDK
mi::examples::mdl::load_and_get_ineuray());
if (!neuray.is_valid_interface())
exit_failure("Failed to load the SDK.");
// Handle the --version flag
if (print_version_and_exit)
{
// Print library version information
neuray->get_api_component<const mi::neuraylib::IVersion>());
std::cout << version->get_string() << "\n";
// Free the handles and unload the MDL SDK
version = nullptr;
neuray = nullptr;
if (!mi::examples::mdl::unload())
exit_failure("Failed to unload the SDK.");
exit_success();
}
// Configure the MDL SDK
if (!mi::examples::mdl::configure(neuray.get(), configure_options))
exit_failure("Failed to initialize the SDK.");
// Start the MDL SDK
mi::Sint32 result = neuray->start();
if (result != 0)
exit_failure("Failed to initialize the SDK. Result code: %d", result);
{
// Create a transaction
neuray->get_api_component<mi::neuraylib::IDatabase>());
mi::base::Handle<mi::neuraylib::IScope> scope(database->get_global_scope());
mi::base::Handle<mi::neuraylib::ITransaction> transaction(scope->create_transaction());
// Access needed API components
neuray->get_api_component<mi::neuraylib::IMdl_factory>());
neuray->get_api_component<mi::neuraylib::IMdl_impexp_api>());
neuray->get_api_component<mi::neuraylib::IMdl_backend_api>());
neuray->get_api_component<mi::neuraylib::IImage_api>());
mdl_factory->create_execution_context());
{
// Load and compile material, and generate GLSL code
create_material_instance(mdl_impexp_api.get(), mdl_factory.get(),
transaction.get(), context.get(), options.material_name));
compile_material_instance(mdl_factory.get(), transaction.get(),
material_instance.get(), context.get(),
options.use_class_compilation));
mi::Size argument_block_index;
generate_glsl_code(compiled_material.get(), mdl_backend_api.get(),
transaction.get(), context.get(), options, argument_block_index));
if (options.dump_glsl)
{
std::cout << "Dumping GLSL target code to target_code.glsl\n";
std::ofstream file_stream("target_code.glsl");
file_stream.write(target_code->get_code(), target_code->get_code_size());
}
// Start application
mi::examples::vk::Vulkan_example_app::Config app_config;
app_config.window_title = "MDL SDK DF Vulkan Example";
app_config.image_width = options.res_x;
app_config.image_height = options.res_y;
app_config.image_count = options.num_images;
app_config.headless = options.no_window;
app_config.iteration_count = options.samples_per_pixel / options.samples_per_iteration;
app_config.enable_validation_layers = options.enable_validation_layers;
Df_vulkan_app app(transaction, mdl_impexp_api, image_api, target_code, argument_block_index, options);
app.run(app_config);
}
transaction->commit();
}
// Shut down the MDL SDK
if (neuray->shutdown() != 0)
exit_failure("Failed to shutdown the SDK.");
// Unload the MDL SDK
neuray = nullptr;
if (!mi::examples::mdl::unload())
exit_failure("Failed to unload the SDK.");
exit_success();
}
// Convert command line arguments to UTF8 on Windows
COMMANDLINE_TO_UTF8
Fixed-size math vector class template with generic operations.
Definition: vector.h:286
This interface represents a compiled material.
Definition: icompiled_material.h:97
virtual const IExpression * lookup_sub_expression(const char *path) const =0
Looks up a sub-expression of the compiled material.
virtual base::Uuid get_slot_hash(Material_slot slot) const =0
Returns the hash of a particular material slot.
This interface is used to interact with the distributed database.
Definition: idatabase.h:289
A constant expression.
Definition: iexpression.h:96
@ EK_CONSTANT
A constant expression. See mi::neuraylib::IExpression_constant.
Definition: iexpression.h:55
This interface represents a function call.
Definition: ifunction_call.h:52
This interface represents a function definition.
Definition: ifunction_definition.h:44
This interface provides various utilities related to canvases and buffers.
Definition: iimage_api.h:72
virtual ITile * convert(const ITile *tile, const char *pixel_type) const =0
Converts a tile to a different pixel type.
virtual void adjust_gamma(ITile *tile, Float32 old_gamma, Float32 new_gamma) const =0
Sets the gamma value of a tile and adjusts the pixel data accordingly.
This interface represents a pixel image file.
Definition: iimage.h:66
This interface represents a material instance.
Definition: imaterial_instance.h:34
@ CLASS_COMPILATION
Selects class compilation instead of instance compilation.
Definition: imaterial_instance.h:41
@ DEFAULT_OPTIONS
Default compilation options (e.g., instance compilation).
Definition: imaterial_instance.h:40
This interface can be used to obtain the MDL backends.
Definition: imdl_backend_api.h:56
virtual IMdl_backend * get_backend(Mdl_backend_kind kind)=0
Returns an MDL backend generator.
The execution context can be used to query status information like error and warning messages concern...
Definition: imdl_execution_context.h:131
virtual Sint32 set_option(const char *name, const char *value)=0
Sets a string option.
Factory for various MDL interfaces and functions.
Definition: imdl_factory.h:53
virtual IMdl_execution_context * create_execution_context()=0
Creates an execution context.
virtual IType_factory * create_type_factory(ITransaction *transaction)=0
Returns an MDL type factory for the given transaction.
virtual const IString * get_db_definition_name(const char *mdl_name)=0
Returns the DB name for the MDL name of an material or function definition.
API component for MDL related import and export operations.
Definition: imdl_impexp_api.h:43
virtual Sint32 load_module(ITransaction *transaction, const char *argument, IMdl_execution_context *context=0)=0
Loads an MDL module from disk (or a builtin module) into the database.
This interface represents an MDL module.
Definition: imodule.h:634
Represents target code of an MDL backend.
Definition: imdl_backend.h:783
Textures add image processing options to images.
Definition: itexture.h:68
A transaction provides a consistent view on the database.
Definition: itransaction.h:82
virtual const base::IInterface * access(const char *name)=0
Retrieves an element from the database.
virtual Sint32 commit()=0
Commits the transaction.
@ SID_MATERIAL
The "::material" struct type.
Definition: itype.h:484
A value of type boolean.
Definition: ivalue.h:106
@ VK_INVALID_DF
An invalid distribution function value. See mi::neuraylib::IValue_invalid_df.
Definition: ivalue.h:60
Abstract interface for accessing version information.
Definition: iversion.h:19
virtual const IInterface * get_interface(const Uuid &interface_id) const =0
Acquires a const interface from another.
virtual Uint32 retain() const =0
Increments the reference count.
unsigned int Uint32
32-bit unsigned integer.
Definition: types.h:49
Uint64 Size
Unsigned integral type that is large enough to hold the size of all types.
Definition: types.h:112
signed int Sint32
32-bit signed integer.
Definition: types.h:46
Float32 length(Float32 a)
Returns the Euclidean norm of the scalar a (its absolute value).
Definition: function.h:1107
Color acos(const Color &c)
Returns a color with the elementwise arc cosine of the color c.
Definition: color.h:477
Color tan(const Color &c)
Returns a color with the elementwise tangent of the color c.
Definition: color.h:816
Color atan2(const Color &c, const Color &d)
Returns a color with the elementwise arc tangent of the color c / d.
Definition: color.h:509
Color sin(const Color &c)
Returns a color with the elementwise sine of the color c.
Definition: color.h:761
Color cos(const Color &c)
Returns a color with the elementwise cosine of the color c.
Definition: color.h:558
Color pow(const Color &a, const Color &b)
Returns the color a elementwise to the power of b.
Definition: color.h:719
math::Vector<Float32, 3> Float32_3
Vector of three Float32.
Definition: vector_typedefs.h:90
virtual const char * get_code() const =0
Returns the represented target code in ASCII representation.
virtual const char * get_texture(Size index) const =0
Returns the name of a texture resource used by the target code.
virtual Texture_shape get_texture_shape(Size index) const =0
Returns the texture shape of a given texture resource used by the target code.
virtual Size get_ro_data_segment_count() const =0
Returns the number of constant data initializers.
virtual Size get_ro_data_segment_size(Size index) const =0
Returns the size of the constant data segment at the given index.
virtual const char * get_ro_data_segment_data(Size index) const =0
Returns the data of the constant data segment at the given index.
virtual Size get_argument_block_count() const =0
Returns the number of target argument blocks.
Df_flags
Flags controlling the calculation of DF results.
Definition: target_code_types.h:761
Texture_shape
Definition: imdl_backend.h:811
virtual const ITarget_argument_block * get_argument_block(Size index) const =0
Get a target argument block if available.
virtual Size get_code_size() const =0
Returns the length of the represented target code.
@ DF_FLAGS_NONE
allows nothing -> black
Definition: target_code_types.h:762
@ Texture_shape_bsdf_data
Three-dimensional texture representing a BSDF data table.
Definition: imdl_backend.h:817
@ Texture_shape_3d
Three-dimensional texture.
Definition: imdl_backend.h:814
@ Texture_shape_2d
Two-dimensional texture.
Definition: imdl_backend.h:813
@ SLOT_BACKFACE_EMISSION_INTENSITY
Slot "backface.emission.intensity".
Definition: icompiled_material.h:34
@ SLOT_BACKFACE_SCATTERING
Slot "backface.scattering".
Definition: icompiled_material.h:32
@ SLOT_SURFACE_EMISSION_INTENSITY
Slot "surface.emission.intensity".
Definition: icompiled_material.h:30
@ SLOT_SURFACE_SCATTERING
Slot "surface.scattering".
Definition: icompiled_material.h:28
@ SLOT_BACKFACE_EMISSION_EDF_EMISSION
Slot "backface.emission.emission".
Definition: icompiled_material.h:33
@ SLOT_SURFACE_EMISSION_EDF_EMISSION
Slot "surface.emission.emission".
Definition: icompiled_material.h:29
Math API.

Source Code Location: examples/mdl_sdk/df_vulkan/path_trace.comp

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/df_vulkan/path_trace.comp
// Expected defines:
// LOCAL_SIZE_X : The local work group size in the x dimension
// LOCAL_SIZE_Y : The local work group size in the y dimension
//
// BINDING_RENDER_PARAMS : Binding index for Render_params
// BINDING_ENV_MAP : Binding index for uEnvironmentMap
// BINDING_ENV_MAP_SAMPLING_DATA : Binding index for Environment_sample_data_buffer
// BINDING_BEAUTY_BUFFER : Binding index for uBeautyBuffer
// BINDING_AUX_ALBEDO_BUFFER : Binding index for uAuxAlbedoBuffer
// BINDING_AUX_NORMAL_BUFFER : Binding index for uAuxNormalBuffer
//
// HAS_BACKFACE_BSDF : Only defined if backface.scattering is non-default and different than surface.scattering
// HAS_BACKFACE_EDF : Only defined if backface.emission.emission is non-default and different than surface.emission.emission
// HAS_BACKFACE_EMISSION_INTENSITY : Only defined if backface.emission.intensity is non-default and different than surface.emission.intensity
#version 450
#extension GL_GOOGLE_include_directive : require
#include "mdl_runtime.glsl"
layout(local_size_x = LOCAL_SIZE_X, local_size_y = LOCAL_SIZE_Y, local_size_z = 1) in;
layout(std140, set = 0, binding = BINDING_RENDER_PARAMS) uniform Render_params
{
// Camera
vec3 uCamPos;
vec3 uCamDir;
vec3 uCamRight;
vec3 uCamUp;
float uCamFocal;
// Point light
vec3 uPointLightPos;
vec3 uPointLightColor;
float uPointLightIntensity;
// Environment map
float uEnvironmentIntensityFactor;
float uEnvironmentInvIntegral;
// Render params
uint uMaxPathLength;
uint uSamplesPerIteration;
uint uProgressiveIteration;
int uBsdfDataFlags;
};
layout(set = 0, binding = BINDING_ENV_MAP) uniform sampler2D uEnvironmentMap;
struct Environment_sample_data
{
uint alias;
float q;
};
layout(set = 0, binding = BINDING_ENV_MAP_SAMPLING_DATA)
readonly restrict buffer Environment_sample_data_buffer
{
Environment_sample_data uEnvMapSampleData[];
};
layout(rgba32f, set = 0, binding = BINDING_BEAUTY_BUFFER)
uniform restrict image2D uBeautyBuffer;
layout(rgba32f, set = 0, binding = BINDING_AUX_ALBEDO_BUFFER)
uniform restrict image2D uAuxAlbedoBuffer;
layout(rgba32f, set = 0, binding = BINDING_AUX_NORMAL_BUFFER)
uniform restrict image2D uAuxNormalBuffer;
struct Ray_state
{
vec3 contribution;
vec3 aux_albedo;
vec3 aux_normal;
vec3 weight;
vec3 origin;
vec3 dir;
float last_bsdf_pdf;
uint aux_num_samples;
bool is_inside;
};
struct Ray_hit_info
{
float dist;
vec3 position;
vec3 normal;
vec3 tex_coord;
vec3 tangent_u;
vec3 tangent_v;
};
const float M_PI = 3.14159265358979323846;
const float M_ONE_OVER_PI = 0.318309886183790671538;
const float DIRAC = -1.0;
//-------------------------------------------------------------------------------------------------
// MDL function prototypes
//-------------------------------------------------------------------------------------------------
bool mdl_thin_walled(in State state);
void mdl_bsdf_init(inout State state);
void mdl_bsdf_sample(inout Bsdf_sample_data sret_ptr, in State state);
void mdl_bsdf_evaluate(inout Bsdf_evaluate_data sret_ptr, in State state);
void mdl_bsdf_pdf(inout Bsdf_pdf_data sret_ptr, in State state);
void mdl_bsdf_auxiliary(inout Bsdf_auxiliary_data sret_ptr, in State state);
void mdl_edf_init(inout State state);
void mdl_edf_sample(inout Edf_sample_data sret_ptr, in State state);
void mdl_edf_evaluate(inout Edf_evaluate_data sret_ptr, in State state);
void mdl_edf_pdf(inout Edf_pdf_data sret_ptr, in State state);
void mdl_edf_auxiliary(inout Edf_auxiliary_data sret_ptr, inout State state);
vec3 mdl_emission_intensity(in State state);
#ifdef HAS_BACKFACE_BSDF
void mdl_backface_bsdf_init(inout State state);
void mdl_backface_bsdf_sample(inout Bsdf_sample_data sret_ptr, in State state);
void mdl_backface_bsdf_evaluate(inout Bsdf_evaluate_data sret_ptr, in State state);
void mdl_backface_bsdf_pdf(inout Bsdf_pdf_data sret_ptr, in State state);
void mdl_backface_bsdf_auxiliary(inout Bsdf_auxiliary_data sret_ptr, in State state);
#else
#define mdl_backface_bsdf_init mdl_bsdf_init
#define mdl_backface_bsdf_sample mdl_bsdf_sample
#define mdl_backface_bsdf_evaluate mdl_bsdf_evaluate
#define mdl_backface_bsdf_pdf mdl_bsdf_pdf
#define mdl_backface_bsdf_auxiliary mdl_bsdf_auxiliary
#endif // HAS_BACKFACE_BSDF
#ifdef HAS_BACKFACE_EDF
void mdl_backface_edf_init(inout State state);
void mdl_backface_edf_sample(inout Edf_sample_data sret_ptr, in State state);
void mdl_backface_edf_evaluate(inout Edf_evaluate_data sret_ptr, in State state);
void mdl_backface_edf_pdf(inout Edf_pdf_data sret_ptr, in State state);
void mdl_backface_edf_auxiliary(inout Edf_auxiliary_data sret_ptr, inout State state);
#else
#define mdl_backface_edf_init mdl_edf_init
#define mdl_backface_edf_sample mdl_edf_sample
#define mdl_backface_edf_evaluate mdl_edf_evaluate
#define mdl_backface_edf_pdf mdl_edf_pdf
#define mdl_backface_edf_auxiliary mdl_edf_auxiliary
#endif // HAS_BACKFACE_EDF
#ifdef HAS_BACKFACE_EMISSION_INTENSITY
vec3 mdl_backface_emission_intensity(in State state);
#else
#define mdl_backface_emission_intensity mdl_emission_intensity
#endif // HAS_BACKFACE_EMISSION_INTENSITY
vec3 mdl_absorption_coefficient(in State state);
float mdl_cutout_opacity(in State state);
//-------------------------------------------------------------------------------------------------
// random number generator based on the Optix SDK
//-------------------------------------------------------------------------------------------------
uint tea(uint N, uint val0, uint val1)
{
uint v0 = val0;
uint v1 = val1;
uint s0 = 0;
for (uint n = 0; n < N; n++)
{
s0 += 0x9e3779b9;
v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4);
v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e);
}
return v0;
}
// Generate random uint in [0, 2^24)
uint lcg(inout uint prev)
{
const uint LCG_A = 1664525u;
const uint LCG_C = 1013904223u;
prev = (LCG_A * prev + LCG_C);
return prev & 0x00FFFFFF;
}
// Generate random float in [0, 1)
float rnd(inout uint prev)
{
return float(lcg(prev)) / float(0x01000000);
}
//-------------------------------------------------------------------------------------------------
// environment map importance sampling
//-------------------------------------------------------------------------------------------------
vec3 environment_evaluate(vec3 normalized_dir, out float pdf)
{
// assuming lat long
const float u = atan(normalized_dir.z, normalized_dir.x) * 0.5 * M_ONE_OVER_PI + 0.5;
const float v = acos(-normalized_dir.y) * M_ONE_OVER_PI;
// get radiance and calculate pdf
vec3 t = textureLod(uEnvironmentMap, vec2(u, v), /*lod=*/0).xyz;
pdf = max(t.x, max(t.y, t.z)) * uEnvironmentInvIntegral;
return t * uEnvironmentIntensityFactor;
}
vec3 environment_sample(inout uint seed, out vec3 to_light, out float pdf)
{
vec3 xi = vec3(rnd(seed), rnd(seed), rnd(seed));
// importance sample an envmap pixel using an alias map
ivec2 tex_size = textureSize(uEnvironmentMap, /*lod=*/0);
const uint size = tex_size.x * tex_size.y;
const uint idx = min(uint(xi.x * float(size)), size - 1);
uint env_idx;
float xi_y = xi.y;
if (xi_y < uEnvMapSampleData[idx].q)
{
env_idx = idx;
xi_y /= uEnvMapSampleData[idx].q;
}
else
{
env_idx = uEnvMapSampleData[idx].alias;
xi_y = (xi_y - uEnvMapSampleData[idx].q) / (1.0 - uEnvMapSampleData[idx].q);
}
const uint py = env_idx / tex_size.x;
const uint px = env_idx % tex_size.x;
// uniformly sample spherical area of pixel
const float u = float(px + xi_y) / float(tex_size.x);
const float phi = u * 2.0 * M_PI - M_PI;
const float sin_phi = sin(phi);
const float cos_phi = cos(phi);
const float step_theta = M_PI / float(tex_size.y);
const float theta0 = float(py) * step_theta;
const float cos_theta = cos(theta0) * (1.0 - xi.z) + cos(theta0 + step_theta) * xi.z;
const float theta = acos(cos_theta);
const float sin_theta = sin(theta);
to_light = vec3(cos_phi * sin_theta, -cos_theta, sin_phi * sin_theta);
// lookup filtered beauty
const float v = theta * M_ONE_OVER_PI;
const vec4 t = textureLod(uEnvironmentMap, vec2(u, v), /*lod=*/0);
pdf = max(t.x, max(t.y, t.z)) * uEnvironmentInvIntegral;
return vec3(t.x, t.y, t.z) * uEnvironmentIntensityFactor;
}
vec3 sample_lights(State state, inout uint seed, out vec3 to_light, out float light_pdf)
{
float p_select_light = 1.0;
if (uPointLightIntensity > 0.0)
{
p_select_light = uEnvironmentIntensityFactor > 0.0 ? 0.5 : 1.0;
if (rnd(seed) <= p_select_light)
{
light_pdf = DIRAC; // infinity
to_light = uPointLightPos - state.position;
const float inv_distance2 = 1.0 / dot(to_light, to_light);
to_light *= sqrt(inv_distance2);
return uPointLightColor * uPointLightIntensity * inv_distance2 * 0.25 * M_ONE_OVER_PI / p_select_light;
}
// Probability to select the environment instead
p_select_light = (1.0 - p_select_light);
}
vec3 radiance = environment_sample(seed, to_light, light_pdf);
// Return radiance over pdf
light_pdf *= p_select_light;
return radiance / light_pdf;
}
// Intersects the unit sphere that is located at the world origin.
bool intersect_sphere(vec3 ray_origin, vec3 ray_dir, out Ray_hit_info out_info)
{
const float sphere_radius = 1.0;
const float a = dot(ray_dir, ray_dir);
const float half_b = dot(ray_dir, ray_origin);
const float c = dot(ray_origin, ray_origin) - sphere_radius * sphere_radius;
const float discriminant = half_b * half_b - a * c;
if (discriminant < 0.0)
return false;
const float sqrt_discr = sqrt(discriminant);
float t = (-half_b - sqrt_discr) / a;
if (t < 0.0)
t = (-half_b + sqrt_discr) / a;
if (t < 0.0)
return false;
out_info.dist = t;
out_info.position = ray_origin + ray_dir * t;
out_info.normal = normalize(out_info.position);
const float phi = atan(out_info.normal.x, out_info.normal.z);
const float theta = acos(out_info.normal.y);
out_info.tex_coord = vec3(
phi * M_ONE_OVER_PI + 1.0,
1.0 - theta * M_ONE_OVER_PI,
0.0);
const float sin_phi = sin(phi);
const float cos_phi = cos(phi);
const float sin_theta = sin(theta);
out_info.tangent_u = vec3(cos_phi * sin_theta, 0.0, -sin_phi * sin_theta) * M_PI * sphere_radius;
out_info.tangent_u = normalize(out_info.tangent_u);
out_info.tangent_v = vec3(sin_phi * out_info.normal.y, -sin_theta, cos_phi * out_info.normal.y) * -M_PI * sphere_radius;
out_info.tangent_v = normalize(out_info.tangent_v);
return true;
}
vec3 offset_ray(const vec3 p, const vec3 n)
{
const float origin = 1.0 / 32.0;
const float float_scale = 1.0 / 65536.0;
const float int_scale = 256.0;
const ivec3 of_i = ivec3(int_scale * n);
vec3 p_i = vec3(intBitsToFloat(floatBitsToInt(p.x) + ((p.x < 0.0) ? -of_i.x : of_i.x)),
intBitsToFloat(floatBitsToInt(p.y) + ((p.y < 0.0) ? -of_i.y : of_i.y)),
intBitsToFloat(floatBitsToInt(p.z) + ((p.z < 0.0) ? -of_i.z : of_i.z)));
return vec3(abs(p.x) < origin ? p.x + float_scale * n.x : p_i.x,
abs(p.y) < origin ? p.y + float_scale * n.y : p_i.y,
abs(p.z) < origin ? p.z + float_scale * n.z : p_i.z);
}
State make_state(Ray_hit_info hit_info, bool inside)
{
const vec3 oriented_normal = inside ? -hit_info.normal : hit_info.normal;
return State(
/*normal=*/ oriented_normal,
/*geom_normal=*/ oriented_normal,
/*position=*/ hit_info.position,
/*animation_time=*/ 0.0,
/*text_coords[1]=*/ vec3[1](hit_info.tex_coord),
/*tangent_u[1]=*/ vec3[1](hit_info.tangent_u),
/*tangent_v[1]=*/ vec3[1](hit_info.tangent_v),
/*text_results[16]=*/ vec4[16](vec4(0), vec4(0), vec4(0), vec4(0),
vec4(0), vec4(0), vec4(0), vec4(0),
vec4(0), vec4(0), vec4(0), vec4(0),
vec4(0), vec4(0), vec4(0), vec4(0)),
/*ro_data_segment_offset=*/ 0,
/*world_to_object=*/ mat4(1.0),
/*object_to_world=*/ mat4(1.0),
/*object_id=*/ 0,
/*meters_per_scene_unit=*/ 1.0,
/*arg_block_offset=*/ 0
);
}
void trace_path(inout Ray_state ray_state, inout uint seed)
{
for (int bounce = 0; bounce < uMaxPathLength; bounce++)
{
Ray_hit_info hit_info;
if (!intersect_sphere(ray_state.origin, ray_state.dir, hit_info))
{
// Ray missed
float light_pdf;
vec3 light_radiance = environment_evaluate(ray_state.dir, light_pdf);
// Incoperate point light selection probability
if (uPointLightIntensity > 0.0)
light_pdf *= 0.5;
float mis_weight = ray_state.last_bsdf_pdf == DIRAC
? 1.0
: ray_state.last_bsdf_pdf / (ray_state.last_bsdf_pdf + light_pdf);
ray_state.contribution += light_radiance * ray_state.weight * mis_weight;
break;
}
State state = make_state(hit_info, ray_state.is_inside);
// Handle cutout opacity
float x_anyhit = rnd(seed);
if (x_anyhit > mdl_cutout_opacity(state))
{
// Don't count this as a bounce
bounce--;
ray_state.is_inside = !ray_state.is_inside;
ray_state.origin = offset_ray(state.position, -state.geom_normal);
continue;
}
// Cache shading normal because we need to reset
// the normal before each df::init call, which expects
// an unchanged normal as input.
const vec3 shading_normal = state.normal;
const bool thin_walled = mdl_thin_walled(state);
const bool is_backface = ray_state.is_inside && thin_walled;
const vec3 ior1 = vec3((ray_state.is_inside && !thin_walled) ? BSDF_USE_MATERIAL_IOR : 1.0);
const vec3 ior2 = vec3((ray_state.is_inside && !thin_walled) ? 1.0 : BSDF_USE_MATERIAL_IOR);
// Apply volume attenuation
if (ray_state.is_inside && !thin_walled)
{
const vec3 abs_coeff = mdl_absorption_coefficient(state);
ray_state.weight.x *= abs_coeff.x > 0.0 ? exp(-abs_coeff.x * hit_info.dist) : 1.0;
ray_state.weight.y *= abs_coeff.y > 0.0 ? exp(-abs_coeff.y * hit_info.dist) : 1.0;
ray_state.weight.z *= abs_coeff.z > 0.0 ? exp(-abs_coeff.z * hit_info.dist) : 1.0;
}
// Add emission
Edf_evaluate_data edf_evaluate_data;
edf_evaluate_data.k1 = -ray_state.dir;
if (is_backface)
{
mdl_backface_edf_init(state);
mdl_backface_edf_evaluate(edf_evaluate_data, state);
const vec3 emission_intensity = mdl_backface_emission_intensity(state);
ray_state.contribution += ray_state.weight * emission_intensity * edf_evaluate_data.edf;
}
else if (!ray_state.is_inside)
{
mdl_edf_init(state);
mdl_edf_evaluate(edf_evaluate_data, state);
const vec3 emission_intensity = mdl_emission_intensity(state);
ray_state.contribution += ray_state.weight * emission_intensity * edf_evaluate_data.edf;
}
// BSDF
state.normal = shading_normal; // Restore shading normal
if (is_backface)
mdl_backface_bsdf_init(state);
else
mdl_bsdf_init(state);
// Output auxiliary data
if (bounce == 0)
{
Bsdf_auxiliary_data bsdf_aux_data;
bsdf_aux_data.ior1 = ior1;
bsdf_aux_data.ior2 = ior2;
bsdf_aux_data.k1 = -ray_state.dir;
bsdf_aux_data.flags = uBsdfDataFlags;
if (is_backface)
mdl_backface_bsdf_auxiliary(bsdf_aux_data, state);
else
mdl_bsdf_auxiliary(bsdf_aux_data, state);
ray_state.aux_albedo += bsdf_aux_data.albedo_diffuse + bsdf_aux_data.albedo_glossy;
ray_state.aux_normal += bsdf_aux_data.normal;
ray_state.aux_num_samples++;
}
// Sample lights
if (bounce < uMaxPathLength - 1)
{
vec3 to_light;
float light_pdf;
vec3 light_radiance_over_pdf = sample_lights(state, seed, to_light, light_pdf);
float offset_dir = sign(dot(state.geom_normal, to_light));
vec3 shadow_ray_origin = offset_ray(state.position, state.geom_normal * offset_dir);
Ray_hit_info shadow_hit;
bool shadow_ray_hit = intersect_sphere(shadow_ray_origin, to_light, shadow_hit);
float x_light_anyhit = rnd(seed);
if (shadow_ray_hit)
{
State shadow_hit_state = make_state(shadow_hit, ray_state.is_inside);
shadow_ray_hit = (x_light_anyhit <= mdl_cutout_opacity(shadow_hit_state));
}
if (!shadow_ray_hit && light_pdf != 0.0)
{
Bsdf_evaluate_data bsdf_eval_data;
bsdf_eval_data.ior1 = ior1;
bsdf_eval_data.ior2 = ior2;
bsdf_eval_data.k1 = -ray_state.dir;
bsdf_eval_data.k2 = to_light;
bsdf_eval_data.flags = uBsdfDataFlags;
if (is_backface)
mdl_backface_bsdf_evaluate(bsdf_eval_data, state);
else
mdl_bsdf_evaluate(bsdf_eval_data, state);
if (bsdf_eval_data.pdf > 0.0)
{
const vec3 bsdf = bsdf_eval_data.bsdf_diffuse + bsdf_eval_data.bsdf_glossy;
const float mis_weight = (light_pdf == DIRAC)
? 1.0
: light_pdf / (light_pdf + bsdf_eval_data.pdf);
ray_state.contribution += ray_state.weight * light_radiance_over_pdf * bsdf * mis_weight;
}
}
}
// Sample BSDF
Bsdf_sample_data bsdf_sample_data;
bsdf_sample_data.ior1 = ior1;
bsdf_sample_data.ior2 = ior2;
bsdf_sample_data.k1 = -ray_state.dir;
bsdf_sample_data.xi = vec4(rnd(seed), rnd(seed), rnd(seed), rnd(seed));
bsdf_sample_data.flags = uBsdfDataFlags;
if (is_backface)
mdl_backface_bsdf_sample(bsdf_sample_data, state);
else
mdl_bsdf_sample(bsdf_sample_data, state);
if (bsdf_sample_data.event_type == BSDF_EVENT_ABSORB)
break;
// Continue path
bool is_specular = (bsdf_sample_data.event_type & BSDF_EVENT_SPECULAR) != 0;
bool is_transmission = (bsdf_sample_data.event_type & BSDF_EVENT_TRANSMISSION) != 0;
if (is_transmission)
{
ray_state.is_inside = !ray_state.is_inside;
ray_state.origin = offset_ray(state.position, -state.geom_normal);
}
else
{
ray_state.origin = offset_ray(state.position, state.geom_normal);
}
ray_state.weight *= bsdf_sample_data.bsdf_over_pdf;
ray_state.dir = bsdf_sample_data.k2;
ray_state.last_bsdf_pdf = is_specular ? DIRAC : bsdf_sample_data.pdf;
}
}
//-------------------------------------------------------------------------------------------------
// entry point
//-------------------------------------------------------------------------------------------------
void main()
{
ivec2 image_size = imageSize(uBeautyBuffer);
if (gl_GlobalInvocationID.x > image_size.x || gl_GlobalInvocationID.y > image_size.y)
return;
vec3 contribution = vec3(0.0);
vec3 aux_albedo = vec3(0.0);
vec3 aux_normal = vec3(0.0);
uint aux_num_samples = 0;
for (uint iteration = 0; iteration < uSamplesPerIteration; iteration++)
{
uint seed = tea(
16, /*magic (see OptiX path tracing example)*/
gl_GlobalInvocationID.y * uint(image_size.x) + gl_GlobalInvocationID.x,
uProgressiveIteration + iteration);
vec2 resolution = vec2(image_size);
vec2 jitter = vec2(rnd(seed), rnd(seed));
vec2 uv = (vec2(gl_GlobalInvocationID.xy) + jitter) / resolution * 2.0 - 1.0;
float aspect = resolution.y / resolution.x;
vec3 ray_origin = uCamPos;
vec3 ray_dir = normalize(uCamDir * uCamFocal + uCamRight * uv.x + uCamUp * uv.y * aspect);
Ray_state ray_state;
ray_state.contribution = vec3(0.0);
ray_state.aux_albedo = vec3(0.0);
ray_state.aux_normal = vec3(0.0);
ray_state.weight = vec3(1.0);
ray_state.origin = ray_origin;
ray_state.dir = ray_dir;
ray_state.last_bsdf_pdf = DIRAC;
ray_state.aux_num_samples = 0;
ray_state.is_inside = dot(ray_origin, ray_origin) < 1.0;
trace_path(ray_state, seed);
contribution += ray_state.contribution;
aux_albedo += ray_state.aux_albedo;
aux_normal += ray_state.aux_normal;
aux_num_samples += ray_state.aux_num_samples;
}
contribution *= 1.0 / float(uSamplesPerIteration);
aux_albedo *= 1.0 / max(1.0, float(aux_num_samples));
if (dot(aux_normal, aux_normal) > 0.0)
aux_normal = normalize(aux_normal);
const ivec2 image_coord = ivec2(gl_GlobalInvocationID.xy);
vec3 old_beauty = imageLoad(uBeautyBuffer, image_coord).xyz;
vec3 old_aux_albedo = imageLoad(uAuxAlbedoBuffer, image_coord).xyz;
vec3 old_aux_normal = imageLoad(uAuxNormalBuffer, image_coord).xyz;
float blend_weight = float(uSamplesPerIteration) / float(uProgressiveIteration + uSamplesPerIteration);
vec3 new_beauty = mix(old_beauty, contribution, blend_weight);
vec3 new_aux_albedo = mix(old_aux_albedo, aux_albedo, blend_weight);
vec3 new_aux_normal = mix(old_aux_normal, aux_normal, blend_weight);
// Renormalize auxiliary normal
if (dot(new_aux_normal, new_aux_normal) > 0.0)
new_aux_normal = normalize(new_aux_normal);
imageStore(uBeautyBuffer, image_coord, vec4(new_beauty, 0.0));
imageStore(uAuxAlbedoBuffer, image_coord, vec4(new_aux_albedo, 0.0));
imageStore(uAuxNormalBuffer, image_coord, vec4(new_aux_normal, 0.0));
}

Source Code Location: examples/mdl_sdk/df_vulkan/mdl_runtime.glsl

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/df_vulkan/mdl_runtime.glsl
// Expected defines:
// NUM_MATERIAL_TEXTURES_2D : The number of material 2D textures
// NUM_MATERIAL_TEXTURES_3D : The number of material 3D textures
// SET_MATERIAL_TEXTURES_INDICES : The set index for the array of material texture array indices
// SET_MATERIAL_TEXTURES_2D : The set index for the array of material 2D textures
// SET_MATERIAL_TEXTURES_3D : The set index for the array of material 3D textures
// SET_MATERIAL_ARGUMENT_BLOCK : The set index for the material argument block buffer
// SET_MATERIAL_RO_DATA_SEGMENT : The set index for the material read-only data segment buffer
// BINDING_MATERIAL_TEXTURES_INDICES : The binding index for the array of material texture array indices
// BINDING_MATERIAL_TEXTURES_2D : The binding index for the array of material 2D textures
// BINDING_MATERIAL_TEXTURES_3D : The binding index for the array of material 3D textures
// BINDING_MATERIAL_ARGUMENT_BLOCK : The binding index for the material argument block buffer
// BINDING_MATERIAL_RO_DATA_SEGMENT : The binding index for the material read-only data segment buffer
// USE_RO_DATA_SEGMENT : Defined if the read-only data segment is enabled
#ifndef MDL_RUNTIME_GLSL
#define MDL_RUNTIME_GLSL
// The array indices of the material textures. This is needed because in MDL the 2D and 3D textures
// are not differentiated and thus, the 2D and 3D sampler arrays should overlap.
// e.g. 2D textures: | A | _ | C | D | _ | _ |
// 3D textures: | _ | B | _ | _ | E | F |
// However, in Vulkan 1.0 without extensions this is not possible since all bindings of a descriptor
// set must be bound to a valid resource. Therefore, we need to tightly pack the texture arrays
// defined below and add the indirection from MDL texture index to the real index. Alternatively,
// the VK_EXT_robustness2 extension allows "null descriptors" which lets the texture arrays be
// sparse. The best solution, however, would be to use "Descriptor Indexing" which is core in
// Vulkan 1.2 or can be used through various extensions in older versions. This would allow
// variable sized texture arrays that can have undefined entries. We show the solution that is
// most compatible with older hardware, but recommend using "Descriptor Indexing" if possible.
#if (NUM_MATERIAL_TEXTURES_2D + NUM_MATERIAL_TEXTURES_3D > 0)
layout(std140, set = SET_MATERIAL_TEXTURES_INDICES, binding = BINDING_MATERIAL_TEXTURES_INDICES)
uniform MaterialTextureIndiciesBuffer
{
uint uMaterialTextureIndices[NUM_MATERIAL_TEXTURES_2D + NUM_MATERIAL_TEXTURES_3D];
};
#endif // (NUM_MATERIAL_TEXTURES_2D + NUM_MATERIAL_TEXTURES_3D > 0)
// The arrays of material textures used in the texturing functions
#if (NUM_MATERIAL_TEXTURES_2D > 0)
layout(set = SET_MATERIAL_TEXTURES_2D, binding = BINDING_MATERIAL_TEXTURES_2D)
uniform sampler2D uMaterialTextures2D[NUM_MATERIAL_TEXTURES_2D];
#endif // (NUM_MATERIAL_TEXTURES_2D > 0)
#if (NUM_MATERIAL_TEXTURES_3D > 0)
layout(set = SET_MATERIAL_TEXTURES_3D, binding = BINDING_MATERIAL_TEXTURES_3D)
uniform sampler3D uMaterialTextures3D[NUM_MATERIAL_TEXTURES_3D];
#endif // (NUM_MATERIAL_TEXTURES_3D > 0)
// The material argument block used for dynamic parameters in class compilation mode
layout(std430, set = SET_MATERIAL_ARGUMENT_BLOCK, binding = BINDING_MATERIAL_ARGUMENT_BLOCK)
readonly restrict buffer ArgumentBlockBuffer
{
uint uMaterialArgumentBlock[];
};
#ifdef USE_RO_DATA_SEGMENT
// The read-only data segment
layout(std430, set = SET_MATERIAL_RO_DATA_SEGMENT, binding = BINDING_MATERIAL_RO_DATA_SEGMENT)
readonly restrict buffer RODataSegmentBuffer
{
uint uMaterialRODataSegment[];
};
#endif
//-----------------------------------------------------------------------------
// MDL data types and constants
//-----------------------------------------------------------------------------
#define Tex_wrap_mode int
#define TEX_WRAP_CLAMP 0
#define TEX_WRAP_REPEAT 1
#define TEX_WRAP_MIRRORED_REPEAT 2
#define TEX_WRAP_CLIP 3
#define Bsdf_event_type int
#define BSDF_EVENT_ABSORB 0
#define BSDF_EVENT_DIFFUSE 1
#define BSDF_EVENT_GLOSSY (1 << 1)
#define BSDF_EVENT_SPECULAR (1 << 2)
#define BSDF_EVENT_REFLECTION (1 << 3)
#define BSDF_EVENT_TRANSMISSION (1 << 4)
#define BSDF_EVENT_DIFFUSE_REFLECTION (BSDF_EVENT_DIFFUSE | BSDF_EVENT_REFLECTION)
#define BSDF_EVENT_DIFFUSE_TRANSMISSION (BSDF_EVENT_DIFFUSE | BSDF_EVENT_TRANSMISSION)
#define BSDF_EVENT_GLOSSY_REFLECTION (BSDF_EVENT_GLOSSY | BSDF_EVENT_REFLECTION)
#define BSDF_EVENT_GLOSSY_TRANSMISSION (BSDF_EVENT_GLOSSY | BSDF_EVENT_TRANSMISSION)
#define BSDF_EVENT_SPECULAR_REFLECTION (BSDF_EVENT_SPECULAR | BSDF_EVENT_REFLECTION)
#define BSDF_EVENT_SPECULAR_TRANSMISSION (BSDF_EVENT_SPECULAR | BSDF_EVENT_TRANSMISSION)
#define BSDF_EVENT_FORCE_32_BIT 0xffffffffU
#define Edf_event_type int
#define EDF_EVENT_NONE 0
#define EDF_EVENT_EMISSION 1
#define EDF_EVENT_FORCE_32_BIT 0xffffffffU
#define BSDF_USE_MATERIAL_IOR (-1.0)
/// Flags controlling the calculation of DF results.
/// This cannot be represented as a real enum, because the MDL SDK HLSL backend only sees enums
/// as ints on LLVM level and would create wrong types for temporary variables
#define Df_flags int
#define DF_FLAGS_NONE 0 ///< allows nothing -> black
#define DF_FLAGS_ALLOW_REFLECT 1
#define DF_FLAGS_ALLOW_TRANSMIT 2
#define DF_FLAGS_ALLOW_REFLECT_AND_TRANSMIT (DF_FLAGS_ALLOW_REFLECT | DF_FLAGS_ALLOW_TRANSMIT)
#define DF_FLAGS_ALLOWED_SCATTER_MODE_MASK (DF_FLAGS_ALLOW_REFLECT_AND_TRANSMIT)
#define DF_FLAGS_FORCE_32_BIT 0xffffffffU
struct State
{
vec3 normal;
vec3 geom_normal;
vec3 position;
float animation_time;
vec3 text_coords[1];
vec3 tangent_u[1];
vec3 tangent_v[1];
vec4 text_results[16];
int ro_data_segment_offset;
mat4 world_to_object;
mat4 object_to_world;
int object_id;
float meters_per_scene_unit;
int arg_block_offset;
};
struct Bsdf_sample_data
{
/*Input*/ vec3 ior1; // IOR current med
/*Input*/ vec3 ior2; // IOR other side
/*Input*/ vec3 k1; // outgoing direction
/*Output*/ vec3 k2; // incoming direction
/*Input*/ vec4 xi; // pseudo-random sample numbers in range [0, 1)
/*Output*/ float pdf; // pdf (non-projected hemisphere)
/*Output*/ vec3 bsdf_over_pdf; // bsdf * dot(normal, k2) / pdf
/*Output*/ int event_type; // the type of event for the generated sample
/*Output*/ int handle; // handle of the sampled elemental BSDF (lobe)
/*Input*/ Df_flags flags; // flags controlling calculation of result
// (optional depending on backend options)
};
struct Bsdf_evaluate_data
{
/*Input*/ vec3 ior1; // IOR current medium
/*Input*/ vec3 ior2; // IOR other side
/*Input*/ vec3 k1; // outgoing direction
/*Input*/ vec3 k2; // incoming direction
/*Output*/ vec3 bsdf_diffuse; // bsdf_diffuse * dot(normal, k2)
/*Output*/ vec3 bsdf_glossy; // bsdf_glossy * dot(normal, k2)
/*Output*/ float pdf; // pdf (non-projected hemisphere)
/*Input*/ Df_flags flags; // flags controlling calculation of result
// (optional depending on backend options)
};
struct Bsdf_pdf_data
{
/*Input*/ vec3 ior1; // IOR current medium
/*Input*/ vec3 ior2; // IOR other side
/*Input*/ vec3 k1; // outgoing direction
/*Input*/ vec3 k2; // incoming direction
/*Output*/ float pdf; // pdf (non-projected hemisphere)
/*Input*/ Df_flags flags; // flags controlling calculation of result
// (optional depending on backend options)
};
struct Bsdf_auxiliary_data
{
/*Input*/ vec3 ior1; // IOR current medium
/*Input*/ vec3 ior2; // IOR other side
/*Input*/ vec3 k1; // outgoing direction
/*Output*/ vec3 albedo_diffuse; // (diffuse part of the) albedo
/*Output*/ vec3 albedo_glossy; // (glossy part of the) albedo
/*Output*/ vec3 normal; // normal
/*Output*/ vec3 roughness; // glossy roughness_u, glossy roughness_v, bsdf_weight
/*Input*/ Df_flags flags; // flags controlling calculation of result
// (optional depending on backend options)
};
struct Edf_sample_data
{
/*Input*/ vec4 xi; // pseudo-random sample numbers in range [0, 1)
/*Output*/ vec3 k1; // outgoing direction
/*Output*/ float pdf; // pdf (non-projected hemisphere)
/*Output*/ vec3 edf_over_pdf; // edf * dot(normal,k1) / pdf
/*Output*/ int event_type; // the type of event for the generated sample
/*Output*/ int handle; // handle of the sampled elemental EDF (lobe)
};
struct Edf_evaluate_data
{
/*Input*/ vec3 k1; // outgoing direction
/*Output*/ float cos; // dot(normal, k1)
/*Output*/ vec3 edf; // edf
/*Output*/ float pdf; // pdf (non-projected hemisphere)
};
struct Edf_pdf_data
{
/*Input*/ vec3 k1; // outgoing direction
/*Output*/ float pdf; // pdf (non-projected hemisphere)
};
struct Edf_auxiliary_data
{
/*Input*/ vec3 k1; // outgoing direction
};
// ------------------------------------------------------------------------------------------------
// Argument block access for dynamic parameters in class compilation mode
// ------------------------------------------------------------------------------------------------
float mdl_read_argblock_as_float(int offs)
{
return uintBitsToFloat(uMaterialArgumentBlock[offs >> 2]);
}
double mdl_read_argblock_as_double(int offs)
{
return packDouble2x32(
uvec2(uMaterialArgumentBlock[offs >> 2], uMaterialArgumentBlock[(offs >> 2) + 1]));
}
int mdl_read_argblock_as_int(int offs)
{
return int(uMaterialArgumentBlock[offs >> 2]);
}
uint mdl_read_argblock_as_uint(int offs)
{
return uMaterialArgumentBlock[offs >> 2];
}
bool mdl_read_argblock_as_bool(int offs)
{
uint val = uMaterialArgumentBlock[offs >> 2];
return (val & (0xff << (8 * (offs & 3)))) != 0;
}
// ------------------------------------------------------------------------------------------------
// Read-only data access via read functions
// ------------------------------------------------------------------------------------------------
#ifdef USE_RO_DATA_SEGMENT
float mdl_read_rodata_as_float(int offs)
{
return uintBitsToFloat(uMaterialRODataSegment[offs >> 2]);
}
double mdl_read_rodata_as_double(int offs)
{
return packDouble2x32(
uvec2(uMaterialRODataSegment[offs >> 2], uMaterialRODataSegment[(offs >> 2) + 1]));
}
int mdl_read_rodata_as_int(int offs)
{
return int(uMaterialRODataSegment[offs >> 2]);
}
uint mdl_read_rodata_as_uint(int offs)
{
return uMaterialRODataSegment[offs >> 2];
}
bool mdl_read_rodata_as_bool(int offs)
{
uint val = uMaterialRODataSegment[offs >> 2];
return (val & (0xff << (8 * (offs & 3)))) != 0;
}
#endif // USE_RO_DATA_SEGMENT
//-----------------------------------------------------------------------------
// Texture helper functions
//-----------------------------------------------------------------------------
// corresponds to ::tex::texture_isvalid(uniform texture_2d tex)
// corresponds to ::tex::texture_isvalid(uniform texture_3d tex)
// corresponds to ::tex::texture_isvalid(uniform texture_cube tex) // not supported by this example
// corresponds to ::tex::texture_isvalid(uniform texture_ptex tex) // not supported by this example
bool tex_texture_isvalid(int tex)
{
// assuming that there is no indexing out of bounds of the texture arrays
return tex != 0; // invalid texture
}
// helper function to realize wrap and crop.
// Out of bounds case for TEX_WRAP_CLIP must already be handled.
float apply_wrap_and_crop(float coord, int wrap, vec2 crop, int res)
{
if (wrap != TEX_WRAP_REPEAT || crop.x != 0.0 || crop.y != 1.0)
{
if (wrap == TEX_WRAP_REPEAT)
{
coord -= floor(coord);
}
else
{
if (wrap == TEX_WRAP_MIRRORED_REPEAT)
{
float floored_val = floor(coord);
if ((int(floored_val) & 1) != 0)
coord = 1 - (coord - floored_val);
else
coord -= floored_val;
}
float inv_hdim = 0.5 / float(res);
coord = clamp(coord, inv_hdim, 1.0 - inv_hdim);
}
coord = coord * (crop.y - crop.x) + crop.x;
}
return coord;
}
// Modify texture coordinates to get better texture filtering,
// see http://www.iquilezles.org/www/articles/texture/texture.htm
vec2 apply_smootherstep_filter(vec2 uv, ivec2 size)
{
vec2 res = uv * vec2(size) + 0.5;
vec2 i = floor(res);
vec2 f = res - i;
f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
return ((i + f) - 0.5) / vec2(size);
}
//-----------------------------------------------------------------------------
// Texture function implementations, 2D
//-----------------------------------------------------------------------------
#if (NUM_MATERIAL_TEXTURES_2D > 0)
// corresponds to ::tex::width(uniform texture_2d tex, int2 uv_tile, float frame)
int tex_width_2d(int tex, ivec2 uv_tile, float frame)
{
if (tex == 0) return 0; // invalid texture
uint texture_index = uMaterialTextureIndices[tex - 1];
return textureSize(uMaterialTextures2D[texture_index], 0).x;
}
// corresponds to ::tex::height(uniform texture_2d tex, int2 uv_tile, float frame)
int tex_height_2d(int tex, ivec2 uv_tile, float frame)
{
if (tex == 0) return 0; // invalid texture
uint texture_index = uMaterialTextureIndices[tex - 1];
return textureSize(uMaterialTextures2D[texture_index], 0).y;
}
// corresponds to ::tex::lookup_float4(uniform texture_2d tex, float2 coord, ...)
vec4 tex_lookup_float4_2d(int tex, vec2 coord, int wrap_u, int wrap_v, vec2 crop_u, vec2 crop_v, float frame)
{
if (tex == 0) return vec4(0.0); // invalid texture
if (wrap_u == TEX_WRAP_CLIP && (coord.x < 0.0 || coord.x >= 1.0))
return vec4(0.0);
if (wrap_v == TEX_WRAP_CLIP && (coord.y < 0.0 || coord.y >= 1.0))
return vec4(0.0);
uint texture_index = uMaterialTextureIndices[tex - 1];
ivec2 tex_size = textureSize(uMaterialTextures2D[texture_index], 0);
coord.x = apply_wrap_and_crop(coord.x, wrap_u, crop_u, tex_size.x);
coord.y = apply_wrap_and_crop(coord.y, wrap_v, crop_v, tex_size.y);
coord = apply_smootherstep_filter(coord, tex_size);
return texture(uMaterialTextures2D[texture_index], coord);
}
vec3 tex_lookup_float3_2d(int tex, vec2 coord, int wrap_u, int wrap_v, vec2 crop_u, vec2 crop_v, float frame)
{
return tex_lookup_float4_2d(tex, coord, wrap_u, wrap_v, crop_u, crop_v, frame).xyz;
}
vec3 tex_lookup_color_2d(int tex, vec2 coord, int wrap_u, int wrap_v, vec2 crop_u, vec2 crop_v, float frame)
{
return tex_lookup_float4_2d(tex, coord, wrap_u, wrap_v, crop_u, crop_v, frame).xyz;
}
vec2 tex_lookup_float2_2d(int tex, vec2 coord, int wrap_u, int wrap_v, vec2 crop_u, vec2 crop_v, float frame)
{
return tex_lookup_float4_2d(tex, coord, wrap_u, wrap_v, crop_u, crop_v, frame).xy;
}
float tex_lookup_float_2d(int tex, vec2 coord, int wrap_u, int wrap_v, vec2 crop_u, vec2 crop_v, float frame)
{
return tex_lookup_float4_2d(tex, coord, wrap_u, wrap_v, crop_u, crop_v, frame).x;
}
// corresponds to ::tex::texel_float4(uniform texture_2d tex, int2 coord, int2 uv_tile, float frame)
vec4 tex_texel_float4_2d(int tex, ivec2 coord, ivec2 uv_tile, float frame)
{
if (tex == 0) return vec4(0.0); // invalid texture
uint texture_index = uMaterialTextureIndices[tex - 1];
ivec2 res = textureSize(uMaterialTextures2D[texture_index], 0);
if (coord.x < 0 || coord.y < 0 || coord.x >= res.x || coord.y >= res.y)
return vec4(0.0); // out of bounds
return texelFetch(uMaterialTextures2D[texture_index], coord, 0);
}
vec3 tex_texel_float3_2d(int tex, ivec2 coord, ivec2 uv_tile, float frame)
{
return tex_texel_float4_2d(tex, coord, uv_tile, frame).xyz;
}
vec3 tex_texel_color_2d(int tex, ivec2 coord, ivec2 uv_tile, float frame)
{
return tex_texel_float4_2d(tex, coord, uv_tile, frame).xyz;
}
vec2 tex_texel_float2_2d(int tex, ivec2 coord, ivec2 uv_tile, float frame)
{
return tex_texel_float4_2d(tex, coord, uv_tile, frame).xy;
}
float tex_texel_float_2d(int tex, ivec2 coord, ivec2 uv_tile, float frame)
{
return tex_texel_float4_2d(tex, coord, uv_tile, frame).x;
}
#endif // (NUM_MATERIAL_TEXTURES_2D > 0)
//-----------------------------------------------------------------------------
// Texture function implementations, 3D
//-----------------------------------------------------------------------------
#if (NUM_MATERIAL_TEXTURES_3D > 0)
// corresponds to ::tex::width(uniform texture_3d tex, float frame)
int tex_width_3d(int tex, float frame)
{
if (tex == 0) return 0; // invalid texture
uint texture_index = uMaterialTextureIndices[tex - 1];
return textureSize(uMaterialTextures3D[texture_index], 0).x;
}
// corresponds to ::tex::height(uniform texture_3d tex, float frame)
int tex_height_3d(int tex, float frame)
{
if (tex == 0) return 0; // invalid texture
uint texture_index = uMaterialTextureIndices[tex - 1];
return textureSize(uMaterialTextures3D[texture_index], 0).y;
}
// corresponds to ::tex::depth(uniform texture_3d tex, float frame)
int tex_depth_3d(int tex, float frame)
{
if (tex == 0) return 0; // invalid texture
uint texture_index = uMaterialTextureIndices[tex - 1];
return textureSize(uMaterialTextures3D[texture_index], 0).z;
}
// corresponds to ::tex::lookup_float4(uniform texture_3d tex, float3 coord, ...)
vec4 tex_lookup_float4_3d(int tex, vec3 coord, int wrap_u, int wrap_v, int wrap_w, vec2 crop_u, vec2 crop_v, vec2 crop_w, float frame)
{
if (tex == 0) return vec4(0.0); // invalid texture
if (wrap_u == TEX_WRAP_CLIP && (coord.x < 0.0 || coord.x >= 1.0))
return vec4(0.0);
if (wrap_v == TEX_WRAP_CLIP && (coord.y < 0.0 || coord.y >= 1.0))
return vec4(0.0);
if (wrap_w == TEX_WRAP_CLIP && (coord.z < 0.0 || coord.z >= 1.0))
return vec4(0.0);
uint texture_index = uMaterialTextureIndices[tex - 1];
ivec3 tex_size = textureSize(uMaterialTextures3D[texture_index], 0);
coord.x = apply_wrap_and_crop(coord.x, wrap_u, crop_u, tex_size.x);
coord.y = apply_wrap_and_crop(coord.y, wrap_v, crop_v, tex_size.y);
coord.z = apply_wrap_and_crop(coord.z, wrap_w, crop_w, tex_size.z);
return texture(uMaterialTextures3D[texture_index], coord);
}
vec3 tex_lookup_float3_3d(int tex, vec3 coord, int wrap_u, int wrap_v, int wrap_w, vec2 crop_u, vec2 crop_v, vec2 crop_w, float frame)
{
return tex_lookup_float4_3d(tex, coord, wrap_u, wrap_v, wrap_w, crop_u, crop_v, crop_w, frame).xyz;
}
vec3 tex_lookup_color_3d(int tex, vec3 coord, int wrap_u, int wrap_v, int wrap_w, vec2 crop_u, vec2 crop_v, vec2 crop_w, float frame)
{
return tex_lookup_float4_3d(tex, coord, wrap_u, wrap_v, wrap_w, crop_u, crop_v, crop_w, frame).xyz;
}
vec2 tex_lookup_float2_3d(int tex, vec3 coord, int wrap_u, int wrap_v, int wrap_w, vec2 crop_u, vec2 crop_v, vec2 crop_w, float frame)
{
return tex_lookup_float4_3d(tex, coord, wrap_u, wrap_v, wrap_w, crop_u, crop_v, crop_w, frame).xy;
}
float tex_lookup_float_3d(int tex, vec3 coord, int wrap_u, int wrap_v, int wrap_w, vec2 crop_u, vec2 crop_v, vec2 crop_w, float frame)
{
return tex_lookup_float4_3d(tex, coord, wrap_u, wrap_v, wrap_w, crop_u, crop_v, crop_w, frame).x;
}
// corresponds to ::tex::texel_float4(uniform texture_3d tex, int3 coord, float frame)
vec4 tex_texel_float4_3d(int tex, ivec3 coord, float frame)
{
if (tex == 0) return vec4(0.0); // invalid texture
uint texture_index = uMaterialTextureIndices[tex - 1];
ivec3 res = textureSize(uMaterialTextures3D[texture_index], 0);
if (coord.x < 0 || coord.y < 0 || coord.z < 0 || coord.x >= res.x || coord.y >= res.y || coord.z >= res.z)
return vec4(0.0); // out of bounds
return texelFetch(uMaterialTextures3D[texture_index], coord, 0);
}
vec3 tex_texel_float3_3d(int tex, ivec3 coord, float frame)
{
return tex_texel_float4_3d(tex, coord, frame).xyz;
}
vec3 tex_texel_color_3d(int tex, ivec3 coord, float frame)
{
return tex_texel_float4_3d(tex, coord, frame).xyz;
}
vec2 tex_texel_float2_3d(int tex, ivec3 coord, float frame)
{
return tex_texel_float4_3d(tex, coord, frame).xy;
}
float tex_texel_float_3d(int tex, ivec3 coord, float frame)
{
return tex_texel_float4_3d(tex, coord, frame).x;
}
#endif // (NUM_MATERIAL_TEXTURES_3D > 0)
// ------------------------------------------------------------------------------------------------
// Texture function implementations, Cube (not supported by this example)
// ------------------------------------------------------------------------------------------------
// corresponds to ::tex::width(uniform texture_cube tex)
int tex_width_cube(int tex)
{
return 0;
}
// corresponds to ::tex::height(uniform texture_cube tex)
int tex_height_cube(int tex)
{
return 0;
}
// corresponds to ::tex::lookup_float4(uniform texture_cube tex, float3 coord)
vec4 tex_lookup_float4_cube(int tex, vec3 coord)
{
return vec4(0.0);
}
vec3 tex_lookup_float3_cube(int tex, vec3 coord)
{
return tex_lookup_float4_cube(tex, coord).xyz;
}
vec3 tex_lookup_color_cube(int tex, vec3 coord)
{
return tex_lookup_float4_cube(tex, coord).xyz;
}
vec2 tex_lookup_float2_cube(int tex, vec3 coord)
{
return tex_lookup_float4_cube(tex, coord).xy;
}
float tex_lookup_float_cube(int tex, vec3 coord)
{
return tex_lookup_float4_cube(tex, coord).x;
}
// corresponds to ::tex::texel_float4(uniform texture_cube tex, int3 coord)
vec4 tex_texel_float4_cube(int tex, ivec3 coord)
{
return vec4(0.0);
}
vec3 tex_texel_float3_cube(int tex, ivec3 coord)
{
return tex_texel_float4_cube(tex, coord).xyz;
}
vec3 tex_texel_color_cube(int tex, ivec3 coord)
{
return tex_texel_float4_cube(tex, coord).xyz;
}
vec2 tex_texel_float2_cube(int tex, ivec3 coord)
{
return tex_texel_float4_cube(tex, coord).xy;
}
float tex_texel_float_cube(int tex, ivec3 coord)
{
return tex_texel_float4_cube(tex, coord).x;
}
//-----------------------------------------------------------------------------
// Texture function implementations, PTEX (not supported by this example)
//-----------------------------------------------------------------------------
vec4 tex_lookup_float4_ptex(int tex, int channel)
{
return vec4(0.0);
}
vec3 tex_lookup_float3_ptex(int tex, int channel)
{
return tex_lookup_float4_ptex(tex, channel).xyz;
}
vec3 tex_lookup_color_ptex(int tex, int channel)
{
return tex_lookup_float3_ptex(tex, channel);
}
vec2 tex_lookup_float2_ptex(int tex, int channel)
{
return tex_lookup_float4_ptex(tex, channel).xy;
}
float tex_lookup_float_ptex(int tex, int channel)
{
return tex_lookup_float4_ptex(tex, channel).x;
}
// ------------------------------------------------------------------------------------------------
// Light Profiles function implementations (not supported by this example)
// ------------------------------------------------------------------------------------------------
bool df_light_profile_isvalid(int lp_idx)
{
return false;
}
float df_light_profile_power(int lp_idx)
{
return 0.0;
}
float df_light_profile_maximum(int lp_idx)
{
return 0.0;
}
float df_light_profile_evaluate(int lp_idx, vec2 theta_phi)
{
return 0.0;
}
vec3 df_light_profile_sample(int lp_idx, vec3 xi)
{
return vec3(0.0);
}
float df_light_profile_pdf(int lp_idx, vec2 theta_phi)
{
return 0.0;
}
// ------------------------------------------------------------------------------------------------
// Measured BSDFs function implementations (not supported by this example)
// ------------------------------------------------------------------------------------------------
bool df_bsdf_measurement_isvalid(int bm_idx)
{
return false;
}
ivec3 df_bsdf_measurement_resolution(int bm_idx, int part)
{
return ivec3(0);
}
vec3 df_bsdf_measurement_evaluate(int bm_idx, vec2 theta_phi_in, vec2 theta_phi_out, int part)
{
return vec3(0.0);
}
vec3 df_bsdf_measurement_sample(int bm_idx, vec2 theta_phi_out, vec3 xi, int part)
{
return vec3(0.0);
}
float df_bsdf_measurement_pdf(int bm_idx, vec2 theta_phi_in, vec2 theta_phi_out, int part)
{
return 0.0;
}
vec4 df_bsdf_measurement_albedos(int bm_idx, vec2 theta_phi)
{
return vec4(0.0);
}
// ------------------------------------------------------------------------------------------------
// Scene Data API function implementations (not supported by this example)
// ------------------------------------------------------------------------------------------------
bool scene_data_isvalid(State state, int scene_data_id)
{
return false;
}
vec4 scene_data_lookup_float4(State state, int scene_data_id, vec4 default_value, bool uniform_lookup)
{
return default_value;
}
vec3 scene_data_lookup_float3(State state, int scene_data_id, vec3 default_value, bool uniform_lookup)
{
return default_value;
}
vec3 scene_data_lookup_color(State state, int scene_data_id, vec3 default_value, bool uniform_lookup)
{
return default_value;
}
vec2 scene_data_lookup_float2(State state, int scene_data_id, vec2 default_value, bool uniform_lookup)
{
return default_value;
}
float scene_data_lookup_float(State state, int scene_data_id, float default_value, bool uniform_lookup)
{
return default_value;
}
ivec4 scene_data_lookup_int4(State state, int scene_data_id, ivec4 default_value, bool uniform_lookup)
{
return default_value;
}
ivec3 scene_data_lookup_int3(State state, int scene_data_id, ivec3 default_value, bool uniform_lookup)
{
return default_value;
}
ivec2 scene_data_lookup_int2(State state, int scene_data_id, ivec2 default_value, bool uniform_lookup)
{
return default_value;
}
int scene_data_lookup_int(State state, int scene_data_id, int default_value, bool uniform_lookup)
{
return default_value;
}
mat4 scene_data_lookup_float4x4(State state, int scene_data_id, mat4 default_value, bool uniform_lookup)
{
return default_value;
}
#endif // MDL_RUNTIME_GLSL

Source Code Location: examples/mdl_sdk/df_vulkan/display.vert

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/df_vulkan/display.frag
#version 450
layout(location = 0) out vec4 FragColor;
layout(rgba32f, set = 0, binding = 0) uniform readonly restrict image2D uBeautyBuffer;
layout(rgba32f, set = 0, binding = 1) uniform readonly restrict image2D uAuxAlbedoBuffer;
layout(rgba32f, set = 0, binding = 2) uniform readonly restrict image2D uAuxNormalBuffer;
layout(push_constant) uniform UserData
{
uint uBufferIndex;
};
void main()
{
ivec2 uv = ivec2(gl_FragCoord.xy);
// Flip image because Vulkan uses the bottom-left corner as the origin,
// but the rendering code assumed the origin to be the top-left corner.
uv.y = imageSize(uBeautyBuffer).y - uv.y - 1;
vec3 color;
switch (uBufferIndex)
{
case 1:
color = imageLoad(uAuxAlbedoBuffer, uv).xyz;
break;
case 2:
color = imageLoad(uAuxNormalBuffer, uv).xyz;
if (dot(color, color) > 0.01)
color = normalize(color) * 0.5 + 0.5;
break;
default:
color = imageLoad(uBeautyBuffer, uv).xyz;
break;
}
// Apply reinhard tone mapping
const float burn_out = 0.1;
color *= (vec3(1.0) + color * burn_out) / (vec3(1.0) + color);
// Apply gamma correction
color = pow(clamp(color, 0.0, 1.0), vec3(1.0 / 2.2));
FragColor = vec4(color, 1.0);
}

Source Code Location: examples/mdl_sdk/df_vulkan/display.vert

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/df_vulkan/display.frag
#version 450
layout(location = 0) out vec4 FragColor;
layout(rgba32f, set = 0, binding = 0) uniform readonly restrict image2D uBeautyBuffer;
layout(rgba32f, set = 0, binding = 1) uniform readonly restrict image2D uAuxAlbedoBuffer;
layout(rgba32f, set = 0, binding = 2) uniform readonly restrict image2D uAuxNormalBuffer;
layout(push_constant) uniform UserData
{
uint uBufferIndex;
};
void main()
{
ivec2 uv = ivec2(gl_FragCoord.xy);
// Flip image because Vulkan uses the bottom-left corner as the origin,
// but the rendering code assumed the origin to be the top-left corner.
uv.y = imageSize(uBeautyBuffer).y - uv.y - 1;
vec3 color;
switch (uBufferIndex)
{
case 1:
color = imageLoad(uAuxAlbedoBuffer, uv).xyz;
break;
case 2:
color = imageLoad(uAuxNormalBuffer, uv).xyz;
if (dot(color, color) > 0.01)
color = normalize(color) * 0.5 + 0.5;
break;
default:
color = imageLoad(uBeautyBuffer, uv).xyz;
break;
}
// Apply reinhard tone mapping
const float burn_out = 0.1;
color *= (vec3(1.0) + color * burn_out) / (vec3(1.0) + color);
// Apply gamma correction
color = pow(clamp(color, 0.0, 1.0), vec3(1.0 / 2.2));
FragColor = vec4(color, 1.0);
}

Source Code Location: examples/mdl_sdk/shared/example_vulkan_shared.h

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/shared/example_vulkan_shared.h
//
// Code shared by all Vulkan examples.
#ifndef EXAMPLE_VULKAN_SHARED_H
#define EXAMPLE_VULKAN_SHARED_H
#include <memory>
#include <vector>
#include <iostream>
#include <vulkan/vulkan.h>
#include <glslang/Public/ShaderLang.h>
#include "example_shared.h"
#define terminate() \
do { \
glfwTerminate(); \
exit_failure(); \
} while (0)
#define VK_CHECK(x) \
do { \
VkResult err = x; \
if (err != VK_SUCCESS) { \
std::cerr << "Vulkan error " \
<< mi::examples::vk::vkresult_to_str(err) \
<< " (" << err << ")" \
<< " in file " << __FILE__ \
<< ", line " << __LINE__ << ".\n"; \
terminate(); \
} \
} while (0)
// Extensions
extern PFN_vkCreateDebugUtilsMessengerEXT CreateDebugUtilsMessengerEXT;
extern PFN_vkDestroyDebugUtilsMessengerEXT DestroyDebugUtilsMessengerEXT;
struct GLFWwindow;
namespace mi::examples::vk
{
// Compiles GLSL source code to SPIR-V which can be used to create Vulkan shader modules.
// Multiple shaders of the same type (e.g. EShLangFragment) are linked into a single
// SPIR-V module. Newer SPIR-V versions can be generated by changing the relevant
// parameters for TShader::setEnvClient and TShader::setEnvTarget.
class Glsl_compiler
{
public:
Glsl_compiler(EShLanguage shader_type, const char* entry_point = "main")
: m_shader_type(shader_type)
, m_entry_point(entry_point)
{
}
// Adds a list of #define to each shader's preamble parsed after this method is called.
// Call this method BEFORE any call to add_shader.
// Example: add_define({";MY_DEF=1", ";MY_OTHER_DEF", ";MY_THIRD_DEF 3"})
void add_defines(const std::vector<std::string>& defines);
// Parses the given shader source and adds it to the shader program
// which can be compiled to SPIR-V by link_program.
void add_shader(std::string_view source);
// Links all previously added shaders and compiles the linked program to SPIR-V.
std::vector<unsigned int> link_program(bool optimize = true);
private:
class Simple_file_includer : public glslang::TShader::Includer
{
public:
virtual ~Simple_file_includer() = default;
virtual IncludeResult* includeSystem(const char* header_name,
const char* includer_name, size_t inclusion_depth) override;
virtual IncludeResult* includeLocal(const char* header_name,
const char* includer_name, size_t inclusion_depth) override;
virtual void releaseInclude(IncludeResult* include_result) override;
};
private:
static const EShMessages s_messages
= static_cast<EShMessages>(EShMsgVulkanRules | EShMsgSpvRules);
EShLanguage m_shader_type;
std::string m_entry_point;
std::vector<std::unique_ptr<glslang::TShader>> m_shaders;
Simple_file_includer m_file_includer;
std::string m_shader_preamble;
};
class Vulkan_example_app
{
public:
struct Config
{
std::string window_title = "MDL SDK Vulkan Example";
uint32_t image_width = 1024;
uint32_t image_height = 768;
uint32_t image_count = 3;
std::vector<VkFormat> preferred_image_formats
= { VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM };
bool headless = false;
uint32_t iteration_count = 1; // Headless mode only
bool enable_validation_layers = false;
};
public:
Vulkan_example_app(
: m_mdl_impexp_api(mdl_impexp_api, mi::base::DUP_INTERFACE)
, m_image_api(image_api, mi::base::DUP_INTERFACE)
{
}
virtual ~Vulkan_example_app() = default;
void run(const Config& config);
protected:
// The callback for initializing all rendering related resources. Is called
// after all resources (device, swapchain, etc.) of this base class are
// initialized.
virtual void init_resources() {}
// The callback for destroying all rendering related resources. Is called
// before all resources (device, swapchain, etc.) of this base class are
// destroyed.
virtual void cleanup_resources() {}
// Render resources are split into framebuffer size dependent
// and independent resources so that the correct resources are
// recreated when the framebuffer is resized.
virtual void recreate_size_dependent_resources() {}
// The callback to update the application logic. Is called directly after
// a new swapchain image is acquired and after waiting for the fence for
// the current frame's command buffer.
virtual void update(float elapsed_seconds, uint32_t frame_index) = 0;
// The callback to fill the current frame's command buffer. Is called directly
// after vkBeginCommandBuffer and before vkEndCommandBuffer.
virtual void render(VkCommandBuffer command_buffer, uint32_t frame_index, uint32_t image_index) = 0;
// Called when a keyboard key is pressed or released.
virtual void key_callback(int key, int action) {}
// Called when a mouse button is pressed or released.
virtual void mouse_button_callback(int button, int action) {}
// Called when the mouse moves.
virtual void mouse_move_callback(float pos_x, float pos_y) {}
// Called when the scrolling event occurs (e.g. mouse wheel or touch pad).
virtual void mouse_scroll_callback(float offset_x, float offset_y) {}
// Called when the window is resized.
virtual void resized_callback(uint32_t width, uint32_t height) {}
// Request to save a screenshot the next frame.
void request_screenshot() { m_screenshot_requested = true; }
// Save the specified swapchain image to a file.
void save_screenshot(uint32_t image_index, const char* filename) const;
protected:
// MDL image interfaces.
GLFWwindow* m_window = nullptr;
VkInstance m_instance = nullptr;
VkSurfaceKHR m_surface = nullptr;
VkPhysicalDevice m_physical_device = nullptr;
VkDevice m_device = nullptr;
uint32_t m_graphics_queue_family_index;
uint32_t m_present_queue_family_index;
VkQueue m_graphics_queue = nullptr;
VkQueue m_present_queue = nullptr;
// Framebuffer information for either the swapchain
// images or headless framebuffer.
VkFormat m_image_format;
uint32_t m_image_width;
uint32_t m_image_height;
uint32_t m_image_count;
// Swapchain is only created if a window is used.
VkSwapchainKHR m_swapchain = nullptr;
std::vector<VkImage> m_swapchain_images;
std::vector<VkImageView> m_swapchain_image_views;
std::vector<VkSemaphore> m_image_available_semaphores;
std::vector<VkSemaphore> m_render_finished_semaphores;
// For no window mode we have to handle device memory ourselves
std::vector<VkDeviceMemory> m_swapchain_device_memories;
// Depth stencil buffer
VkFormat m_depth_stencil_format;
VkImage m_depth_stencil_image = nullptr;
VkDeviceMemory m_depth_stencil_device_memory = nullptr;
VkImageView m_depth_stencil_image_view = nullptr;
std::vector<VkFence> m_frame_inflight_fences;
VkCommandPool m_command_pool = nullptr;
std::vector<VkCommandBuffer> m_command_buffers;
std::vector<VkFramebuffer> m_framebuffers;
VkRenderPass m_main_render_pass = nullptr;
private:
void init(const Config& config);
void cleanup();
void init_window();
void init_instance(
const std::vector<const char*>& instance_extensions,
const std::vector<const char*>& validation_layers);
void pick_physical_device(const std::vector<const char*>& device_extensions);
void init_device(
const std::vector<const char*>& device_extensions,
const std::vector<const char*>& validation_layers);
void init_swapchain_for_window();
void init_swapchain_for_headless();
void init_depth_stencil_buffer();
void init_render_pass();
void init_framebuffers();
void init_command_pool_and_buffers();
void init_synchronization_objects();
void recreate_swapchain_or_framebuffer_image();
void render_loop_iteration(uint32_t frame_index, uint32_t image_index, double& last_frame_time);
static void internal_key_callback(
GLFWwindow* window, int key, int scancode, int action, int mods);
static void internal_mouse_button_callback(
GLFWwindow* window, int button, int action, int mods);
static void internal_mouse_move_callback(GLFWwindow* window, double pos_x, double pos_y);
static void internal_mouse_scroll_callback(GLFWwindow* window, double offset_x, double offset_y);
static void internal_resize_callback(GLFWwindow* window, int width, int height);
static void glfw_error_callback(int error_code, const char* description);
static VKAPI_ATTR VkBool32 VKAPI_PTR debug_messenger_callback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_types,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data);
private:
Config m_config;
VkDebugUtilsMessengerEXT m_debug_messenger = nullptr;
bool m_framebuffer_resized = false;
bool m_screenshot_requested = false;
};
class Staging_buffer
{
public:
Staging_buffer(VkDevice device, VkPhysicalDevice physical_device,
VkDeviceSize size, VkBufferUsageFlags usage);
~Staging_buffer();
void* map_memory() const;
void unmap_memory() const;
VkBuffer get() const { return m_buffer; }
private:
VkDevice m_device;
VkBuffer m_buffer;
VkDeviceMemory m_device_memory;
};
class Temporary_command_buffer
{
public:
Temporary_command_buffer(VkDevice device, VkCommandPool command_pool);
~Temporary_command_buffer();
void begin();
void end_and_submit(VkQueue queue, bool wait = true);
VkCommandBuffer get() const { return m_command_buffer; }
private:
VkDevice m_device;
VkCommandPool m_command_pool;
VkCommandBuffer m_command_buffer;
};
// Extension helpers
bool load_debug_utils_extension(VkInstance instance);
bool check_instance_extensions_support(
const std::vector<const char*>& requested_extensions);
bool check_device_extensions_support(VkPhysicalDevice device,
const std::vector<const char*>& requested_extensions);
bool check_validation_layers_support(
const std::vector<const char*>& requested_layers);
// Shader compilation helpers
VkShaderModule create_shader_module_from_file(
VkDevice device, const char* shader_filename, EShLanguage shader_type,
const std::vector<std::string>& defines = {});
VkShaderModule create_shader_module_from_sources(
VkDevice device, const std::vector<std::string_view> shader_sources, EShLanguage shader_type,
const std::vector<std::string>& defines = {});
// Memory allocation helpers
VkDeviceMemory allocate_and_bind_buffer_memory(
VkDevice device, VkPhysicalDevice physical_device, VkBuffer buffer,
VkMemoryPropertyFlags memory_property_flags);
VkDeviceMemory allocate_and_bind_image_memory(
VkDevice device, VkPhysicalDevice physical_device, VkImage image,
VkMemoryPropertyFlags memory_property_flags);
uint32_t find_memory_type(
VkPhysicalDevice physical_device,
uint32_t memory_type_bits_requirement,
VkMemoryPropertyFlags required_properties);
// Format helpers
VkFormat find_supported_format(
VkPhysicalDevice physical_device,
const std::vector<VkFormat>& formats,
VkImageTiling tiling,
VkFormatFeatureFlags feature_flags);
bool has_stencil_component(VkFormat format);
uint32_t get_image_format_bpp(VkFormat format);
// Initialization helpers
VkRenderPass create_simple_color_only_render_pass(
VkDevice device, VkFormat image_format, VkImageLayout final_layout);
VkRenderPass create_simple_render_pass(
VkDevice device, VkFormat image_format,
VkFormat depth_stencil_format, VkImageLayout final_layout);
VkSampler create_linear_sampler(VkDevice device);
// Misc helpers
std::vector<uint8_t> copy_image_to_buffer(
VkDevice device, VkPhysicalDevice physical_device, VkCommandPool command_pool, VkQueue queue,
VkImage image, uint32_t image_width, uint32_t image_height, uint32_t image_bpp,
VkImageLayout image_layout, bool flip);
const char* vkresult_to_str(VkResult result);
// Convert some of the common formats to string.
const char* vkformat_to_str(VkFormat format);
// Default resource values for glslang.
const TBuiltInResource* get_default_resource_limits();
} // namespace mi::examples::vk
#endif // EXAMPLE_VULKAN_SHARED_H
static const Dup_interface DUP_INTERFACE
Symbolic constant to trigger a special constructor in the Handle class.
Definition: handle.h:37
Common namespace for APIs of NVIDIA Advanced Rendering Center GmbH.
Definition: example_derivatives.dox:5

Source Code Location: examples/mdl_sdk/shared/example_vulkan_shared.cpp

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/shared/example_vulkan_shared.cpp
//
// Code shared by all Vulkan examples.
#include "example_vulkan_shared.h"
#include <GLFW/glfw3.h>
#include <glslang/SPIRV/GlslangToSpv.h>
#include <unordered_set>
#include <algorithm>
// Extensions
PFN_vkCreateDebugUtilsMessengerEXT CreateDebugUtilsMessengerEXT;
PFN_vkDestroyDebugUtilsMessengerEXT DestroyDebugUtilsMessengerEXT;
namespace
{
// Similar to std::remove_copy_if, but returns an iterator that can be used in erase
template <typename Src_iterator, typename Dst_iterator, typename Pred>
Src_iterator splice_if(Src_iterator first, Src_iterator last, Dst_iterator out, Pred p)
{
Src_iterator result = first;
for (; first != last; ++first)
{
if (p(*first))
*result++ = *first;
else
*out++ = *first;
}
return result;
}
} // namespace
namespace mi::examples::vk
{
void Glsl_compiler::add_defines(const std::vector<std::string>& defines)
{
for (std::string define : defines)
{
// Replace first = with a space.
// E.g. MY_DEFINE=1 -> MY_DEFINE 1
const size_t equal_pos = define.find_first_of("=");
if (equal_pos != define.npos)
define[equal_pos] = ' ';
m_shader_preamble += "#define ";
m_shader_preamble += define;
m_shader_preamble += '\n';
}
}
// Parses the given shader source and adds it to the shader program
// which can be compiled to SPIR-V by link_program.
void Glsl_compiler::add_shader(std::string_view source)
{
std::unique_ptr<glslang::TShader>& shader =
m_shaders.emplace_back(std::make_unique<glslang::TShader>(m_shader_type));
const char* sources[] = { source.data() };
const int lengths[] = { static_cast<int>(source.length()) };
shader->setStringsWithLengths(sources, lengths, 1);
shader->setEntryPoint(m_entry_point.c_str());
shader->setEnvClient(
glslang::EShClientVulkan,
glslang::EshTargetClientVersion::EShTargetVulkan_1_0);
shader->setEnvTarget(
glslang::EShTargetLanguage::EShTargetSpv,
glslang::EShTargetLanguageVersion::EShTargetSpv_1_0);
shader->setEnvInput(
glslang::EShSourceGlsl, m_shader_type,
glslang::EShClient::EShClientVulkan, 100);
shader->setPreamble(m_shader_preamble.c_str());
bool success = shader->parse(
/*builtInResource=*/ mi::examples::vk::get_default_resource_limits(),
/*defaultVersion=*/ 100, // Will be overridden by #version in shader source
/*forwardCompatible=*/ true,
/*messages =*/ s_messages,
/*includer =*/ m_file_includer);
if (!success)
{
std::cerr << "Compilation for shader "
<< (m_shaders.size() - 1) << " failed:\n"
<< shader->getInfoLog()
<< shader->getInfoDebugLog();
terminate();
}
}
// Links all previously added shaders and compiles the linked program to SPIR-V.
std::vector<unsigned int> Glsl_compiler::link_program(bool optimize)
{
glslang::TProgram program;
for (std::unique_ptr<glslang::TShader>& shader : m_shaders)
program.addShader(shader.get());
if (!program.link(s_messages))
{
std::cerr << "Shader program linking failed:\n"
<< program.getInfoLog()
<< program.getInfoDebugLog();
terminate();
}
glslang::SpvOptions spv_options;
spv_options.disableOptimizer = !optimize;
std::vector<unsigned int> spirv;
glslang::GlslangToSpv(*program.getIntermediate(m_shader_type), spirv, &spv_options);
return spirv;
}
glslang::TShader::Includer::IncludeResult* Glsl_compiler::Simple_file_includer::includeSystem(
const char* header_name, const char* includer_name, size_t inclusion_depth)
{
// Not supported
return nullptr;
}
glslang::TShader::Includer::IncludeResult* Glsl_compiler::Simple_file_includer::includeLocal(
const char* header_name, const char* includer_name, size_t inclusion_depth)
{
std::string filename(header_name);
// Drop strict relative marker
if (mi::examples::strings::starts_with(filename, "./"))
filename = filename.substr(2);
// Make path absolute if not already
if (!mi::examples::io::is_absolute_path(filename))
filename = mi::examples::io::get_executable_folder() + "/" + filename;
std::ifstream file_stream(filename, std::ios_base::binary | std::ios_base::ate);
if (!file_stream.is_open())
{
std::cerr << "Shader include file \"" << filename << "\" not found.";
terminate();
}
size_t length = file_stream.tellg();
char* content = new char[length];
file_stream.seekg(0, std::ios::beg);
file_stream.read(content, length);
return new IncludeResult(header_name, content, length, content);
}
void Glsl_compiler::Simple_file_includer::releaseInclude(IncludeResult* include_result)
{
if (include_result)
{
delete[] static_cast<char*>(include_result->userData);
delete include_result;
}
}
void Vulkan_example_app::run(const Config& config)
{
init(config);
double last_frame_time = glfwGetTime();
if (!m_config.headless)
{
uint32_t frame_index = 0;
while (!glfwWindowShouldClose(m_window))
{
glfwPollEvents();
// Wait for the GPU to finish the current command buffer
VK_CHECK(vkWaitForFences(
m_device, 1, &m_frame_inflight_fences[frame_index], true, UINT64_MAX));
VK_CHECK(vkResetFences(
m_device, 1, &m_frame_inflight_fences[frame_index]));
uint32_t image_index;
VkResult result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX,
m_image_available_semaphores[frame_index], nullptr, &image_index);
if (result == VK_ERROR_OUT_OF_DATE_KHR)
{
recreate_swapchain_or_framebuffer_image();
continue;
}
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
VK_CHECK(result); // This will output the error
render_loop_iteration(frame_index, image_index, last_frame_time);
if (m_screenshot_requested)
{
m_screenshot_requested = false;
save_screenshot(image_index, "screenshot.png");
}
// Present the next image
VkPresentInfoKHR present_info = {};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &m_render_finished_semaphores[frame_index];
present_info.swapchainCount = 1;
present_info.pSwapchains = &m_swapchain;
present_info.pImageIndices = &image_index;
result = vkQueuePresentKHR(m_present_queue, &present_info);
if (result == VK_ERROR_OUT_OF_DATE_KHR
|| result == VK_SUBOPTIMAL_KHR
|| m_framebuffer_resized)
{
recreate_swapchain_or_framebuffer_image();
m_framebuffer_resized = false;
continue;
}
else if (result != VK_SUCCESS)
VK_CHECK(result); // This will output the error
frame_index = (frame_index + 1) % m_image_count;
}
}
else
{
for (uint32_t i = 0; i < m_config.iteration_count; i++)
{
uint32_t image_index = i % m_image_count;
// Wait for the GPU to finish the current command buffer
VK_CHECK(vkWaitForFences(
m_device, 1, &m_frame_inflight_fences[image_index], true, UINT64_MAX));
VK_CHECK(vkResetFences(
m_device, 1, &m_frame_inflight_fences[image_index]));
render_loop_iteration(image_index, image_index, last_frame_time);
}
}
// Wait for all resources to be unused
VK_CHECK(vkDeviceWaitIdle(m_device));
cleanup();
}
void Vulkan_example_app::save_screenshot(uint32_t image_index, const char* filename) const
{
uint32_t bpp = mi::examples::vk::get_image_format_bpp(m_image_format);
VkImageLayout image_layout = m_config.headless
? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
std::vector<uint8_t> pixels = mi::examples::vk::copy_image_to_buffer(
m_device, m_physical_device, m_command_pool, m_graphics_queue,
m_swapchain_images[image_index], m_image_width, m_image_height,
bpp, image_layout, true);
// Convert from BGRA to RGBA if needed.
if (m_image_format == VK_FORMAT_B8G8R8A8_UNORM
|| m_image_format == VK_FORMAT_B8G8R8A8_SRGB)
{
for (uint32_t i = 0; i < m_image_width * m_image_height; i++)
std::swap(pixels[i * 4 + 0], pixels[i * 4 + 2]);
}
m_image_api->create_canvas("Rgba", m_image_width, m_image_height));
mi::base::Handle<mi::neuraylib::ITile> tile(canvas->get_tile());
std::memcpy(tile->get_data(), pixels.data(), pixels.size());
m_mdl_impexp_api->export_canvas(filename, canvas.get());
}
void Vulkan_example_app::init(const Config& config)
{
m_config = config;
if (!m_config.headless)
init_window();
// Gather extensions and validation layers
std::vector<const char*> instance_extensions;
std::vector<const char*> device_extensions;
std::vector<const char*> validation_layers;
if (!m_config.headless)
{
uint32_t glfw_extensions_count;
const char** glfw_extensions
= glfwGetRequiredInstanceExtensions(&glfw_extensions_count);
instance_extensions.insert(instance_extensions.end(),
glfw_extensions, glfw_extensions + glfw_extensions_count);
device_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
if (config.enable_validation_layers)
{
instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
validation_layers.push_back(";VK_LAYER_KHRONOS_validation");
}
// Determine if all validation layers are supported and present
if (!validation_layers.empty())
{
uint32_t layer_count;
VK_CHECK(vkEnumerateInstanceLayerProperties(&layer_count, nullptr));
std::vector<VkLayerProperties> available_layers(layer_count);
VK_CHECK(vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data()));
// Copy all unsupported layers into a separate vector and remove them from the original
std::vector<const char*> unsupported_layers;
auto it = splice_if(validation_layers.begin(), validation_layers.end(),
std::back_inserter(unsupported_layers),
[&available_layers](const char* requested_layer)
{
for (const VkLayerProperties& available_layer : available_layers)
{
if (std::strcmp(requested_layer, available_layer.layerName) == 0)
return true;
}
return false;
});
validation_layers.erase(it, validation_layers.end());
if (!unsupported_layers.empty())
{
std::cerr << "Not all requested instance layers are available. Could not find:\n";
for (const char* layer : unsupported_layers)
std::cerr << " " << layer << "\n";
std::cerr << "The environment variable VK_LAYER_PATH might need to be set.\n";
}
}
// Initialize Vulkan interfaces
init_instance(instance_extensions, validation_layers);
if (!m_config.headless)
VK_CHECK(glfwCreateWindowSurface(m_instance, m_window, nullptr, &m_surface));
pick_physical_device(device_extensions);
init_device(device_extensions, validation_layers);
vkGetDeviceQueue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue);
vkGetDeviceQueue(m_device, m_present_queue_family_index, 0, &m_present_queue);
if (config.headless)
init_swapchain_for_headless();
else
init_swapchain_for_window();
init_depth_stencil_buffer();
init_render_pass();
init_framebuffers();
init_command_pool_and_buffers();
init_synchronization_objects();
// Init application resources
init_resources();
}
void Vulkan_example_app::cleanup()
{
// Cleanup application resources
cleanup_resources();
for (VkFramebuffer framebuffer : m_framebuffers)
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
vkDestroyRenderPass(m_device, m_main_render_pass, nullptr);
vkDestroyCommandPool(m_device, m_command_pool, nullptr);
vkDestroyImageView(m_device, m_depth_stencil_image_view, nullptr);
vkDestroyImage(m_device, m_depth_stencil_image, nullptr);
vkFreeMemory(m_device, m_depth_stencil_device_memory, nullptr);
for (VkImageView image_view : m_swapchain_image_views)
vkDestroyImageView(m_device, image_view, nullptr);
if (m_config.headless)
{
for (VkImage image : m_swapchain_images)
vkDestroyImage(m_device, image, nullptr);
for (VkDeviceMemory device_memory : m_swapchain_device_memories)
vkFreeMemory(m_device, device_memory, nullptr);
}
else
vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
for (uint32_t i = 0; i < m_image_count; i++)
{
vkDestroyFence(m_device, m_frame_inflight_fences[i], nullptr);
vkDestroySemaphore(m_device, m_image_available_semaphores[i], nullptr);
vkDestroySemaphore(m_device, m_render_finished_semaphores[i], nullptr);
}
vkDestroyDevice(m_device, nullptr);
if (!m_config.headless)
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
if (m_debug_messenger)
DestroyDebugUtilsMessengerEXT(m_instance, m_debug_messenger, nullptr);
vkDestroyInstance(m_instance, nullptr);
glfwDestroyWindow(m_window);
glfwTerminate();
}
void Vulkan_example_app::init_window()
{
glfwSetErrorCallback(&glfw_error_callback);
if (!glfwInit())
{
std::cerr << "Failed to initialize GLFW.\n";
terminate();
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
m_window = glfwCreateWindow(
static_cast<int>(m_config.image_width),
static_cast<int>(m_config.image_height),
m_config.window_title.c_str(),
nullptr, nullptr);
if (!m_window)
{
std::cerr << "Failed to create GLFW window.\n";
terminate();
}
glfwSetWindowUserPointer(m_window, this);
glfwSetKeyCallback(m_window, &internal_key_callback);
glfwSetCursorPosCallback(m_window, &internal_mouse_move_callback);
glfwSetMouseButtonCallback(m_window, &internal_mouse_button_callback);
glfwSetScrollCallback(m_window, &internal_mouse_scroll_callback);
glfwSetFramebufferSizeCallback(m_window, &internal_resize_callback);
}
void Vulkan_example_app::init_instance(
const std::vector<const char*>& instance_extensions,
const std::vector<const char*>& validation_layers)
{
// Create Vulkan instance
VkApplicationInfo application_info = {};
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.pApplicationName = "MDL SDK Vulkan Example";
application_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
application_info.pEngineName = "MDL-SDK";
application_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
application_info.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo instance_create_info = {};
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pApplicationInfo = &application_info;
VkDebugUtilsMessengerCreateInfoEXT debug_utils_create_info = {};
debug_utils_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
debug_utils_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debug_utils_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debug_utils_create_info.pfnUserCallback = &debug_messenger_callback;
if (!validation_layers.empty())
{
// Add debug callback extension
instance_create_info.pNext = &debug_utils_create_info;
instance_create_info.ppEnabledLayerNames = validation_layers.data();
instance_create_info.enabledLayerCount = static_cast<uint32_t>(validation_layers.size());
}
instance_create_info.ppEnabledExtensionNames = instance_extensions.data();
instance_create_info.enabledExtensionCount = static_cast<uint32_t>(instance_extensions.size());
VK_CHECK(vkCreateInstance(&instance_create_info, nullptr, &m_instance));
if (!validation_layers.empty())
{
if (!mi::examples::vk::load_debug_utils_extension(m_instance))
{
std::cerr << "Failed loading the functions for VK_EXT_debug_utils.\n";
terminate();
}
VK_CHECK(CreateDebugUtilsMessengerEXT(
m_instance, &debug_utils_create_info, nullptr, &m_debug_messenger));
}
}
void Vulkan_example_app::pick_physical_device(
const std::vector<const char*>& device_extensions)
{
uint32_t physical_device_count;
VK_CHECK(vkEnumeratePhysicalDevices(m_instance, &physical_device_count, nullptr));
std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
VK_CHECK(vkEnumeratePhysicalDevices(
m_instance, &physical_device_count, physical_devices.data()));
// Find all physical device that support the requested extensions and queue families
struct Supported_gpu
{
VkPhysicalDevice physical_device;
uint32_t graphics_queue_family_index;
uint32_t present_queue_family_index;
};
std::vector<Supported_gpu> supported_gpus;
for (const VkPhysicalDevice& physical_device : physical_devices)
{
// Select queue families
uint32_t queue_family_count;
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr);
std::vector<VkQueueFamilyProperties> queue_family_props(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(
physical_device, &queue_family_count, queue_family_props.data());
uint32_t graphics_queue_family_index = ~0u;
uint32_t present_queue_family_index = ~0u;
for (uint32_t i = 0; i < queue_family_count; i++)
{
if (queue_family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
graphics_queue_family_index = i;
VkBool32 has_presentation_support;
if (!m_config.headless)
{
VK_CHECK(vkGetPhysicalDeviceSurfaceSupportKHR(
physical_device, i, m_surface, &has_presentation_support));
}
else
{
// For headless mode just treat every graphics queue as a
// valid "present" queue to make the setup a lot simpler.
has_presentation_support = true;
}
if (has_presentation_support)
present_queue_family_index = i;
if (graphics_queue_family_index != ~0u && present_queue_family_index != ~0u)
break;
}
if (graphics_queue_family_index == ~0u || present_queue_family_index == ~0u)
continue;
// The device must support all requested extensions
if (!check_device_extensions_support(physical_device, device_extensions))
continue;
supported_gpus.push_back(
{ physical_device, graphics_queue_family_index, present_queue_family_index });
}
if (supported_gpus.empty())
{
std::cerr << "No suitable physical device found.\n";
terminate();
}
// Pick the first discrete physical device
m_physical_device = nullptr;
if (supported_gpus.size() > 1)
{
std::cout << "Multiple supported GPUs detected, trying to pick first discrete one:\n";
for (size_t i = 0; i < supported_gpus.size(); i++)
{
VkPhysicalDeviceProperties physical_device_props;
vkGetPhysicalDeviceProperties(
supported_gpus[i].physical_device, &physical_device_props);
if (!m_physical_device
&& physical_device_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
{
m_physical_device = supported_gpus[i].physical_device;
m_graphics_queue_family_index = supported_gpus[i].graphics_queue_family_index;
m_present_queue_family_index = supported_gpus[i].present_queue_family_index;
}
std::cout << " " << (i + 1) << ". " << physical_device_props.deviceName;
switch (physical_device_props.deviceType)
{
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
std::cout << " [integrated]";
break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
std::cout << " [discrete]";
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
std::cout << " [virtual]";
break;
case VK_PHYSICAL_DEVICE_TYPE_CPU:
std::cout << " [cpu]";
break;
default:
std::cout << " [unknown]";
break;
}
std::cout << "\n";
}
std::cout << "\n";
}
// Either only one supported GPU was found or in the case of multiple GPUs,
// no discrete one was found. In either case the first one is picked.
if (!m_physical_device)
{
m_physical_device = supported_gpus[0].physical_device;
m_graphics_queue_family_index = supported_gpus[0].graphics_queue_family_index;
m_present_queue_family_index = supported_gpus[0].present_queue_family_index;
}
VkPhysicalDeviceProperties physical_device_props;
vkGetPhysicalDeviceProperties(
m_physical_device, &physical_device_props);
std::cout << "Chosen GPU: " << physical_device_props.deviceName << "\n\n";
}
void Vulkan_example_app::init_device(
const std::vector<const char*>& device_extensions,
const std::vector<const char*>& validation_layers)
{
std::unordered_set<uint32_t> unique_queue_families = {
m_graphics_queue_family_index,
m_present_queue_family_index
};
std::vector< VkDeviceQueueCreateInfo> queue_create_infos;
for (uint32_t queue_family_index : unique_queue_families)
{
float queue_priority = 1.0f;
VkDeviceQueueCreateInfo queue_create_info = {};
queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_create_info.queueFamilyIndex = queue_family_index;
queue_create_info.queueCount = 1;
queue_create_info.pQueuePriorities = &queue_priority;
queue_create_infos.push_back(queue_create_info);
}
VkDeviceCreateInfo device_create_info = {};
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.queueCreateInfoCount = static_cast<uint32_t>(queue_create_infos.size());
device_create_info.pQueueCreateInfos = queue_create_infos.data();
if (!validation_layers.empty())
{
device_create_info.ppEnabledLayerNames = validation_layers.data();
device_create_info.enabledLayerCount = static_cast<uint32_t>(validation_layers.size());
}
device_create_info.ppEnabledExtensionNames = device_extensions.data();
device_create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
VK_CHECK(vkCreateDevice(m_physical_device, &device_create_info, nullptr, &m_device));
}
void Vulkan_example_app::init_swapchain_for_window()
{
VkSurfaceCapabilitiesKHR surface_caps;
VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
m_physical_device, m_surface, &surface_caps));
// Find correct swapchain image size
m_image_width = surface_caps.currentExtent.width;
m_image_height = surface_caps.currentExtent.height;
if (m_image_width == 0xffffffff|| m_image_height == 0xffffffff)
{
// The extents in surface_caps are invalid, take the windows
// framebuffer size and make them fit the surface caps.
int framebuffer_width;
int framebuffer_height;
glfwGetFramebufferSize(m_window, &framebuffer_width, &framebuffer_height);
m_image_width = std::clamp(static_cast<uint32_t>(framebuffer_width),
surface_caps.minImageExtent.width, surface_caps.maxImageExtent.width);
m_image_height = std::clamp(static_cast<uint32_t>(framebuffer_height),
surface_caps.minImageExtent.height, surface_caps.maxImageExtent.height);
}
// Find a suitable swapchain image format
uint32_t surface_format_count;
VK_CHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(
m_physical_device, m_surface, &surface_format_count, nullptr));
std::vector<VkSurfaceFormatKHR> available_surface_formats(surface_format_count);
VK_CHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(
m_physical_device, m_surface, &surface_format_count, available_surface_formats.data()));
m_image_format = VK_FORMAT_UNDEFINED;
VkColorSpaceKHR image_color_space = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
for (VkFormat preferred_format : m_config.preferred_image_formats)
{
for (const VkSurfaceFormatKHR& surface_format : available_surface_formats)
{
if (surface_format.format == preferred_format)
{
m_image_format = surface_format.format;
image_color_space = surface_format.colorSpace;
}
}
}
if (m_image_format == VK_FORMAT_UNDEFINED)
{
m_image_format = available_surface_formats[0].format;
image_color_space = available_surface_formats[0].colorSpace;
std::cerr << "None of the preferred image formats {";
for (size_t i = 0; i < m_config.preferred_image_formats.size(); i++)
{
if (i > 0)
std::cerr << ", ";
std::cerr << vkformat_to_str(m_config.preferred_image_formats[i]);
}
std::cerr << "} are supported. Choosing the first supported format (";
std::cerr << vkformat_to_str(m_image_format) << ") instead.\n";
}
// Find a compatible alpha channel composite mode
VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
const VkCompositeAlphaFlagBitsKHR composite_alpha_flags[] = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
};
for (VkCompositeAlphaFlagBitsKHR flag : composite_alpha_flags)
{
if (surface_caps.supportedCompositeAlpha & flag)
{
composite_alpha = flag;
break;
}
}
// Find a present mode
uint32_t present_mode_count;
VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(
m_physical_device, m_surface, &present_mode_count, nullptr));
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(
m_physical_device, m_surface, &present_mode_count, present_modes.data()));
VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;// Support is guaranteed by spec
for (VkPresentModeKHR mode : present_modes)
{
if (mode == VK_PRESENT_MODE_MAILBOX_KHR)
{
present_mode = mode;
break;
}
}
// Requested image count might not be supported, so clamp it
m_image_count = std::clamp(
m_config.image_count, surface_caps.minImageCount, surface_caps.maxImageCount);
// Create swapchain
VkSwapchainCreateInfoKHR swapchain_create_info = {};
swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_create_info.surface = m_surface;
swapchain_create_info.minImageCount = m_image_count;
swapchain_create_info.imageFormat = m_image_format;
swapchain_create_info.imageColorSpace = image_color_space;
swapchain_create_info.imageExtent.width = m_image_width;
swapchain_create_info.imageExtent.height = m_image_height;
swapchain_create_info.imageArrayLayers = 1; // No stereo/multiview
swapchain_create_info.imageUsage
= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
const uint32_t queue_family_indices[] = {
m_graphics_queue_family_index,
m_present_queue_family_index
};
if (queue_family_indices[0] == queue_family_indices[1])
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
else
{
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_create_info.queueFamilyIndexCount = std::size(queue_family_indices);
swapchain_create_info.pQueueFamilyIndices = queue_family_indices;
}
swapchain_create_info.preTransform = surface_caps.currentTransform;
swapchain_create_info.compositeAlpha = composite_alpha;
swapchain_create_info.presentMode = present_mode;
swapchain_create_info.clipped = VK_TRUE;
VK_CHECK(vkCreateSwapchainKHR(m_device, &swapchain_create_info, nullptr, &m_swapchain));
VK_CHECK(vkGetSwapchainImagesKHR(m_device, m_swapchain, &m_image_count, nullptr));
m_swapchain_images.resize(m_image_count);
VK_CHECK(vkGetSwapchainImagesKHR(
m_device, m_swapchain, &m_image_count, m_swapchain_images.data()));
m_swapchain_image_views.resize(m_swapchain_images.size());
for (size_t i = 0; i < m_swapchain_images.size(); i++)
{
VkImageViewCreateInfo image_view_create_info = {};
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.image = m_swapchain_images[i];
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = m_image_format;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.layerCount = 1;
image_view_create_info.subresourceRange.levelCount = 1;
VK_CHECK(vkCreateImageView(
m_device, &image_view_create_info, nullptr, &m_swapchain_image_views[i]));
}
}
void Vulkan_example_app::init_swapchain_for_headless()
{
// Find suitable image format
VkFormatFeatureFlags required_features
= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT
| VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT;
m_image_format = find_supported_format(m_physical_device,
m_config.preferred_image_formats, VK_IMAGE_TILING_OPTIMAL, required_features);
if (m_image_format == VK_FORMAT_UNDEFINED)
{
const std::vector<VkFormat> default_formats = {
VK_FORMAT_R32G32B32A32_SFLOAT,
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_B8G8R8A8_UNORM
};
m_image_format = find_supported_format(m_physical_device,
default_formats, VK_IMAGE_TILING_OPTIMAL, required_features);
std::cerr << "None of the preferred image formats {";
for (size_t i = 0; i < m_config.preferred_image_formats.size(); i++)
{
if (i > 0)
std::cerr << ", ";
std::cerr << vkformat_to_str(m_config.preferred_image_formats[i]);
}
std::cerr << "} ";
if (m_image_format == VK_FORMAT_UNDEFINED)
{
std::cerr << " or default formats {";
for (size_t i = 0; i < default_formats.size(); i++)
{
if (i > 0)
std::cerr << ", ";
std::cerr << vkformat_to_str(default_formats[i]);
}
std::cerr << "} are supported.";
terminate();
}
std::cerr << " are supported. Choosing the first supported default format ("
<< vkformat_to_str(m_image_format) << ") instead.\n\n";
}
// Check if the image size is valid, if not clamp it
VkImageUsageFlags usage_flags
= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkImageFormatProperties image_format_props;
VK_CHECK(vkGetPhysicalDeviceImageFormatProperties(m_physical_device, m_image_format,
VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage_flags, 0, &image_format_props));
m_image_width = std::min(m_config.image_width, image_format_props.maxExtent.width);
m_image_height = std::min(m_config.image_height, image_format_props.maxExtent.height);
// No restrictions on how many images we can create
m_image_count = m_config.image_count;
// Create the swapchain image resources
m_swapchain_images.resize(m_image_count);
m_swapchain_device_memories.resize(m_image_count);
m_swapchain_image_views.resize(m_image_count);
for (uint32_t i = 0; i < m_image_count; i++)
{
VkImageCreateInfo image_create_info = {};
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = m_image_format;
image_create_info.extent = { m_image_width, m_image_height, 1 };
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = usage_flags;
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VK_CHECK(vkCreateImage(
m_device, &image_create_info, nullptr, &m_swapchain_images[i]));
m_swapchain_device_memories[i] = allocate_and_bind_image_memory(
m_device, m_physical_device, m_swapchain_images[i],
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VkImageViewCreateInfo image_view_create_info = {};
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.image = m_swapchain_images[i];
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = m_image_format;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.layerCount = 1;
image_view_create_info.subresourceRange.levelCount = 1;
VK_CHECK(vkCreateImageView(
m_device, &image_view_create_info, nullptr, &m_swapchain_image_views[i]));
}
}
void Vulkan_example_app::init_depth_stencil_buffer()
{
m_depth_stencil_format = find_supported_format(m_physical_device,
{ VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT },
VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
if (m_depth_stencil_format == VK_FORMAT_UNDEFINED)
{
std::cerr << "No supported depth stencil format found.\n";
terminate();
}
VkImageCreateInfo image_create_info = {};
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = m_depth_stencil_format;
image_create_info.extent = { m_image_width, m_image_height, 1 };
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VK_CHECK(vkCreateImage(m_device, &image_create_info, nullptr, &m_depth_stencil_image));
m_depth_stencil_device_memory = allocate_and_bind_image_memory(
m_device, m_physical_device, m_depth_stencil_image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VkImageViewCreateInfo image_view_create_info = {};
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.image = m_depth_stencil_image;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = m_depth_stencil_format;
if (has_stencil_component(m_depth_stencil_format))
{
image_view_create_info.subresourceRange.aspectMask
= VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
}
else
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
image_view_create_info.subresourceRange.layerCount = 1;
image_view_create_info.subresourceRange.levelCount = 1;
VK_CHECK(vkCreateImageView(
m_device, &image_view_create_info, nullptr, &m_depth_stencil_image_view));
}
void Vulkan_example_app::init_render_pass()
{
VkImageLayout final_layout = m_config.headless
? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
m_main_render_pass = create_simple_render_pass(
m_device, m_image_format, m_depth_stencil_format, final_layout);
}
void Vulkan_example_app::init_framebuffers()
{
m_framebuffers.resize(m_image_count);
for (size_t i = 0; i < m_image_count; i++)
{
const VkImageView attachments[] = {
m_swapchain_image_views[i],
m_depth_stencil_image_view
};
VkFramebufferCreateInfo framebuffer_create_info = {};
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_create_info.renderPass = m_main_render_pass;
framebuffer_create_info.attachmentCount = std::size(attachments);
framebuffer_create_info.pAttachments = attachments;
framebuffer_create_info.width = m_image_width;
framebuffer_create_info.height = m_image_height;
framebuffer_create_info.layers = 1;
VK_CHECK(vkCreateFramebuffer(
m_device, &framebuffer_create_info, nullptr, &m_framebuffers[i]));
}
}
void Vulkan_example_app::init_command_pool_and_buffers()
{
if (!m_command_pool)
{
// Create command pool
VkCommandPoolCreateInfo command_pool_create_info = {};
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
command_pool_create_info.queueFamilyIndex = m_graphics_queue_family_index;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
VK_CHECK(vkCreateCommandPool(
m_device, &command_pool_create_info, nullptr, &m_command_pool));
}
else
{
// If called by recreate_swapchain the pool doesn't need to be recreated,
// only the command buffer need to be freed
vkFreeCommandBuffers(m_device, m_command_pool,
static_cast<uint32_t>(m_command_buffers.size()), m_command_buffers.data());
m_command_buffers.clear();
}
// One command buffer per frame
m_command_buffers.resize(m_image_count, nullptr);
VkCommandBufferAllocateInfo command_buffer_aloc_info = {};
command_buffer_aloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_aloc_info.commandPool = m_command_pool;
command_buffer_aloc_info.commandBufferCount = m_image_count;
command_buffer_aloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
VK_CHECK(vkAllocateCommandBuffers(
m_device, &command_buffer_aloc_info, m_command_buffers.data()));
}
void Vulkan_example_app::init_synchronization_objects()
{
// If called in recreate_swapchain only the fences need to be created
if (m_image_available_semaphores.empty())
{
m_image_available_semaphores.resize(m_image_count);
m_render_finished_semaphores.resize(m_image_count);
VkSemaphoreCreateInfo semaphore_create_info = {};
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
for (size_t i = 0; i < m_image_count; i++)
{
VK_CHECK(vkCreateSemaphore(
m_device, &semaphore_create_info, nullptr, &m_image_available_semaphores[i]));
VK_CHECK(vkCreateSemaphore(
m_device, &semaphore_create_info, nullptr, &m_render_finished_semaphores[i]));
}
}
m_frame_inflight_fences.resize(m_image_count);
for (size_t i = 0; i < m_image_count; i++)
{
VkFenceCreateInfo fence_create_info = {};
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
VK_CHECK(vkCreateFence(
m_device, &fence_create_info, nullptr, &m_frame_inflight_fences[i]));
}
}
void Vulkan_example_app::recreate_swapchain_or_framebuffer_image()
{
if (!m_config.headless)
{
// Wait until the window is unminimized if necessary
int framebuffer_width = 0;
int framebuffer_height = 0;
glfwGetFramebufferSize(m_window, &framebuffer_width, &framebuffer_height);
while (framebuffer_width == 0 || framebuffer_height == 0)
{
glfwGetFramebufferSize(m_window, &framebuffer_width, &framebuffer_height);
glfwWaitEvents();
}
}
VK_CHECK(vkDeviceWaitIdle(m_device));
// Cleanup swapchain related resources
vkDestroyImageView(m_device, m_depth_stencil_image_view, nullptr);
vkDestroyImage(m_device, m_depth_stencil_image, nullptr);
vkFreeMemory(m_device, m_depth_stencil_device_memory, nullptr);
for (VkImageView image_view : m_swapchain_image_views)
vkDestroyImageView(m_device, image_view, nullptr);
if (m_config.headless)
{
for (VkImage image : m_swapchain_images)
vkDestroyImage(m_device, image, nullptr);
for (VkDeviceMemory device_memory : m_swapchain_device_memories)
vkFreeMemory(m_device, device_memory, nullptr);
}
else
vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
for (VkFramebuffer framebuffer : m_framebuffers)
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
vkDestroyRenderPass(m_device, m_main_render_pass, nullptr);
for (VkFence fence : m_frame_inflight_fences)
vkDestroyFence(m_device, fence, nullptr);
// Recreate swapchain related resources
if (m_config.headless)
init_swapchain_for_headless();
else
init_swapchain_for_window();
init_depth_stencil_buffer();
init_render_pass();
init_framebuffers();
init_command_pool_and_buffers();
init_synchronization_objects();
recreate_size_dependent_resources();
}
void Vulkan_example_app::render_loop_iteration(uint32_t frame_index, uint32_t image_index, double& last_frame_time)
{
// Measure elapsed seconds since last frame and update application logic
double current_frame_time = glfwGetTime();
float elapsed_time = static_cast<float>(current_frame_time - last_frame_time);
last_frame_time = current_frame_time;
update(elapsed_time, frame_index);
// Record command buffer
VkCommandBuffer command_buffer = m_command_buffers[frame_index];
VK_CHECK(vkResetCommandBuffer(command_buffer, 0));
VkCommandBufferBeginInfo begin_info = {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK_CHECK(vkBeginCommandBuffer(command_buffer, &begin_info));
// Let application fill the command buffer
render(command_buffer, frame_index, image_index);
VK_CHECK(vkEndCommandBuffer(command_buffer));
// Submit recorded command buffer to GPU
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
if (!m_config.headless)
{
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &m_image_available_semaphores[frame_index];
submit_info.pWaitDstStageMask = &wait_stage;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &m_render_finished_semaphores[frame_index];
}
VK_CHECK(vkQueueSubmit(
m_graphics_queue, 1, &submit_info, m_frame_inflight_fences[frame_index]));
}
void Vulkan_example_app::internal_key_callback(
GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (action == GLFW_PRESS && key == GLFW_KEY_ESCAPE)
glfwSetWindowShouldClose(window, GLFW_TRUE);
auto app = static_cast<Vulkan_example_app*>(glfwGetWindowUserPointer(window));
app->key_callback(key, action);
}
void Vulkan_example_app::internal_mouse_button_callback(
GLFWwindow* window, int button, int action, int mods)
{
auto app = static_cast<Vulkan_example_app*>(glfwGetWindowUserPointer(window));
app->mouse_button_callback(button, action);
}
void Vulkan_example_app::internal_mouse_move_callback(GLFWwindow* window, double pos_x, double pos_y)
{
auto app = static_cast<Vulkan_example_app*>(glfwGetWindowUserPointer(window));
app->mouse_move_callback(static_cast<float>(pos_x), static_cast<float>(pos_y));
}
void Vulkan_example_app::internal_mouse_scroll_callback(
GLFWwindow* window, double offset_x, double offset_y)
{
auto app = static_cast<Vulkan_example_app*>(glfwGetWindowUserPointer(window));
app->mouse_scroll_callback(static_cast<float>(offset_x), static_cast<float>(offset_y));
}
void Vulkan_example_app::internal_resize_callback(GLFWwindow* window, int width, int height)
{
auto app = static_cast<Vulkan_example_app*>(glfwGetWindowUserPointer(window));
if (app->m_config.headless)
app->recreate_swapchain_or_framebuffer_image();
app->resized_callback(static_cast<uint32_t>(width), static_cast<uint32_t>(height));
app->m_framebuffer_resized = true;
}
void Vulkan_example_app::glfw_error_callback(int error_code, const char* description)
{
std::cerr << "GLFW error (code: " << error_code << "): \"" << description << "\"\n";
}
VKAPI_ATTR VkBool32 VKAPI_PTR Vulkan_example_app::debug_messenger_callback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_types,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data)
{
std::cerr << callback_data->pMessage << "\n";
return VK_FALSE;
}
Staging_buffer::Staging_buffer(VkDevice device, VkPhysicalDevice physical_device,
VkDeviceSize size, VkBufferUsageFlags usage)
: m_device(device)
{
VkBufferCreateInfo buffer_create_info = {};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.size = size;
buffer_create_info.usage = usage;
VK_CHECK(vkCreateBuffer(m_device, &buffer_create_info, nullptr, &m_buffer));
m_device_memory = mi::examples::vk::allocate_and_bind_buffer_memory(
m_device, physical_device, m_buffer,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
Staging_buffer::~Staging_buffer()
{
vkDestroyBuffer(m_device, m_buffer, nullptr);
vkFreeMemory(m_device, m_device_memory, nullptr);
}
void* Staging_buffer::map_memory() const
{
void* mapped_data;
VK_CHECK(vkMapMemory(m_device, m_device_memory, 0, VK_WHOLE_SIZE, 0, &mapped_data));
return mapped_data;
}
void Staging_buffer::unmap_memory() const
{
vkUnmapMemory(m_device, m_device_memory);
}
Temporary_command_buffer::Temporary_command_buffer(VkDevice device, VkCommandPool command_pool)
: m_device(device)
, m_command_pool(command_pool)
{
VkCommandBufferAllocateInfo command_buffer_alloc_info= {};
command_buffer_alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_alloc_info.commandPool = m_command_pool;
command_buffer_alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
command_buffer_alloc_info.commandBufferCount = 1;
VK_CHECK(vkAllocateCommandBuffers(
m_device, &command_buffer_alloc_info, &m_command_buffer));
}
Temporary_command_buffer::~Temporary_command_buffer()
{
vkFreeCommandBuffers(m_device, m_command_pool, 1, &m_command_buffer);
}
void Temporary_command_buffer::begin()
{
VkCommandBufferBeginInfo command_buffer_begin_info = {};
command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK_CHECK(vkBeginCommandBuffer(m_command_buffer, &command_buffer_begin_info));
}
void Temporary_command_buffer::end_and_submit(VkQueue queue, bool wait)
{
VK_CHECK(vkEndCommandBuffer(m_command_buffer));
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &m_command_buffer;
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, nullptr));
if (wait)
VK_CHECK(vkQueueWaitIdle(queue));
}
bool load_debug_utils_extension(VkInstance instance)
{
CreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"));
DestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"));
return CreateDebugUtilsMessengerEXT && DestroyDebugUtilsMessengerEXT;
}
bool check_instance_extensions_support(
const std::vector<const char*>& requested_extensions)
{
uint32_t extension_count;
VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr));
std::vector<VkExtensionProperties> available_extensions(extension_count);
VK_CHECK(vkEnumerateInstanceExtensionProperties(
nullptr, &extension_count, available_extensions.data()));
for (const char* requested_extension : requested_extensions)
{
bool found = false;
for (const VkExtensionProperties& available_extension : available_extensions)
{
if (std::strcmp(requested_extension, available_extension.extensionName) == 0)
{
found = true;
break;
}
}
if (!found)
{
return false;
}
}
return true;
}
bool check_device_extensions_support(VkPhysicalDevice device,
const std::vector<const char*>& requested_extensions)
{
uint32_t extension_count;
VK_CHECK(vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, nullptr));
std::vector<VkExtensionProperties> available_extensions(extension_count);
VK_CHECK(vkEnumerateDeviceExtensionProperties(
device, nullptr, &extension_count, available_extensions.data()));
for (const char* requested_extension : requested_extensions)
{
bool found = false;
for (const VkExtensionProperties& available_extension : available_extensions)
{
if (std::strcmp(requested_extension, available_extension.extensionName) == 0)
{
found = true;
break;
}
}
if (!found)
{
return false;
}
}
return true;
}
bool check_validation_layers_support(
const std::vector<const char*>& requested_layers)
{
uint32_t layer_count;
VK_CHECK(vkEnumerateInstanceLayerProperties(&layer_count, nullptr));
std::vector<VkLayerProperties> available_layers(layer_count);
VK_CHECK(vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data()));
for (const char* requested_layer : requested_layers)
{
bool found = false;
for (const VkLayerProperties& available_layer : available_layers)
{
if (std::strcmp(requested_layer, available_layer.layerName) == 0)
{
found = true;
break;
}
}
if (!found)
{
return false;
}
}
return true;
}
VkShaderModule create_shader_module_from_file(
VkDevice device, const char* shader_filename, EShLanguage shader_type,
const std::vector<std::string>& defines)
{
std::string shader_source = mi::examples::io::read_text_file(
mi::examples::io::get_executable_folder() + "/" + shader_filename);
return create_shader_module_from_sources(device, { shader_source }, shader_type, defines);
}
VkShaderModule create_shader_module_from_sources(
VkDevice device, const std::vector<std::string_view> shader_sources,
EShLanguage shader_type, const std::vector<std::string>& defines)
{
mi::examples::vk::Glsl_compiler glsl_compiler(shader_type, "main");
glsl_compiler.add_defines(defines);
for (const std::string_view& source : shader_sources)
glsl_compiler.add_shader(source);
std::vector<unsigned int> compiled_shader = glsl_compiler.link_program();
VkShaderModuleCreateInfo shader_module_create_info = {};
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_module_create_info.pCode = compiled_shader.data();
shader_module_create_info.codeSize = compiled_shader.size() * sizeof(unsigned int);
VkShaderModule shader_module;
VK_CHECK(vkCreateShaderModule(device,
&shader_module_create_info, nullptr, &shader_module));
return shader_module;
}
// NOTE: A more sophisticated should be used in a real application.
// This example simply allocates dedicated memory for each resource.
// For best practices on Vulkan memory management see:
// https://developer.nvidia.com/blog/vulkan-dos-donts/
// https://developer.nvidia.com/vulkan-memory-management
// https://developer.nvidia.com/what%E2%80%99s-your-vulkan-memory-type
VkDeviceMemory allocate_and_bind_buffer_memory(
VkDevice device, VkPhysicalDevice physical_device, VkBuffer buffer,
VkMemoryPropertyFlags memory_property_flags)
{
VkMemoryRequirements memory_requirements;
vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
VkMemoryAllocateInfo memory_alloc_info = {};
memory_alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_alloc_info.allocationSize = memory_requirements.size;
memory_alloc_info.memoryTypeIndex = mi::examples::vk::find_memory_type(physical_device,
memory_requirements.memoryTypeBits, memory_property_flags);
VkDeviceMemory device_memory;
VK_CHECK(vkAllocateMemory(device, &memory_alloc_info, nullptr, &device_memory));
VK_CHECK(vkBindBufferMemory(device, buffer, device_memory, 0));
return device_memory;
}
// NOTE: A more sophisticated should be used in a real application.
// This example simply allocates dedicated memory for each resource.
// For best practices on Vulkan memory management see:
// https://developer.nvidia.com/blog/vulkan-dos-donts/
// https://developer.nvidia.com/vulkan-memory-management
// https://developer.nvidia.com/what%E2%80%99s-your-vulkan-memory-type
VkDeviceMemory allocate_and_bind_image_memory(
VkDevice device, VkPhysicalDevice physical_device, VkImage image,
VkMemoryPropertyFlags memory_property_flags)
{
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(device, image, &memory_requirements);
VkMemoryAllocateInfo memory_alloc_info = {};
memory_alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_alloc_info.allocationSize = memory_requirements.size;
memory_alloc_info.memoryTypeIndex = mi::examples::vk::find_memory_type(physical_device,
memory_requirements.memoryTypeBits, memory_property_flags);
VkDeviceMemory device_memory;
VK_CHECK(vkAllocateMemory(device, &memory_alloc_info, nullptr, &device_memory));
VK_CHECK(vkBindImageMemory(device, image, device_memory, 0));
return device_memory;
}
uint32_t find_memory_type(
VkPhysicalDevice physical_device,
uint32_t memory_type_bits_requirement,
VkMemoryPropertyFlags required_properties)
{
VkPhysicalDeviceMemoryProperties device_memory_props;
vkGetPhysicalDeviceMemoryProperties(physical_device, &device_memory_props);
const uint32_t memory_count = device_memory_props.memoryTypeCount;
for (uint32_t memory_index = 0; memory_index < memory_count; memory_index++)
{
uint32_t memory_type_bits = (1 << memory_index);
bool is_required_memory_type = (memory_type_bits_requirement & memory_type_bits);
VkMemoryPropertyFlags property_flags
= device_memory_props.memoryTypes[memory_index].propertyFlags;
bool has_required_properties
= (property_flags & required_properties) == required_properties;
if (is_required_memory_type && has_required_properties)
return memory_index;
}
return 0xffffffff;
}
VkFormat find_supported_format(
VkPhysicalDevice physical_device,
const std::vector<VkFormat>& formats,
VkImageTiling tiling,
VkFormatFeatureFlags feature_flags)
{
for (VkFormat format : formats)
{
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties(physical_device, format, &format_properties);
if (tiling == VK_IMAGE_TILING_LINEAR
&& (format_properties.linearTilingFeatures & feature_flags) == feature_flags)
{
return format;
}
else if (tiling == VK_IMAGE_TILING_OPTIMAL
&& (format_properties.optimalTilingFeatures & feature_flags) == feature_flags)
{
return format;
}
}
return VK_FORMAT_UNDEFINED;
}
bool has_stencil_component(VkFormat format)
{
return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
}
uint32_t get_image_format_bpp(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R8_UNORM:
case VK_FORMAT_R8_SNORM:
case VK_FORMAT_R8_USCALED:
case VK_FORMAT_R8_SSCALED:
case VK_FORMAT_R8_UINT:
case VK_FORMAT_R8_SINT:
case VK_FORMAT_R8_SRGB:
case VK_FORMAT_S8_UINT:
return 1;
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
case VK_FORMAT_R5G6B5_UNORM_PACK16:
case VK_FORMAT_B5G6R5_UNORM_PACK16:
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
case VK_FORMAT_R8G8_UNORM:
case VK_FORMAT_R8G8_SNORM:
case VK_FORMAT_R8G8_USCALED:
case VK_FORMAT_R8G8_SSCALED:
case VK_FORMAT_R8G8_UINT:
case VK_FORMAT_R8G8_SINT:
case VK_FORMAT_R8G8_SRGB:
case VK_FORMAT_R16_UNORM:
case VK_FORMAT_R16_SNORM:
case VK_FORMAT_R16_USCALED:
case VK_FORMAT_R16_SSCALED:
case VK_FORMAT_R16_UINT:
case VK_FORMAT_R16_SINT:
case VK_FORMAT_R16_SFLOAT:
case VK_FORMAT_D16_UNORM:
return 2;
case VK_FORMAT_R8G8B8_UNORM:
case VK_FORMAT_R8G8B8_SNORM:
case VK_FORMAT_R8G8B8_USCALED:
case VK_FORMAT_R8G8B8_SSCALED:
case VK_FORMAT_R8G8B8_UINT:
case VK_FORMAT_R8G8B8_SINT:
case VK_FORMAT_R8G8B8_SRGB:
case VK_FORMAT_B8G8R8_UNORM:
case VK_FORMAT_B8G8R8_SNORM:
case VK_FORMAT_B8G8R8_USCALED:
case VK_FORMAT_B8G8R8_SSCALED:
case VK_FORMAT_B8G8R8_UINT:
case VK_FORMAT_B8G8R8_SINT:
case VK_FORMAT_B8G8R8_SRGB:
case VK_FORMAT_D16_UNORM_S8_UINT:
return 3;
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SNORM:
case VK_FORMAT_R8G8B8A8_USCALED:
case VK_FORMAT_R8G8B8A8_SSCALED:
case VK_FORMAT_R8G8B8A8_UINT:
case VK_FORMAT_R8G8B8A8_SINT:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SNORM:
case VK_FORMAT_B8G8R8A8_USCALED:
case VK_FORMAT_B8G8R8A8_SSCALED:
case VK_FORMAT_B8G8R8A8_UINT:
case VK_FORMAT_B8G8R8A8_SINT:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
case VK_FORMAT_A2R10G10B10_UINT_PACK32:
case VK_FORMAT_A2R10G10B10_SINT_PACK32:
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
case VK_FORMAT_R16G16_UNORM:
case VK_FORMAT_R16G16_SNORM:
case VK_FORMAT_R16G16_USCALED:
case VK_FORMAT_R16G16_SSCALED:
case VK_FORMAT_R16G16_UINT:
case VK_FORMAT_R16G16_SINT:
case VK_FORMAT_R16G16_SFLOAT:
case VK_FORMAT_R32_UINT:
case VK_FORMAT_R32_SINT:
case VK_FORMAT_R32_SFLOAT:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_X8_D24_UNORM_PACK32:
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
case VK_FORMAT_G8B8G8R8_422_UNORM:
case VK_FORMAT_B8G8R8G8_422_UNORM:
return 4;
case VK_FORMAT_R16G16B16_UNORM:
case VK_FORMAT_R16G16B16_SNORM:
case VK_FORMAT_R16G16B16_USCALED:
case VK_FORMAT_R16G16B16_SSCALED:
case VK_FORMAT_R16G16B16_UINT:
case VK_FORMAT_R16G16B16_SINT:
case VK_FORMAT_R16G16B16_SFLOAT:
return 6;
case VK_FORMAT_R16G16B16A16_UNORM:
case VK_FORMAT_R16G16B16A16_SNORM:
case VK_FORMAT_R16G16B16A16_USCALED:
case VK_FORMAT_R16G16B16A16_SSCALED:
case VK_FORMAT_R16G16B16A16_UINT:
case VK_FORMAT_R16G16B16A16_SINT:
case VK_FORMAT_R16G16B16A16_SFLOAT:
case VK_FORMAT_R32G32_UINT:
case VK_FORMAT_R32G32_SINT:
case VK_FORMAT_R32G32_SFLOAT:
case VK_FORMAT_R64_UINT:
case VK_FORMAT_R64_SINT:
case VK_FORMAT_R64_SFLOAT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return 8;
case VK_FORMAT_R32G32B32_UINT:
case VK_FORMAT_R32G32B32_SINT:
case VK_FORMAT_R32G32B32_SFLOAT:
return 12;
case VK_FORMAT_R32G32B32A32_UINT:
case VK_FORMAT_R32G32B32A32_SINT:
case VK_FORMAT_R32G32B32A32_SFLOAT:
case VK_FORMAT_R64G64_UINT:
case VK_FORMAT_R64G64_SINT:
case VK_FORMAT_R64G64_SFLOAT:
return 16;
case VK_FORMAT_R64G64B64_UINT:
case VK_FORMAT_R64G64B64_SINT:
case VK_FORMAT_R64G64B64_SFLOAT:
return 24;
case VK_FORMAT_R64G64B64A64_UINT:
case VK_FORMAT_R64G64B64A64_SINT:
case VK_FORMAT_R64G64B64A64_SFLOAT:
return 32;
default:
return 0;
}
}
VkRenderPass create_simple_color_only_render_pass(
VkDevice device, VkFormat image_format, VkImageLayout final_layout)
{
VkAttachmentDescription attachment_desc = {};
attachment_desc.format = image_format;
attachment_desc.samples = VK_SAMPLE_COUNT_1_BIT;
attachment_desc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachment_desc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment_desc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment_desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachment_desc.finalLayout = final_layout;
VkAttachmentReference attachment_reference = {};
attachment_reference.attachment = 0;
attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &attachment_reference;
VkSubpassDependency subpass_dependency = {};
subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
subpass_dependency.dstSubpass = 0;
subpass_dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpass_dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpass_dependency.srcAccessMask = 0;
subpass_dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo render_pass_create_info = {};
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_create_info.attachmentCount = 1;
render_pass_create_info.pAttachments = &attachment_desc;
render_pass_create_info.subpassCount = 1;
render_pass_create_info.pSubpasses = &subpass;
render_pass_create_info.dependencyCount = 1;
render_pass_create_info.pDependencies = &subpass_dependency;
VkRenderPass render_pass;
VK_CHECK(vkCreateRenderPass(device, &render_pass_create_info, nullptr, &render_pass));
return render_pass;
}
VkRenderPass create_simple_render_pass(
VkDevice device, VkFormat image_format,
VkFormat depth_stencil_format, VkImageLayout final_layout)
{
VkAttachmentDescription color_attachment = {};
color_attachment.format = image_format;
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
color_attachment.finalLayout = final_layout;
VkAttachmentDescription depth_attachment = {};
depth_attachment.format = depth_stencil_format;
depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference color_attachment_reference = {};
color_attachment_reference.attachment = 0;
color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depth_attachment_reference = {};
depth_attachment_reference.attachment = 1;
depth_attachment_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_attachment_reference;
subpass.pDepthStencilAttachment = &depth_attachment_reference;
VkSubpassDependency subpass_dependency = {};
subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
subpass_dependency.dstSubpass = 0;
subpass_dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
| VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
subpass_dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
| VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
subpass_dependency.srcAccessMask = 0;
subpass_dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
const VkAttachmentDescription attachments[]
= { color_attachment, depth_attachment };
VkRenderPassCreateInfo render_pass_create_info = {};
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_create_info.attachmentCount = std::size(attachments);
render_pass_create_info.pAttachments = attachments;
render_pass_create_info.subpassCount = 1;
render_pass_create_info.pSubpasses = &subpass;
render_pass_create_info.dependencyCount = 1;
render_pass_create_info.pDependencies = &subpass_dependency;
VkRenderPass render_pass;
VK_CHECK(vkCreateRenderPass(device, &render_pass_create_info, nullptr, &render_pass));
return render_pass;
}
VkSampler create_linear_sampler(VkDevice device)
{
VkSamplerCreateInfo sampler_create_info = {};
sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sampler_create_info.magFilter = VK_FILTER_LINEAR;
sampler_create_info.minFilter = VK_FILTER_LINEAR;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_create_info.unnormalizedCoordinates = false;
VkSampler sampler;
VK_CHECK(vkCreateSampler(device, &sampler_create_info, nullptr, &sampler));
return sampler;
}
std::vector<uint8_t> copy_image_to_buffer(
VkDevice device, VkPhysicalDevice physical_device, VkCommandPool command_pool, VkQueue queue,
VkImage image, uint32_t image_width, uint32_t image_height, uint32_t image_bpp,
VkImageLayout image_layout, bool flip)
{
size_t staging_buffer_size = image_width * image_height * image_bpp;
Staging_buffer staging_buffer(device, physical_device,
staging_buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
Temporary_command_buffer command_buffer(device, command_pool);
command_buffer.begin();
// Determine access and stage mask for image based on current layout
VkAccessFlags from_access_mask;
VkPipelineStageFlags from_stage_mask;
switch (image_layout)
{
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
from_access_mask = VK_ACCESS_SHADER_READ_BIT;
from_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
break;
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
from_access_mask = VK_ACCESS_MEMORY_READ_BIT;
from_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
from_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
from_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
from_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
from_stage_mask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
break;
default:
// TODO: add more specific cases as needed.
from_access_mask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
from_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
}
VkImageAspectFlags aspect_mask;
if (image_layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL)
aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT;
else if (image_layout == VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL)
aspect_mask = VK_IMAGE_ASPECT_STENCIL_BIT;
else if (image_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
else
aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
// Transition image layout to transfer src
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = from_access_mask;
image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
image_memory_barrier.oldLayout = image_layout;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image;
image_memory_barrier.subresourceRange.aspectMask = aspect_mask;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(command_buffer.get(),
from_stage_mask,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
// Copy image to buffer
VkBufferImageCopy copy_region = {};
copy_region.bufferOffset = 0;
copy_region.bufferRowLength = 0;
copy_region.bufferImageHeight = 0;
copy_region.imageSubresource.aspectMask = aspect_mask;
copy_region.imageSubresource.mipLevel = 0;
copy_region.imageSubresource.baseArrayLayer = 0;
copy_region.imageSubresource.layerCount = 1;
copy_region.imageOffset = { 0, 0, 0 };
copy_region.imageExtent = { image_width, image_height, 1 };
vkCmdCopyImageToBuffer(command_buffer.get(),
image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
staging_buffer.get(),
1, &copy_region);
// Transition image layout back to present
image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
image_memory_barrier.dstAccessMask = from_access_mask;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
image_memory_barrier.newLayout = image_layout;
vkCmdPipelineBarrier(command_buffer.get(),
VK_PIPELINE_STAGE_TRANSFER_BIT,
from_stage_mask,
0,
0, nullptr,
0, nullptr,
1, &image_memory_barrier);
command_buffer.end_and_submit(queue);
// Fill buffer
std::vector<uint8_t> buffer(staging_buffer_size);
void* mapped_data = staging_buffer.map_memory();
if (flip)
{
for (uint32_t y = 0; y < image_height; y++)
{
// Copy rows in reverse order
size_t src_offset = ((image_height - y - 1) * image_width) * image_bpp;
size_t dst_offset = (y * image_width) * image_bpp;
uint8_t* src_pixel_ptr = &static_cast<uint8_t*>(mapped_data)[src_offset];
uint8_t* dst_pixel_ptr = &buffer[dst_offset];
std::memcpy(dst_pixel_ptr, src_pixel_ptr, image_width * image_bpp);
}
}
else
std::memcpy(buffer.data(), mapped_data, staging_buffer_size);
staging_buffer.unmap_memory();
return buffer;
}
const char* vkresult_to_str(VkResult result)
{
switch (result)
{
case VK_SUCCESS:
return ";VK_SUCCESS";
case VK_NOT_READY:
return ";VK_NOT_READY";
case VK_TIMEOUT:
return ";VK_TIMEOUT";
case VK_EVENT_SET:
return ";VK_EVENT_SET";
case VK_EVENT_RESET:
return ";VK_EVENT_RESET";
case VK_INCOMPLETE:
return ";VK_INCOMPLETE";
case VK_ERROR_OUT_OF_HOST_MEMORY:
return ";VK_ERROR_OUT_OF_HOST_MEMORY";
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
return ";VK_ERROR_OUT_OF_DEVICE_MEMORY";
case VK_ERROR_INITIALIZATION_FAILED:
return ";VK_ERROR_INITIALIZATION_FAILED";
case VK_ERROR_DEVICE_LOST:
return ";VK_ERROR_DEVICE_LOST";
case VK_ERROR_MEMORY_MAP_FAILED:
return ";VK_ERROR_MEMORY_MAP_FAILED";
case VK_ERROR_LAYER_NOT_PRESENT:
return ";VK_ERROR_LAYER_NOT_PRESENT";
case VK_ERROR_EXTENSION_NOT_PRESENT:
return ";VK_ERROR_EXTENSION_NOT_PRESENT";
case VK_ERROR_FEATURE_NOT_PRESENT:
return ";VK_ERROR_FEATURE_NOT_PRESENT";
case VK_ERROR_INCOMPATIBLE_DRIVER:
return ";VK_ERROR_INCOMPATIBLE_DRIVER";
case VK_ERROR_TOO_MANY_OBJECTS:
return ";VK_ERROR_TOO_MANY_OBJECTS";
case VK_ERROR_FORMAT_NOT_SUPPORTED:
return ";VK_ERROR_FORMAT_NOT_SUPPORTED";
case VK_ERROR_FRAGMENTED_POOL:
return ";VK_ERROR_FRAGMENTED_POOL";
case VK_ERROR_OUT_OF_POOL_MEMORY:
return ";VK_ERROR_OUT_OF_POOL_MEMORY";
case VK_ERROR_INVALID_EXTERNAL_HANDLE:
return ";VK_ERROR_INVALID_EXTERNAL_HANDLE";
case VK_ERROR_FRAGMENTATION:
return ";VK_ERROR_FRAGMENTATION";
case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS:
return ";VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS";
case VK_ERROR_SURFACE_LOST_KHR:
return ";VK_ERROR_SURFACE_LOST_KHR";
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
return ";VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
case VK_SUBOPTIMAL_KHR:
return ";VK_SUBOPTIMAL_KHR";
case VK_ERROR_OUT_OF_DATE_KHR:
return ";VK_ERROR_OUT_OF_DATE_KHR";
case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
return ";VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
case VK_ERROR_VALIDATION_FAILED_EXT:
return ";VK_ERROR_VALIDATION_FAILED_EXT";
case VK_ERROR_INVALID_SHADER_NV:
return ";VK_ERROR_INVALID_SHADER_NV";
case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
return ";VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
case VK_ERROR_NOT_PERMITTED_EXT:
return ";VK_ERROR_NOT_PERMITTED_EXT";
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
return ";VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
case VK_THREAD_IDLE_KHR:
return ";VK_THREAD_IDLE_KHR";
case VK_THREAD_DONE_KHR:
return ";VK_THREAD_DONE_KHR";
case VK_OPERATION_DEFERRED_KHR:
return ";VK_OPERATION_DEFERRED_KHR";
case VK_OPERATION_NOT_DEFERRED_KHR:
return ";VK_OPERATION_NOT_DEFERRED_KHR";
case VK_PIPELINE_COMPILE_REQUIRED_EXT:
return ";VK_PIPELINE_COMPILE_REQUIRED_EXT";
case VK_ERROR_UNKNOWN:
default:
return ";VK_ERROR_UNKNOWN";
}
}
// Convert some of the common formats to string.
const char* vkformat_to_str(VkFormat format)
{
switch (format)
{
case VK_FORMAT_UNDEFINED:
return ";VK_FORMAT_UNDEFINED";
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
return ";VK_FORMAT_R4G4B4A4_UNORM_PACK16";
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
return ";VK_FORMAT_B4G4R4A4_UNORM_PACK16";
case VK_FORMAT_R5G6B5_UNORM_PACK16:
return ";VK_FORMAT_R5G6B5_UNORM_PACK16";
case VK_FORMAT_B5G6R5_UNORM_PACK16:
return ";VK_FORMAT_B5G6R5_UNORM_PACK16";
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
return ";VK_FORMAT_R5G5B5A1_UNORM_PACK16";
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
return ";VK_FORMAT_B5G5R5A1_UNORM_PACK16";
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
return ";VK_FORMAT_A1R5G5B5_UNORM_PACK16";
case VK_FORMAT_R8_UNORM:
return ";VK_FORMAT_R8_UNORM";
case VK_FORMAT_R8_SNORM:
return ";VK_FORMAT_R8_SNORM";
case VK_FORMAT_R8_USCALED:
return ";VK_FORMAT_R8_USCALED";
case VK_FORMAT_R8_SSCALED:
return ";VK_FORMAT_R8_SSCALED";
case VK_FORMAT_R8_UINT:
return ";VK_FORMAT_R8_UINT";
case VK_FORMAT_R8_SINT:
return ";VK_FORMAT_R8_SINT";
case VK_FORMAT_R8_SRGB:
return ";VK_FORMAT_R8_SRGB";
case VK_FORMAT_R8G8_UNORM:
return ";VK_FORMAT_R8G8_UNORM";
case VK_FORMAT_R8G8_SNORM:
return ";VK_FORMAT_R8G8_SNORM";
case VK_FORMAT_R8G8_USCALED:
return ";VK_FORMAT_R8G8_USCALED";
case VK_FORMAT_R8G8_SSCALED:
return ";VK_FORMAT_R8G8_SSCALED";
case VK_FORMAT_R8G8_UINT:
return ";VK_FORMAT_R8G8_UINT";
case VK_FORMAT_R8G8_SINT:
return ";VK_FORMAT_R8G8_SINT";
case VK_FORMAT_R8G8_SRGB:
return ";VK_FORMAT_R8G8_SRGB";
case VK_FORMAT_R8G8B8_UNORM:
return ";VK_FORMAT_R8G8B8_UNORM";
case VK_FORMAT_R8G8B8_SNORM:
return ";VK_FORMAT_R8G8B8_SNORM";
case VK_FORMAT_R8G8B8_USCALED:
return ";VK_FORMAT_R8G8B8_USCALED";
case VK_FORMAT_R8G8B8_SSCALED:
return ";VK_FORMAT_R8G8B8_SSCALED";
case VK_FORMAT_R8G8B8_UINT:
return ";VK_FORMAT_R8G8B8_UINT";
case VK_FORMAT_R8G8B8_SINT:
return ";VK_FORMAT_R8G8B8_SINT";
case VK_FORMAT_R8G8B8_SRGB:
return ";VK_FORMAT_R8G8B8_SRGB";
case VK_FORMAT_B8G8R8_UNORM:
return ";VK_FORMAT_B8G8R8_UNORM";
case VK_FORMAT_B8G8R8_SNORM:
return ";VK_FORMAT_B8G8R8_SNORM";
case VK_FORMAT_B8G8R8_USCALED:
return ";VK_FORMAT_B8G8R8_USCALED";
case VK_FORMAT_B8G8R8_SSCALED:
return ";VK_FORMAT_B8G8R8_SSCALED";
case VK_FORMAT_B8G8R8_UINT:
return ";VK_FORMAT_B8G8R8_UINT";
case VK_FORMAT_B8G8R8_SINT:
return ";VK_FORMAT_B8G8R8_SINT";
case VK_FORMAT_B8G8R8_SRGB:
return ";VK_FORMAT_B8G8R8_SRGB";
case VK_FORMAT_R8G8B8A8_UNORM:
return ";VK_FORMAT_R8G8B8A8_UNORM";
case VK_FORMAT_R8G8B8A8_SNORM:
return ";VK_FORMAT_R8G8B8A8_SNORM";
case VK_FORMAT_R8G8B8A8_USCALED:
return ";VK_FORMAT_R8G8B8A8_USCALED";
case VK_FORMAT_R8G8B8A8_SSCALED:
return ";VK_FORMAT_R8G8B8A8_SSCALED";
case VK_FORMAT_R8G8B8A8_UINT:
return ";VK_FORMAT_R8G8B8A8_UINT";
case VK_FORMAT_R8G8B8A8_SINT:
return ";VK_FORMAT_R8G8B8A8_SINT";
case VK_FORMAT_R8G8B8A8_SRGB:
return ";VK_FORMAT_R8G8B8A8_SRGB";
case VK_FORMAT_B8G8R8A8_UNORM:
return ";VK_FORMAT_B8G8R8A8_UNORM";
case VK_FORMAT_B8G8R8A8_SNORM:
return ";VK_FORMAT_B8G8R8A8_SNORM";
case VK_FORMAT_B8G8R8A8_USCALED:
return ";VK_FORMAT_B8G8R8A8_USCALED";
case VK_FORMAT_B8G8R8A8_SSCALED:
return ";VK_FORMAT_B8G8R8A8_SSCALED";
case VK_FORMAT_B8G8R8A8_UINT:
return ";VK_FORMAT_B8G8R8A8_UINT";
case VK_FORMAT_B8G8R8A8_SINT:
return ";VK_FORMAT_B8G8R8A8_SINT";
case VK_FORMAT_B8G8R8A8_SRGB:
return ";VK_FORMAT_B8G8R8A8_SRGB";
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
return ";VK_FORMAT_A8B8G8R8_UNORM_PACK32";
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
return ";VK_FORMAT_A8B8G8R8_SNORM_PACK32";
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
return ";VK_FORMAT_A8B8G8R8_USCALED_PACK32";
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
return ";VK_FORMAT_A8B8G8R8_SSCALED_PACK32";
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
return ";VK_FORMAT_A8B8G8R8_UINT_PACK32";
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
return ";VK_FORMAT_A8B8G8R8_SINT_PACK32";
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
return ";VK_FORMAT_A8B8G8R8_SRGB_PACK32";
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
return ";VK_FORMAT_A2R10G10B10_UNORM_PACK32";
case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
return ";VK_FORMAT_A2R10G10B10_SNORM_PACK32";
case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
return ";VK_FORMAT_A2R10G10B10_USCALED_PACK32";
case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
return ";VK_FORMAT_A2R10G10B10_SSCALED_PACK32";
case VK_FORMAT_A2R10G10B10_UINT_PACK32:
return ";VK_FORMAT_A2R10G10B10_UINT_PACK32";
case VK_FORMAT_A2R10G10B10_SINT_PACK32:
return ";VK_FORMAT_A2R10G10B10_SINT_PACK32";
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
return ";VK_FORMAT_A2B10G10R10_UNORM_PACK32";
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
return ";VK_FORMAT_A2B10G10R10_SNORM_PACK32";
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
return ";VK_FORMAT_A2B10G10R10_USCALED_PACK32";
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
return ";VK_FORMAT_A2B10G10R10_SSCALED_PACK32";
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
return ";VK_FORMAT_A2B10G10R10_UINT_PACK32";
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
return ";VK_FORMAT_A2B10G10R10_SINT_PACK32";
case VK_FORMAT_R16_UNORM:
return ";VK_FORMAT_R16_UNORM";
case VK_FORMAT_R16_SNORM:
return ";VK_FORMAT_R16_SNORM";
case VK_FORMAT_R16_USCALED:
return ";VK_FORMAT_R16_USCALED";
case VK_FORMAT_R16_SSCALED:
return ";VK_FORMAT_R16_SSCALED";
case VK_FORMAT_R16_UINT:
return ";VK_FORMAT_R16_UINT";
case VK_FORMAT_R16_SINT:
return ";VK_FORMAT_R16_SINT";
case VK_FORMAT_R16_SFLOAT:
return ";VK_FORMAT_R16_SFLOAT";
case VK_FORMAT_R16G16_UNORM:
return ";VK_FORMAT_R16G16_UNORM";
case VK_FORMAT_R16G16_SNORM:
return ";VK_FORMAT_R16G16_SNORM";
case VK_FORMAT_R16G16_USCALED:
return ";VK_FORMAT_R16G16_USCALED";
case VK_FORMAT_R16G16_SSCALED:
return ";VK_FORMAT_R16G16_SSCALED";
case VK_FORMAT_R16G16_UINT:
return ";VK_FORMAT_R16G16_UINT";
case VK_FORMAT_R16G16_SINT:
return ";VK_FORMAT_R16G16_SINT";
case VK_FORMAT_R16G16_SFLOAT:
return ";VK_FORMAT_R16G16_SFLOAT";
case VK_FORMAT_R16G16B16_UNORM:
return ";VK_FORMAT_R16G16B16_UNORM";
case VK_FORMAT_R16G16B16_SNORM:
return ";VK_FORMAT_R16G16B16_SNORM";
case VK_FORMAT_R16G16B16_USCALED:
return ";VK_FORMAT_R16G16B16_USCALED";
case VK_FORMAT_R16G16B16_SSCALED:
return ";VK_FORMAT_R16G16B16_SSCALED";
case VK_FORMAT_R16G16B16_UINT:
return ";VK_FORMAT_R16G16B16_UINT";
case VK_FORMAT_R16G16B16_SINT:
return ";VK_FORMAT_R16G16B16_SINT";
case VK_FORMAT_R16G16B16_SFLOAT:
return ";VK_FORMAT_R16G16B16_SFLOAT";
case VK_FORMAT_R16G16B16A16_UNORM:
return ";VK_FORMAT_R16G16B16A16_UNORM";
case VK_FORMAT_R16G16B16A16_SNORM:
return ";VK_FORMAT_R16G16B16A16_SNORM";
case VK_FORMAT_R16G16B16A16_USCALED:
return ";VK_FORMAT_R16G16B16A16_USCALED";
case VK_FORMAT_R16G16B16A16_SSCALED:
return ";VK_FORMAT_R16G16B16A16_SSCALED";
case VK_FORMAT_R16G16B16A16_UINT:
return ";VK_FORMAT_R16G16B16A16_UINT";
case VK_FORMAT_R16G16B16A16_SINT:
return ";VK_FORMAT_R16G16B16A16_SINT";
case VK_FORMAT_R16G16B16A16_SFLOAT:
return ";VK_FORMAT_R16G16B16A16_SFLOAT";
case VK_FORMAT_R32_UINT:
return ";VK_FORMAT_R32_UINT";
case VK_FORMAT_R32_SINT:
return ";VK_FORMAT_R32_SINT";
case VK_FORMAT_R32_SFLOAT:
return ";VK_FORMAT_R32_SFLOAT";
case VK_FORMAT_R32G32_UINT:
return ";VK_FORMAT_R32G32_UINT";
case VK_FORMAT_R32G32_SINT:
return ";VK_FORMAT_R32G32_SINT";
case VK_FORMAT_R32G32_SFLOAT:
return ";VK_FORMAT_R32G32_SFLOAT";
case VK_FORMAT_R32G32B32_UINT:
return ";VK_FORMAT_R32G32B32_UINT";
case VK_FORMAT_R32G32B32_SINT:
return ";VK_FORMAT_R32G32B32_SINT";
case VK_FORMAT_R32G32B32_SFLOAT:
return ";VK_FORMAT_R32G32B32_SFLOAT";
case VK_FORMAT_R32G32B32A32_UINT:
return ";VK_FORMAT_R32G32B32A32_UINT";
case VK_FORMAT_R32G32B32A32_SINT:
return ";VK_FORMAT_R32G32B32A32_SINT";
case VK_FORMAT_R32G32B32A32_SFLOAT:
return ";VK_FORMAT_R32G32B32A32_SFLOAT";
case VK_FORMAT_R64_UINT:
return ";VK_FORMAT_R64_UINT";
case VK_FORMAT_R64_SINT:
return ";VK_FORMAT_R64_SINT";
case VK_FORMAT_R64_SFLOAT:
return ";VK_FORMAT_R64_SFLOAT";
case VK_FORMAT_R64G64_UINT:
return ";VK_FORMAT_R64G64_UINT";
case VK_FORMAT_R64G64_SINT:
return ";VK_FORMAT_R64G64_SINT";
case VK_FORMAT_R64G64_SFLOAT:
return ";VK_FORMAT_R64G64_SFLOAT";
case VK_FORMAT_R64G64B64_UINT:
return ";VK_FORMAT_R64G64B64_UINT";
case VK_FORMAT_R64G64B64_SINT:
return ";VK_FORMAT_R64G64B64_SINT";
case VK_FORMAT_R64G64B64_SFLOAT:
return ";VK_FORMAT_R64G64B64_SFLOAT";
case VK_FORMAT_R64G64B64A64_UINT:
return ";VK_FORMAT_R64G64B64A64_UINT";
case VK_FORMAT_R64G64B64A64_SINT:
return ";VK_FORMAT_R64G64B64A64_SINT";
case VK_FORMAT_R64G64B64A64_SFLOAT:
return ";VK_FORMAT_R64G64B64A64_SFLOAT";
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
return ";VK_FORMAT_B10G11R11_UFLOAT_PACK32";
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
return ";VK_FORMAT_E5B9G9R9_UFLOAT_PACK32";
case VK_FORMAT_D16_UNORM:
return ";VK_FORMAT_D16_UNORM";
case VK_FORMAT_X8_D24_UNORM_PACK32:
return ";VK_FORMAT_X8_D24_UNORM_PACK32";
case VK_FORMAT_D32_SFLOAT:
return ";VK_FORMAT_D32_SFLOAT";
case VK_FORMAT_S8_UINT:
return ";VK_FORMAT_S8_UINT";
case VK_FORMAT_D16_UNORM_S8_UINT:
return ";VK_FORMAT_D16_UNORM_S8_UINT";
case VK_FORMAT_D24_UNORM_S8_UINT:
return ";VK_FORMAT_D24_UNORM_S8_UINT";
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return ";VK_FORMAT_D32_SFLOAT_S8_UINT";
case VK_FORMAT_G8B8G8R8_422_UNORM:
return ";VK_FORMAT_G8B8G8R8_422_UNORM";
case VK_FORMAT_B8G8R8G8_422_UNORM:
return ";VK_FORMAT_B8G8R8G8_422_UNORM";
default:
return "Unknown Format";
}
}
static TBuiltInResource create_default_resource_limits()
{
TBuiltInResource limits = {};
limits.maxLights = 32;
limits.maxClipPlanes = 6;
limits.maxTextureUnits = 32;
limits.maxTextureCoords = 32;
limits.maxVertexAttribs = 64;
limits.maxVertexUniformComponents = 4096;
limits.maxVaryingFloats = 64;
limits.maxVertexTextureImageUnits = 32;
limits.maxCombinedTextureImageUnits = 80;
limits.maxTextureImageUnits = 32;
limits.maxFragmentUniformComponents = 4096;
limits.maxDrawBuffers = 32;
limits.maxVertexUniformVectors = 128;
limits.maxVaryingVectors = 8;
limits.maxFragmentUniformVectors = 16;
limits.maxVertexOutputVectors = 16;
limits.maxFragmentInputVectors = 15;
limits.minProgramTexelOffset = -8;
limits.maxProgramTexelOffset = 7;
limits.maxClipDistances = 8;
limits.maxComputeWorkGroupCountX = 65535;
limits.maxComputeWorkGroupCountY = 65535;
limits.maxComputeWorkGroupCountZ = 65535;
limits.maxComputeWorkGroupSizeX = 1024;
limits.maxComputeWorkGroupSizeY = 1024;
limits.maxComputeWorkGroupSizeZ = 64;
limits.maxComputeUniformComponents = 1024;
limits.maxComputeTextureImageUnits = 16;
limits.maxComputeImageUniforms = 8;
limits.maxComputeAtomicCounters = 8;
limits.maxComputeAtomicCounterBuffers = 1;
limits.maxVaryingComponents = 60;
limits.maxVertexOutputComponents = 64;
limits.maxGeometryInputComponents = 64;
limits.maxGeometryOutputComponents = 128;
limits.maxFragmentInputComponents = 128;
limits.maxImageUnits = 8;
limits.maxCombinedImageUnitsAndFragmentOutputs = 8;
limits.maxCombinedShaderOutputResources = 8;
limits.maxImageSamples = 0;
limits.maxVertexImageUniforms = 0;
limits.maxTessControlImageUniforms = 0;
limits.maxTessEvaluationImageUniforms = 0;
limits.maxGeometryImageUniforms = 0;
limits.maxFragmentImageUniforms = 8;
limits.maxCombinedImageUniforms = 8;
limits.maxGeometryTextureImageUnits = 16;
limits.maxGeometryOutputVertices = 256;
limits.maxGeometryTotalOutputComponents = 1024;
limits.maxGeometryUniformComponents = 1024;
limits.maxGeometryVaryingComponents = 64;
limits.maxTessControlInputComponents = 128;
limits.maxTessControlOutputComponents = 128;
limits.maxTessControlTextureImageUnits = 16;
limits.maxTessControlUniformComponents = 1024;
limits.maxTessControlTotalOutputComponents = 4096;
limits.maxTessEvaluationInputComponents = 128;
limits.maxTessEvaluationOutputComponents = 128;
limits.maxTessEvaluationTextureImageUnits = 16;
limits.maxTessEvaluationUniformComponents = 1024;
limits.maxTessPatchComponents = 120;
limits.maxPatchVertices = 32;
limits.maxTessGenLevel = 64;
limits.maxViewports = 16;
limits.maxVertexAtomicCounters = 0;
limits.maxTessControlAtomicCounters = 0;
limits.maxTessEvaluationAtomicCounters = 0;
limits.maxGeometryAtomicCounters = 0;
limits.maxFragmentAtomicCounters = 8;
limits.maxCombinedAtomicCounters = 8;
limits.maxAtomicCounterBindings = 1;
limits.maxVertexAtomicCounterBuffers = 0;
limits.maxTessControlAtomicCounterBuffers = 0;
limits.maxTessEvaluationAtomicCounterBuffers = 0;
limits.maxGeometryAtomicCounterBuffers = 0;
limits.maxFragmentAtomicCounterBuffers = 1;
limits.maxCombinedAtomicCounterBuffers = 1;
limits.maxAtomicCounterBufferSize = 16384;
limits.maxTransformFeedbackBuffers = 4;
limits.maxTransformFeedbackInterleavedComponents = 64;
limits.maxCullDistances = 8;
limits.maxCombinedClipAndCullDistances = 8;
limits.maxSamples = 4;
limits.limits.nonInductiveForLoops = 1;
limits.limits.whileLoops = 1;
limits.limits.doWhileLoops = 1;
limits.limits.generalUniformIndexing = 1;
limits.limits.generalAttributeMatrixVectorIndexing = 1;
limits.limits.generalVaryingIndexing = 1;
limits.limits.generalSamplerIndexing = 1;
limits.limits.generalVariableIndexing = 1;
limits.limits.generalConstantMatrixVectorIndexing = 1;
return limits;
}
// Default resource values for glslang.
const TBuiltInResource* get_default_resource_limits()
{
static const TBuiltInResource default_resource_limits = create_default_resource_limits();
return &default_resource_limits;
}
} // namespace mi::examples::vk
Color clamp(const Color &c, const Color &low, const Color &high)
Returns the color c elementwise clamped to the range [low, high].
Definition: color.h:522
[Previous] [Up] [Next]