MDL SDK API nvidia_logo_transpbg.gif Up
Example for Execution of Compiled MDL Materials (GLSL Vulkan)
[Previous] [Up] [Next]

This example shows how a Vulkan based renderer can call the code generated by the "GLSL" backend for compiled materials to evaluate sub-expressions of multiple materials. This example mostly explains how to configure the "GLSL" backend for Vulkan. Loading textures and the texture access functions are not covered here. For an introduction and a more in depth explanation of the "GLSL" backend and how to work with textures in please refer to the Example for Execution of Compiled MDL Materials (GLSL) section.

New Topics

  • Configuration of the GLSL backend for Vulkan

Detailed Description

Configuration of the GLSL backend for Vulkan


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 see the documentation of the set_option method. It is important to set the correct GLSL version and enable placing read-only data into a storage buffer (SSBO) instead of global uniforms since, at the time of writing, GLSL for Vulkan does not support global uniforms. Additionally, the descriptor set and binding of the storage buffer need to be specified. It is also advisable to set "glsl_max_const_data" to zero or a very low value to avoid running into above problems when you already use some constant data in your own code.

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

When SSBOs are enabled, the GLSL backend will place the data of all uniforms into one SSBO. The data for the SSBO can be accessed with mi::neuraylib::ITarget_code::get_ro_data_segment_data(). In this example, the generated SSBO will look like this:

layout(std430, binding = 1u, set = 0u) readonly buffer mdl_buffer {
int[256] mdl_field;
int[256] mdl_field30;
int[256] mdl_field33;
int[256] mdl_field36;
int[256] mdl_field131;
};

Please refer to the create_ro_data_buffer function on how to create the SSBO in Vulkan, which can then be used bound in a descriptor set.

Also please refer to the create_material_texture function on how to create the material textures in Vulkan.

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/execution_glsl_vk/example_execution_glsl_vk.cpp

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/execution_glsl_vk/example_execution_glsl_vk.cpp
//
// Introduces the execution of generated code in Vulkan for compiled materials
// for the GLSL backend and shows how to manually bake a material sub-expression
// to a texture.
#include <string>
#include <sstream>
#include <vector>
#include <iostream>
#include <cstring>
#include "example_shared.h"
#include "example_vulkan_shared.h"
#include <vulkan/vulkan.h>
#include <GLFW/glfw3.h>
// If defined, the GLSL backend will remap these functions
// float ::base::perlin_noise(float4 pos)
// float ::base::mi_noise(float3 pos)
// float ::base::mi_noise(int3 pos)
// ::base::worley_return ::base::worley_noise(float3 pos, float jitter, int metric)
//
// to lut-free alternatives.
//#define REMAP_NOISE_FUNCTIONS
// Enable this to dump the generated GLSL code to stdout.
//#define DUMP_GLSL
// Command line options structure.
struct Options
{
bool no_window = false;
std::string outputfile = "output.png"; // Output filename in headless mode
uint32_t res_x = 1024;
uint32_t res_y = 768;
uint32_t num_images = 3;
bool enable_validation_layers = false;
uint32_t material_pattern = 7; // Starting material pattern
};
struct Vulkan_texture
{
VkImage image = nullptr;
VkImageView image_view = nullptr;
VkDeviceMemory device_memory = nullptr;
};
struct Vulkan_buffer
{
VkBuffer buffer = nullptr;
VkDeviceMemory device_memory = nullptr;
};
static const char* g_vertex_shader_filename = "example_execution_glsl_vk.vert";
static const char* g_fragment_shader_filename = "example_execution_glsl_vk.frag";
// These have to be in sync with the bindings specified in the shaders
static const uint32_t g_material_textures_descriptor_binding = 0;
static const uint32_t g_ro_data_descriptor_set = 0;
static const uint32_t g_ro_data_descriptor_binding = 1;
//------------------------------------------------------------------------------
// GLSL shader creation functions
//------------------------------------------------------------------------------
// Generate GLSL source code for a function executing an MDL subexpression function
// selected by a given id. This function is used in the fragment shader's main function
// to select which MDL function to use.
std::string generate_glsl_switch_func(const mi::neuraylib::ITarget_code* target_code)
{
// Note: The "State" struct must be in sync with the struct in example_execution_glsl_vk.frag and
// the code generated by the MDL SDK (see dumped code when enabling DUMP_GLSL).
std::string src =
"#version 450\n"
"struct State {\n"
" vec3 normal;\n"
" vec3 geom_normal;\n"
" vec3 position;\n"
" float animation_time;\n"
" vec3[1] text_coords;\n"
" vec3[1] tangent_u;\n"
" vec3[1] tangent_v;\n"
" int ro_data_segment_offset;\n"
" mat4 world_to_object;\n"
" mat4 object_to_world;\n"
" int object_id;\n"
" float meters_per_scene_unit;\n"
" int arg_block_offset;\n"
"};\n"
"\n"
"uint get_mdl_num_mat_subexprs() { return " +
std::to_string(target_code->get_callable_function_count()) +
"u; }\n"
"\n";
std::string switch_func =
"vec3 mdl_mat_subexpr(uint id, State state) {\n"
" switch(id) {\n";
// Create one switch case for each callable function in the target code
for (size_t i = 0, num_target_codes = target_code->get_callable_function_count();
i < num_target_codes;
++i)
{
std::string func_name(target_code->get_callable_function(i));
// Add prototype declaration
src += target_code->get_callable_function_prototype(
i, mi::neuraylib::ITarget_code::SL_GLSL);
src += '\n';
switch_func +=
" case " + std::to_string(i) + "u: return " + func_name + "(state);\n";
}
switch_func +=
" default: return vec3(0);\n"
" }\n"
"}\n";
return src + "\n" + switch_func;
}
// Creates the fragment shader by compiling the target code to SPIR-V and
// linking it with the application defined fragment shader.
VkShaderModule create_fragment_shader_module(
VkDevice device, const mi::neuraylib::ITarget_code* target_code)
{
std::stringstream main_source;
main_source << "#version 450\n";
main_source << "#define NUM_TEXTURES "
<< std::to_string(target_code->get_texture_count() - 1) << "\n";
main_source << mi::examples::io::read_text_file(
mi::examples::io::get_executable_folder() + "/" + g_fragment_shader_filename);
#ifdef DUMP_GLSL
std::cout << "Dumping main GLSL code:\n\n" << main_source.str() << std::endl;
#endif
std::string generated_target_source(target_code->get_code());
#ifdef REMAP_NOISE_FUNCTIONS
generated_target_source.append(mi::examples::io::read_text_file(
mi::examples::io::get_executable_folder() + "/" + "noise_no_lut.glsl"));
#endif
#ifdef DUMP_GLSL
std::cout << "Dumping GLSL target code:\n\n" << generated_target_source << std::endl;
#endif
// Generate GLSL switch function for the generated functions
std::string glsl_switch_func_source = generate_glsl_switch_func(target_code);
#ifdef DUMP_GLSL
std::cout << "Dumping GLSL code for the \"mdl_mat_subexpr\" switch function:\n\n"
<< glsl_switch_func_source << std::endl;
#endif
mi::examples::vk::Glsl_compiler glsl_compiler(EShLangFragment, "main");
glsl_compiler.add_shader(main_source.str());
glsl_compiler.add_shader(generated_target_source);
glsl_compiler.add_shader(glsl_switch_func_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;
}
VkShaderModule create_vertex_shader_module(VkDevice device)
{
std::string shader_source = mi::examples::io::read_text_file(
mi::examples::io::get_executable_folder() + "/" + g_vertex_shader_filename);
mi::examples::vk::Glsl_compiler glsl_compiler(EShLangVertex, "main");
glsl_compiler.add_shader(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;
}
//------------------------------------------------------------------------------
// MDL material compilation
//------------------------------------------------------------------------------
// Generates GLSL code for the given material names and expressions.
const mi::neuraylib::ITarget_code* generate_glsl_code(
const std::vector<std::string>& material_db_names,
const std::vector<std::string>& expression_paths,
const std::vector<std::string>& function_names)
{
// Obtain and configure the GLSL backend
check_success(be_glsl->set_option("num_texture_spaces", "1") == 0);
// Vulkan requires GLSL 4.50 or higher
check_success(be_glsl->set_option("glsl_version", "450") == 0);
// Vulkan doesn't support global non-opaque uniforms, so we need to
// use a SSBO for read-only data. We set "max_const_data" to 0 to enforce
// that all read-only data is in the SSBO. Otherwise, data will be stored
// in global constants.
check_success(be_glsl->set_option("glsl_max_const_data", "0") == 0);
check_success(be_glsl->set_option("glsl_place_uniforms_into_ssbo", "on") == 0);
check_success(be_glsl->set_option("glsl_uniform_ssbo_binding",
std::to_string(g_ro_data_descriptor_binding).c_str()) == 0);
check_success(be_glsl->set_option("glsl_uniform_ssbo_set",
std::to_string(g_ro_data_descriptor_set).c_str()) == 0);
#ifdef REMAP_NOISE_FUNCTIONS
// Remap noise functions that access the constant tables
check_success(be_glsl->set_option("glsl_remap_functions",
"_ZN4base12perlin_noiseEu6float4=noise_float4"
",_ZN4base12worley_noiseEu6float3fi=noise_worley"
",_ZN4base8mi_noiseEu6float3=noise_mi_float3"
",_ZN4base8mi_noiseEu4int3=noise_mi_int3") == 0);
#endif
// Add all material sub-expressions to a link unit, so we can afterwards
// generate the GLSL code for all of them
be_glsl->create_link_unit(transaction, context));
for (size_t i = 0; i < material_db_names.size(); ++i)
{
// Get the material definition from the database
transaction->access<mi::neuraylib::IFunction_definition>(material_db_names[i].c_str()));
if (!material_definition)
exit_failure("Failed to access material definition '%s'.", material_db_names[i].c_str());
// Create a material instance for the queried material definition.
// NOTE: here the default arguments are used by not passing an
// argument list. Pass in an instance of IExpression_list
// to give arguments non-default values.
mi::Sint32 result;
material_definition->create_function_call(nullptr, &result));
if (result != 0)
exit_failure("Failed to instantiate material '%s'.", material_db_names[i].c_str());
// Compile the material instance in instance compilation mode (DEFAULT_OPTIONS).
// This means that material arguments are baked into the shader code
// can't be changed without recompiling the material instance. In
// class compilation mode (CLASS_COMPILATION) arguments can be changed
// without the need for recompiling, but this also limits optimizations.
material_instance->get_interface< mi::neuraylib::IMaterial_instance>());
material_instance2->create_compiled_material(compile_flags, context));
check_success(print_messages(context));
// Add the material expression with the given path relative to the material
// root (e.g. surface.scattering.tint) of the compiled material to the link
// unit and give it a function name to use when generating the GLSL code.
// This is the name of the generated GLSL function. The path to the material
// expression is always relative to the material root.
const char* path = expression_paths[i].c_str();
const char* fname = function_names[i].c_str();
link_unit->add_material_expression(compiled_material.get(), path, fname, context);
check_success(print_messages(context));
}
// Generate the GLSL code for the link unit
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();
}
//------------------------------------------------------------------------------
// MDL and Vulkan interop code
//------------------------------------------------------------------------------
// 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)
{
Vulkan_buffer ro_data_buffer;
mi::Size num_segments = target_code->get_ro_data_segment_count();
if (num_segments == 0)
return ro_data_buffer; // Everything is nullptr
if (num_segments > 1)
{
std::cerr << "Multiple uniforms are defined for read-only data."
<< " This should not be the case if a storage buffer is used.\n";
terminate();
}
{ // 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 = target_code->get_ro_data_segment_size(0);
buffer_create_info.usage
= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VK_CHECK(vkCreateBuffer(
device, &buffer_create_info, nullptr, &ro_data_buffer.buffer));
// Allocate device memory for the buffer.
ro_data_buffer.device_memory = mi::examples::vk::allocate_and_bind_buffer_memory(
device, physical_device, ro_data_buffer.buffer,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
{
mi::examples::vk::Staging_buffer staging_buffer(device, physical_device,
target_code->get_ro_data_segment_size(0), 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, target_code->get_ro_data_segment_data(0),
target_code->get_ro_data_segment_size(0));
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();
VkBufferCopy copy_region = {};
copy_region.size = target_code->get_ro_data_segment_size(0);
vkCmdCopyBuffer(command_buffer.get(),
staging_buffer.get(), ro_data_buffer.buffer, 1, &copy_region);
command_buffer.end_and_submit(queue);
}
return ro_data_buffer;
}
// 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();
}
if (tex_layers != 1)
{
std::cerr << "The example doesn't support layered images!" << std::endl;
terminate();
}
// This example supports only 2D textures
= target_code->get_texture_shape(texture_index);
{
std::cerr << "The example only supports 2D 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
Vulkan_texture material_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.width = tex_width;
image_create_info.extent.height = tex_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_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
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 staging_buffer_size = tex_width * tex_height * sizeof(float) * 4; // RGBA32F
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
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(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, 1 };
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 = VK_IMAGE_VIEW_TYPE_2D;
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;
}
//------------------------------------------------------------------------------
// Applications logic
//------------------------------------------------------------------------------
class Example_app : public mi::examples::vk::Vulkan_example_app
{
public:
Example_app(
const Options& options)
: Vulkan_example_app(mdl_impexp_api.get(), image_api.get())
, m_transaction(transaction)
, m_target_code(target_code)
, m_options(options)
{
m_user_data.animation_time = 0.0f;
m_user_data.material_pattern = options.material_pattern;
}
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 frame's command buffer is populated.
virtual void update(float elapsed_seconds, uint32_t frame_index) override;
// Populates the current frame's command buffer.
virtual void render(VkCommandBuffer command_buffer, uint32_t frame_index, uint32_t image_index) override;
// Handles keyboard input from the window.
virtual void key_callback(int key, int action) override;
private:
// 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_layout();
// Create the pipeline layout and state for rendering a fullscreen triangle.
void create_graphics_pipeline_layout();
void create_graphics_pipeline();
// 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_set();
// Writes the descriptors for all material resources into the descriptor set.
void populate_descriptor_set();
private:
// Vulkan resources created for the target code
Vulkan_buffer m_ro_data_buffer;
std::vector<Vulkan_texture> m_material_textures;
VkPipelineLayout m_pipeline_layout = nullptr;
VkPipeline m_graphics_pipeline = nullptr;
VkDescriptorSetLayout m_descriptor_set_layout = nullptr;
VkDescriptorPool m_descriptor_pool = nullptr;
VkDescriptorSet m_descriptor_set = nullptr;
VkSampler m_linear_sampler = nullptr;
// Push constants data
struct User_data
{
uint32_t material_pattern;
float animation_time;
} m_user_data;
Options m_options;
uint32_t m_last_image_index = 0;
};
void Example_app::init_resources()
{
glslang::InitializeProcess();
// Create the buffer that holds all the read-only data used by the materials
m_ro_data_buffer = create_ro_data_buffer(
m_device, m_physical_device, m_graphics_queue, m_command_pool, m_target_code.get());
// Create the textures used by the materials.
// Texture index 0 is skipped here since this is the invalid texture.
for (mi::Size tex_index = 1; tex_index < m_target_code->get_texture_count(); tex_index++)
{
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(),
tex_index);
m_material_textures.push_back(texture);
}
// Create other rendering related resources
create_descriptor_set_layout();
create_descriptor_pool_and_set();
create_graphics_pipeline_layout();
create_graphics_pipeline();
m_linear_sampler = mi::examples::vk::create_linear_sampler(m_device);
// Make the resources available on the GPU during rendering
populate_descriptor_set();
}
void Example_app::cleanup_resources()
{
// In "no window" mode output the last rendered image
// before destroying all resources.
if (m_options.no_window)
save_screenshot(m_last_image_index, m_options.outputfile.c_str());
for (Vulkan_texture& texture : m_material_textures)
{
vkDestroyImageView(m_device, texture.image_view, nullptr);
vkDestroyImage(m_device, texture.image, nullptr);
vkFreeMemory(m_device, texture.device_memory, nullptr);
}
vkDestroyBuffer(m_device, m_ro_data_buffer.buffer, nullptr);
vkFreeMemory(m_device, m_ro_data_buffer.device_memory, nullptr);
vkDestroySampler(m_device, m_linear_sampler, nullptr);
vkDestroyDescriptorPool(m_device, m_descriptor_pool, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_descriptor_set_layout, nullptr);
vkDestroyPipelineLayout(m_device, m_pipeline_layout, nullptr);
vkDestroyPipeline(m_device, m_graphics_pipeline, 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 Example_app::recreate_size_dependent_resources()
{
vkDestroyPipeline(m_device, m_graphics_pipeline, nullptr);
create_graphics_pipeline();
}
// Updates the application logic. This is called right before
// the frame's command buffer is populated.
void Example_app::update(float elapsed_seconds, uint32_t frame_index)
{
m_user_data.animation_time += elapsed_seconds;
}
// Populates the current frame's command buffer.
void Example_app::render(VkCommandBuffer command_buffer, uint32_t frame_index, uint32_t image_index)
{
// Save image index for outputing screenshot on exit
m_last_image_index = image_index;
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_graphics_pipeline);
vkCmdPushConstants(command_buffer, m_pipeline_layout,
VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(User_data), &m_user_data);
vkCmdBindDescriptorSets(
command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout,
0, 1, &m_descriptor_set, 0, nullptr);
vkCmdDraw(command_buffer, 3, 1, 0, 0);
vkCmdEndRenderPass(command_buffer);
}
// Handles keyboard input from the window.
void Example_app::key_callback(int key, int action)
{
// Handle only key press events
if (action != GLFW_PRESS)
return;
// Map keypad numbers to normal numbers
if (GLFW_KEY_KP_0 <= key && key <= GLFW_KEY_KP_9)
key += GLFW_KEY_0 - GLFW_KEY_KP_0;
switch (key)
{
// Numbers 1 - 7 select the different material patterns
case GLFW_KEY_1:
case GLFW_KEY_2:
case GLFW_KEY_3:
case GLFW_KEY_4:
case GLFW_KEY_5:
case GLFW_KEY_6:
case GLFW_KEY_7:
m_user_data.material_pattern = key - GLFW_KEY_0;
break;
case GLFW_KEY_ENTER:
request_screenshot();
break;
}
}
// Creates the descriptors set layout which is used to create the
// pipeline layout. Here the number of material resources is declared.
void Example_app::create_descriptor_set_layout()
{
// Declare the descriptor set layout. See create_descriptor_pool_and set
// for how the descriptors are created.
VkDescriptorSetLayoutBinding material_textures_layout_binding = {};
material_textures_layout_binding.binding = g_material_textures_descriptor_binding;
material_textures_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
material_textures_layout_binding.descriptorCount
= static_cast<uint32_t>(m_material_textures.size());
material_textures_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayoutBinding ro_data_buffer_layout_binding = {};
ro_data_buffer_layout_binding.binding = g_ro_data_descriptor_binding;
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_FRAGMENT_BIT;
const VkDescriptorSetLayoutBinding descriptor_set_layout_bindings[] = {
material_textures_layout_binding,
ro_data_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(descriptor_set_layout_bindings);
descriptor_set_layout_create_info.pBindings = descriptor_set_layout_bindings;
VK_CHECK(vkCreateDescriptorSetLayout(
m_device, &descriptor_set_layout_create_info,
nullptr, &m_descriptor_set_layout));
}
void Example_app::create_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(User_data);
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_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_pipeline_layout));
}
void Example_app::create_graphics_pipeline()
{
VkShaderModule vertex_shader = create_vertex_shader_module(m_device);
VkShaderModule fragment_shader
= create_fragment_shader_module(m_device, m_target_code.get());
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 = 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 = fragment_shader;
fragment_shader_stage_info.pName = "main";
const VkPipelineShaderStageCreateInfo shader_stages[] = {
vertex_shader_stage_info,
fragment_shader_stage_info
};
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;
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(m_image_width);
viewport.height = static_cast<float>(m_image_height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor_rect;
scissor_rect.offset.x = 0;
scissor_rect.offset.y = 0;
scissor_rect.extent.width = m_image_width;
scissor_rect.extent.height = 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_COUNTER_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;
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_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_graphics_pipeline));
vkDestroyShaderModule(m_device, vertex_shader, nullptr);
vkDestroyShaderModule(m_device, fragment_shader, nullptr);
}
// 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 Example_app::create_descriptor_pool_and_set()
{
// Create descriptor pool. Make enough space for the amount of textures
// and read-only segments (storage buffers) specified in the target code.
VkDescriptorPoolSize texture_pool_size;
texture_pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
texture_pool_size.descriptorCount = static_cast<uint32_t>(m_material_textures.size());
// At the current point in time only one storage buffer will be declared
// in GLSL target code for all RO-data. So we can safely assume that we
// only need exactly one storage buffer descriptor.
VkDescriptorPoolSize storage_buffer_pool_size;
storage_buffer_pool_size.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
storage_buffer_pool_size.descriptorCount = 1;
const VkDescriptorPoolSize pool_sizes[] = {
texture_pool_size,
storage_buffer_pool_size
};
VkDescriptorPoolCreateInfo descriptor_pool_create_info = {};
descriptor_pool_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_create_info.maxSets = 1;
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
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_descriptor_set_layout;
VK_CHECK(vkAllocateDescriptorSets(
m_device, &descriptor_set_alloc_info, &m_descriptor_set));
}
// Writes the descriptors for all material resources into the descriptor set.
void Example_app::populate_descriptor_set()
{
std::vector<VkDescriptorImageInfo> descriptor_image_infos(m_material_textures.size());
for (size_t i = 0; i < m_material_textures.size(); i++)
{
descriptor_image_infos[i].sampler = m_linear_sampler;
descriptor_image_infos[i].imageView = m_material_textures[i].image_view;
descriptor_image_infos[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
VkWriteDescriptorSet textures_descriptor_write = {};
textures_descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
textures_descriptor_write.dstSet = m_descriptor_set;
textures_descriptor_write.dstBinding = g_material_textures_descriptor_binding;
textures_descriptor_write.dstArrayElement = 0;
textures_descriptor_write.descriptorCount
= static_cast<uint32_t>(descriptor_image_infos.size());
textures_descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
textures_descriptor_write.pImageInfo = descriptor_image_infos.data();
VkDescriptorBufferInfo descriptor_ro_data_info;
descriptor_ro_data_info.buffer = m_ro_data_buffer.buffer;
descriptor_ro_data_info.offset = 0;
descriptor_ro_data_info.range = VK_WHOLE_SIZE;
VkWriteDescriptorSet ro_data_descriptor_write = {};
ro_data_descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
ro_data_descriptor_write.dstSet = m_descriptor_set;
ro_data_descriptor_write.dstBinding = g_ro_data_descriptor_binding;
ro_data_descriptor_write.dstArrayElement = 0;
ro_data_descriptor_write.descriptorCount = 1;
ro_data_descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
ro_data_descriptor_write.pBufferInfo = &descriptor_ro_data_info;
std::vector<VkWriteDescriptorSet> descriptor_writes;
descriptor_writes.push_back(textures_descriptor_write);
// Might be that no buffer was created for the read-only data.
if (m_ro_data_buffer.buffer)
descriptor_writes.push_back(ro_data_descriptor_write);
vkUpdateDescriptorSets(
m_device, descriptor_writes.size(), descriptor_writes.data(), 0, nullptr);
}
//------------------------------------------------------------------------------
// Command line helpers
//------------------------------------------------------------------------------
void usage(char const* prog_name)
{
std::cout
<< "Usage: " << prog_name << " [options] [<material_pattern>]\n"
<< "Options:\n"
<< " --nowin don't show interactive display\n"
<< " --res <x> <y> resolution (default: 1024x768)\n"
<< " --numimg <n> swapchain image count (default: 3)\n"
<< " -o <outputfile> image file to write result in nowin mode (default: output.png)\n"
<< " --vkdebug enable the Vulkan validation layers\n"
<< " <material_pattern> a number from 1 to 7 choosing which material combination to use"
<< std::endl;
exit_failure();
}
Options parse_command_line(int argc, char* argv[])
{
Options options;
for (int i = 1; i < argc; ++i)
{
std::string arg = argv[i];
if (arg[0] == '-')
{
if (arg == "--nowin")
options.no_window = true;
else if (arg == "-o" && i < argc - 1)
options.outputfile = argv[++i];
else if (arg == "--res" && i < argc - 2)
{
options.res_x = std::max(std::atoi(argv[++i]), 1);
options.res_y = std::max(std::atoi(argv[++i]), 1);
}
else if (arg == "--numimg" && i < argc - 1)
options.num_images = std::max(std::atoi(argv[++i]), 2);
else if (arg == "--vkdebug")
options.enable_validation_layers = true;
else
usage(argv[0]);
}
else
{
options.material_pattern = static_cast<uint32_t>(std::atoi(argv[i]));
if (options.material_pattern < 1 || options.material_pattern > 7)
{
std::cerr << "Invalid material_pattern parameter." << std::endl;
usage(argv[0]);
}
}
}
return options;
}
//------------------------------------------------------------------------------
// Main function
//------------------------------------------------------------------------------
int MAIN_UTF8(int argc, char* argv[])
{
Options options = parse_command_line(argc, argv);
// Access the MDL SDK
mi::examples::mdl::load_and_get_ineuray());
if (!neuray.is_valid_interface())
exit_failure("Failed to load the SDK.");
// Configure the MDL SDK
mi::examples::mdl::Configure_options configure_options;
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>());
mdl_factory->create_execution_context());
// Load module
std::string module_name = "::nvidia::sdk_examples::tutorials";
mdl_impexp_api->load_module(transaction.get(), module_name.c_str(), context.get());
if (!print_messages(context.get()))
exit_failure("Loading module '%s' failed.", module_name.c_str());
// Get the database name for the module we loaded
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.");
// We will generate GLSL code for these materials
std::vector<std::string> material_simple_names;
material_simple_names.push_back("example_execution1");
material_simple_names.push_back("example_execution2");
material_simple_names.push_back("example_execution3");
// To get the materials from the database we need the full database names.
// These include the module's database name, the material name and the
// function/material signature.
// e.g. mdl::nvidia::sdk_examples::tutorials::example_execution1(color)
std::vector<std::string> material_db_names(material_simple_names.size());
for (size_t i = 0; i < material_db_names.size(); ++i)
{
material_db_names[i]
= std::string(module_db_name->get_c_str()) + "::" + material_simple_names[i];
material_db_names[i] = mi::examples::mdl::add_missing_material_signature(
module.get(), material_db_names[i]);
if (material_db_names[i].empty())
{
exit_failure("Failed to find the material %s in the module %s.",
material_simple_names[i].c_str(), module_name.c_str());
}
}
module.reset();
// In this example we are generating code for material expressions
// instead of full materials, so we have to specify which expression
// should be translated. These paths are always relative to the
// material root.
std::vector<std::string> expression_paths;
expression_paths.push_back("surface.scattering.tint");
expression_paths.push_back("surface.scattering.tint");
expression_paths.push_back("surface.scattering.tint");
// The names of the generated functions for the material expressions
// can be chosen freely. These are the function names we will execute
// in the GLSL fragment shader.
std::vector<std::string> function_names;
function_names.push_back("tint");
function_names.push_back("tint_2");
function_names.push_back("tint_3");
// Generate GLSL code for the specified materials and expressions
generate_glsl_code( mdl_backend_api.get(), transaction.get(), context.get(),
material_db_names, expression_paths, function_names));
// Acquire image API needed to prepare the textures
neuray->get_api_component<mi::neuraylib::IImage_api>());
{
mi::examples::vk::Vulkan_example_app::Config app_config;
app_config.window_title = "MDL SDK GLSL Vulkan Execution Example - Switch pattern with keys 1 - 7";
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.enable_validation_layers = options.enable_validation_layers;
// Start application. The generated target code is used to create
// the fragment shader module for the graphics pipeline, as well
// as create the rendering resources (ro-data, textures, etc.)
Example_app app(
transaction, mdl_impexp_api, image_api, target_code, 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
This interface is used to interact with the distributed database.
Definition: idatabase.h:289
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
@ 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
Factory for various MDL interfaces and functions.
Definition: imdl_factory.h:53
API component for MDL related import and export operations.
Definition: imdl_impexp_api.h:43
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.
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
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 const char * get_callable_function_prototype(Size index, Prototype_language lang) const =0
Returns the prototype of a callable function in the target code.
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_callable_function_count() const =0
Returns the number of callable functions in the target code.
virtual Size get_texture_count() const =0
Returns the number of texture resources used by the target code.
virtual const char * get_callable_function(Size index) const =0
Returns the name of a callable function in the target code.
Texture_shape
Definition: imdl_backend.h:811
@ Texture_shape_2d
Two-dimensional texture.
Definition: imdl_backend.h:813

Source Code Location: examples/mdl_sdk/execution_glsl_vk/example_execution_glsl_vk.vert

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/execution_glsl_vk/example_execution_glsl_vk.vert
#version 450
layout(location = 0) out vec3 vPosition;
layout(location = 1) out vec3 vTexCoord;
void main()
{
vec2 uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
vPosition = vec3(uv * 2.0 - 1.0, 0.0);
vTexCoord = vec3(uv, 0.0);
gl_Position = vec4(vPosition, 1.0);
// The y-axis needs to be inverted, since Vulkan uses to top-left corner for (0,0)
// but the example expects the bottom-left corner to be the origin.
gl_Position.y = -gl_Position.y;
}

Source Code Location: examples/mdl_sdk/execution_glsl_vk/example_execution_glsl_vk.frag

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/execution_glsl_vk/example_execution_glsl_vk.frag
//
// This file contains the implementations of the texture access functions
// and the fragment shader used to evaluate the material sub-expressions.
layout(push_constant) uniform User_data
{
// Material pattern as chosen by the user.
uint material_pattern;
// Current time in seconds since the start of the render loop.
float animation_time;
} user_data;
// Array containing all 2D texture samplers of all used materials.
layout(set = 0, binding = 0) uniform sampler2D material_texture_samplers_2d[NUM_TEXTURES];
// The input variables coming from the vertex shader.
layout(location = 0) in vec3 vPosition;
layout(location = 1) in vec3 vTexCoord;
// The color output variable of this fragment shader.
layout(location = 0) out vec4 FragColor;
// The MDL material state structure as configured via the GLSL backend options.
// Note: Must be in sync with the state struct in generate_glsl_switch_func and the code generated
// by the MDL SDK (see dumped code when enabling DUMP_GLSL in example_execution_glsl_vk.cpp).
struct State
{
vec3 normal;
vec3 geom_normal;
vec3 position;
float animation_time;
vec3 text_coords[1];
vec3 tangent_u[1];
vec3 tangent_v[1];
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;
};
//
// The prototypes of the functions generated in our generate_glsl_switch_func() function.
//
// Return the number of available MDL material subexpressions.
uint get_mdl_num_mat_subexprs();
// Return the result of the MDL material subexpression given by the id.
vec3 mdl_mat_subexpr(uint id, State state);
// Implementation of tex::lookup_*() for a texture_2d texture.
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);
return texture(material_texture_samplers_2d[tex - 1], coord);
}
// Implementation of tex::texel_*() for a texture_2d texture.
vec4 tex_texel_2d(int tex, ivec2 coord, ivec2 uv_tile)
{
if (tex == 0) return vec4(0);
return texelFetch(material_texture_samplers_2d[tex - 1], coord, 0);
}
// The fragment shader main function evaluating the MDL sub-expression.
void main()
{
// Set number of materials to use according to selected pattern
uint num_materials = bitCount(user_data.material_pattern);
// Assign materials in a checkerboard pattern
uint material_index =
(uint(vTexCoord.x * 4) ^ uint(vTexCoord.y * 4)) % num_materials;
// Change material index according to selected pattern
switch (user_data.material_pattern)
{
case 2u: material_index = 1u; break;
case 4u: material_index = 2u; break;
case 5u: if (material_index == 1u) material_index = 2u; break;
case 6u: material_index += 1u; break;
}
if (material_index > get_mdl_num_mat_subexprs())
material_index = get_mdl_num_mat_subexprs();
// Set MDL material state for state functions in "field" mode
State state = State(
/*normal=*/ vec3(0.0, 0.0, 1.0),
/*geometry_normal=*/ vec3(0.0, 0.0, 1.0),
/*position=*/ vPosition,
/*animation_time=*/ user_data.animation_time,
/*text_coords=*/ vec3[1](vTexCoord),
/*texture_tangent_u=*/ vec3[1](vec3(1.0, 0.0, 0.0)),
/*texture_tangent_v=*/ vec3[1](vec3(0.0, 1.0, 0.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
);
// Evaluate material sub-expression
vec3 res = mdl_mat_subexpr(material_index, state);
// Apply gamma correction and write to output variable
FragColor = vec4(pow(res, vec3(1.0 / 2.2)), 1.0);
}
[Previous] [Up] [Next]