742 lines
38 KiB
C++
742 lines
38 KiB
C++
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 the "License";
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/*
|
|
* High dynamic range rendering, using vulkan.hpp
|
|
*/
|
|
|
|
#include "hpp_hdr.h"
|
|
|
|
HPPHDR::HPPHDR()
|
|
{
|
|
title = "HPP High dynamic range rendering";
|
|
}
|
|
|
|
HPPHDR::~HPPHDR()
|
|
{
|
|
if (has_device() && get_device().get_handle())
|
|
{
|
|
vk::Device device = get_device().get_handle();
|
|
|
|
bloom.destroy(device);
|
|
composition.destroy(device);
|
|
filter_pass.destroy(device);
|
|
models.destroy(device, descriptor_pool);
|
|
offscreen.destroy(device);
|
|
textures.destroy(device);
|
|
}
|
|
}
|
|
|
|
bool HPPHDR::prepare(const vkb::ApplicationOptions &options)
|
|
{
|
|
assert(!prepared);
|
|
if (HPPApiVulkanSample::prepare(options))
|
|
{
|
|
prepare_camera();
|
|
load_assets();
|
|
prepare_uniform_buffers();
|
|
prepare_offscreen_buffer();
|
|
descriptor_pool = create_descriptor_pool();
|
|
prepare_bloom();
|
|
prepare_composition();
|
|
prepare_models();
|
|
build_command_buffers();
|
|
|
|
prepared = true;
|
|
}
|
|
|
|
return prepared;
|
|
}
|
|
|
|
bool HPPHDR::resize(const uint32_t width, const uint32_t height)
|
|
{
|
|
HPPApiVulkanSample::resize(width, height);
|
|
update_uniform_buffers();
|
|
return true;
|
|
}
|
|
|
|
void HPPHDR::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
|
|
{
|
|
// Enable anisotropic filtering if supported
|
|
if (gpu.get_features().samplerAnisotropy)
|
|
{
|
|
gpu.get_mutable_requested_features().samplerAnisotropy = true;
|
|
}
|
|
}
|
|
|
|
void HPPHDR::build_command_buffers()
|
|
{
|
|
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
|
|
|
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
|
{
|
|
vk::CommandBuffer command_buffer = draw_cmd_buffers[i];
|
|
command_buffer.begin(command_buffer_begin_info);
|
|
|
|
{
|
|
/*
|
|
First pass: Render scene to offscreen framebuffer
|
|
*/
|
|
std::array<vk::ClearValue, 3> clear_values = {{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
|
|
vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
|
|
vk::ClearDepthStencilValue{0.0f, 0}}};
|
|
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = offscreen.render_pass,
|
|
.framebuffer = offscreen.framebuffer,
|
|
.renderArea = {{0, 0}, offscreen.extent},
|
|
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
|
.pClearValues = clear_values.data()};
|
|
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
|
|
|
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(offscreen.extent.width), static_cast<float>(offscreen.extent.height), 0.0f, 1.0f};
|
|
command_buffer.setViewport(0, viewport);
|
|
|
|
vk::Rect2D scissor{{0, 0}, offscreen.extent};
|
|
command_buffer.setScissor(0, scissor);
|
|
|
|
// Skybox
|
|
if (display_skybox)
|
|
{
|
|
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, models.skybox.pipeline);
|
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, models.pipeline_layout, 0, models.skybox.descriptor_set, {});
|
|
draw_model(models.skybox.meshes[0], command_buffer);
|
|
}
|
|
|
|
// 3D object
|
|
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, models.objects.pipeline);
|
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, models.pipeline_layout, 0, models.objects.descriptor_set, {});
|
|
draw_model(models.objects.meshes[models.object_index], command_buffer);
|
|
|
|
command_buffer.endRenderPass();
|
|
}
|
|
|
|
/*
|
|
Second render pass: First bloom pass
|
|
*/
|
|
if (bloom.enabled)
|
|
{
|
|
// Bloom filter
|
|
vk::ClearValue clear_value(vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})));
|
|
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = filter_pass.render_pass,
|
|
.framebuffer = filter_pass.framebuffer,
|
|
.renderArea = {{0, 0}, filter_pass.extent},
|
|
.clearValueCount = 1,
|
|
.pClearValues = &clear_value};
|
|
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
|
|
|
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(filter_pass.extent.width), static_cast<float>(filter_pass.extent.height), 0.0f, 1.0f};
|
|
command_buffer.setViewport(0, viewport);
|
|
|
|
vk::Rect2D scissor{{0, 0}, filter_pass.extent};
|
|
command_buffer.setScissor(0, scissor);
|
|
|
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, bloom.pipeline_layout, 0, bloom.descriptor_set, {});
|
|
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, bloom.pipelines[1]);
|
|
command_buffer.draw(3, 1, 0, 0);
|
|
|
|
command_buffer.endRenderPass();
|
|
}
|
|
|
|
/*
|
|
Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
|
|
*/
|
|
|
|
/*
|
|
Third render pass: Scene rendering with applied second bloom pass (when enabled)
|
|
*/
|
|
{
|
|
// Final composition
|
|
std::array<vk::ClearValue, 2> clear_values = {{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
|
|
vk::ClearDepthStencilValue{0.0f, 0}}};
|
|
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
|
.framebuffer = framebuffers[i],
|
|
.renderArea = {{0, 0}, extent},
|
|
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
|
.pClearValues = clear_values.data()};
|
|
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
|
|
|
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f};
|
|
command_buffer.setViewport(0, viewport);
|
|
|
|
vk::Rect2D scissor{{0, 0}, extent};
|
|
command_buffer.setScissor(0, scissor);
|
|
|
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, composition.pipeline_layout, 0, composition.descriptor_set, {});
|
|
|
|
// Scene
|
|
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, composition.pipeline);
|
|
command_buffer.draw(3, 1, 0, 0);
|
|
|
|
// Bloom
|
|
if (bloom.enabled)
|
|
{
|
|
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, bloom.pipelines[0]);
|
|
command_buffer.draw(3, 1, 0, 0);
|
|
}
|
|
|
|
draw_ui(command_buffer);
|
|
|
|
command_buffer.endRenderPass();
|
|
}
|
|
|
|
command_buffer.end();
|
|
}
|
|
}
|
|
|
|
void HPPHDR::on_update_ui_overlay(vkb::Drawer &drawer)
|
|
{
|
|
if (drawer.header("Settings"))
|
|
{
|
|
if (drawer.combo_box("Object type", &models.object_index, object_names))
|
|
{
|
|
update_uniform_buffers();
|
|
rebuild_command_buffers();
|
|
}
|
|
if (drawer.input_float("Exposure", &ubo_params.exposure, 0.025f, "%.3f"))
|
|
{
|
|
update_params();
|
|
}
|
|
if (drawer.checkbox("Bloom", &bloom.enabled))
|
|
{
|
|
rebuild_command_buffers();
|
|
}
|
|
if (drawer.checkbox("Skybox", &display_skybox))
|
|
{
|
|
rebuild_command_buffers();
|
|
}
|
|
}
|
|
}
|
|
|
|
void HPPHDR::render(float delta_time)
|
|
{
|
|
if (prepared)
|
|
{
|
|
draw();
|
|
if (camera.updated)
|
|
{
|
|
update_uniform_buffers();
|
|
}
|
|
}
|
|
}
|
|
|
|
vk::DeviceMemory HPPHDR::allocate_memory(vk::Image image)
|
|
{
|
|
vk::MemoryRequirements memory_requirements = get_device().get_handle().getImageMemoryRequirements(image);
|
|
|
|
vk::MemoryAllocateInfo memory_allocate_info{.allocationSize = memory_requirements.size,
|
|
.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits,
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal)};
|
|
|
|
return get_device().get_handle().allocateMemory(memory_allocate_info);
|
|
}
|
|
|
|
HPPHDR::FramebufferAttachment HPPHDR::create_attachment(vk::Format format, vk::ImageUsageFlagBits usage)
|
|
{
|
|
vk::Image image = create_image(format, usage);
|
|
vk::DeviceMemory memory = allocate_memory(image);
|
|
get_device().get_handle().bindImageMemory(image, memory, 0);
|
|
vk::ImageView view =
|
|
vkb::common::create_image_view(get_device().get_handle(), image, vk::ImageViewType::e2D, format, vkb::common::get_image_aspect_flags(usage, format));
|
|
|
|
return {format, image, memory, view};
|
|
}
|
|
|
|
vk::DescriptorPool HPPHDR::create_descriptor_pool()
|
|
{
|
|
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 4}, {vk::DescriptorType::eCombinedImageSampler, 6}}};
|
|
return get_device().get_handle().createDescriptorPool(
|
|
{.maxSets = 4, .poolSizeCount = static_cast<uint32_t>(pool_sizes.size()), .pPoolSizes = pool_sizes.data()});
|
|
}
|
|
|
|
vk::Pipeline HPPHDR::create_bloom_pipeline(uint32_t direction)
|
|
{
|
|
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{load_shader("hdr", "bloom.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
|
load_shader("hdr", "bloom.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
|
|
|
// Set constant parameters via specialization constants
|
|
vk::SpecializationMapEntry specialization_map_entry{0, 0, sizeof(uint32_t)};
|
|
|
|
vk::SpecializationInfo specialization_info{1, &specialization_map_entry, sizeof(uint32_t), &direction};
|
|
shader_stages[1].pSpecializationInfo = &specialization_info;
|
|
|
|
vk::PipelineColorBlendAttachmentState blend_attachment_state{.blendEnable = true,
|
|
.srcColorBlendFactor = vk::BlendFactor::eOne,
|
|
.dstColorBlendFactor = vk::BlendFactor::eOne,
|
|
.colorBlendOp = vk::BlendOp::eAdd,
|
|
.srcAlphaBlendFactor = vk::BlendFactor::eSrcAlpha,
|
|
.dstAlphaBlendFactor = vk::BlendFactor::eDstAlpha,
|
|
.alphaBlendOp = vk::BlendOp::eAdd,
|
|
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
|
|
|
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
|
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
|
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
|
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
|
depth_stencil_state.front = depth_stencil_state.back;
|
|
|
|
// Empty vertex input state, full screen triangles are generated by the vertex shader
|
|
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
|
pipeline_cache,
|
|
shader_stages,
|
|
{},
|
|
vk::PrimitiveTopology::eTriangleList,
|
|
0,
|
|
vk::PolygonMode::eFill,
|
|
vk::CullModeFlagBits::eFront,
|
|
vk::FrontFace::eCounterClockwise,
|
|
{blend_attachment_state},
|
|
depth_stencil_state,
|
|
bloom.pipeline_layout,
|
|
direction == 1 ? render_pass : filter_pass.render_pass);
|
|
}
|
|
|
|
vk::Pipeline HPPHDR::create_composition_pipeline()
|
|
{
|
|
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{load_shader("hdr", "composition.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
|
load_shader("hdr", "composition.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
|
|
|
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
|
|
|
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
|
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
|
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
|
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
|
depth_stencil_state.front = depth_stencil_state.back;
|
|
|
|
// Empty vertex input state, full screen triangles are generated by the vertex shader
|
|
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
|
pipeline_cache,
|
|
shader_stages,
|
|
{},
|
|
vk::PrimitiveTopology::eTriangleList,
|
|
0,
|
|
vk::PolygonMode::eFill,
|
|
vk::CullModeFlagBits::eFront,
|
|
vk::FrontFace::eCounterClockwise,
|
|
{blend_attachment_state},
|
|
depth_stencil_state,
|
|
composition.pipeline_layout,
|
|
render_pass);
|
|
}
|
|
|
|
vk::RenderPass HPPHDR::create_filter_render_pass()
|
|
{
|
|
// Set up separate renderpass with references to the color and depth attachments
|
|
vk::AttachmentDescription attachment_description{.format = filter_pass.color.format,
|
|
.samples = vk::SampleCountFlagBits::e1,
|
|
.loadOp = vk::AttachmentLoadOp::eClear,
|
|
.storeOp = vk::AttachmentStoreOp::eStore,
|
|
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
|
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
|
.initialLayout = vk::ImageLayout::eUndefined,
|
|
.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal};
|
|
|
|
vk::AttachmentReference color_reference{0, vk::ImageLayout::eColorAttachmentOptimal};
|
|
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics, .colorAttachmentCount = 1, .pColorAttachments = &color_reference};
|
|
|
|
return create_render_pass({attachment_description}, subpass);
|
|
}
|
|
|
|
vk::Image HPPHDR::create_image(vk::Format format, vk::ImageUsageFlagBits usage)
|
|
{
|
|
vk::ImageCreateInfo image_create_info{.imageType = vk::ImageType::e2D,
|
|
.format = format,
|
|
.extent = {offscreen.extent.width, offscreen.extent.height, 1},
|
|
.mipLevels = 1,
|
|
.arrayLayers = 1,
|
|
.samples = vk::SampleCountFlagBits::e1,
|
|
.tiling = vk::ImageTiling::eOptimal,
|
|
.usage = usage | vk::ImageUsageFlagBits::eSampled};
|
|
|
|
return get_device().get_handle().createImage(image_create_info);
|
|
}
|
|
|
|
vk::Pipeline HPPHDR::create_models_pipeline(uint32_t shaderType, vk::CullModeFlagBits cullMode, bool depthTestAndWrite)
|
|
{
|
|
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{load_shader("hdr", "gbuffer.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
|
load_shader("hdr", "gbuffer.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
|
|
|
// Set constant parameters via specialization constants
|
|
vk::SpecializationMapEntry specialization_map_entry{0, 0, sizeof(uint32_t)};
|
|
|
|
// Set constant parameters via specialization constants
|
|
vk::SpecializationInfo specialization_info{1, &specialization_map_entry, sizeof(uint32_t), &shaderType};
|
|
shader_stages[0].pSpecializationInfo = &specialization_info;
|
|
shader_stages[1].pSpecializationInfo = &specialization_info;
|
|
|
|
// Vertex bindings an attributes for model rendering
|
|
// Binding description
|
|
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
|
|
|
// Attribute descriptions
|
|
std::vector<vk::VertexInputAttributeDescription> vertex_input_attributes = {{0, 0, vk::Format::eR32G32B32Sfloat, 0},
|
|
{1, 0, vk::Format::eR32G32B32Sfloat, 3 * sizeof(float)}};
|
|
|
|
vk::PipelineVertexInputStateCreateInfo vertex_input_state{.vertexBindingDescriptionCount = 1,
|
|
.pVertexBindingDescriptions = &vertex_input_binding,
|
|
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size()),
|
|
.pVertexAttributeDescriptions = vertex_input_attributes.data()};
|
|
|
|
std::vector<vk::PipelineColorBlendAttachmentState> blend_attachment_states(2);
|
|
blend_attachment_states[0].colorWriteMask =
|
|
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
|
blend_attachment_states[1].colorWriteMask = blend_attachment_states[0].colorWriteMask;
|
|
|
|
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
|
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
|
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
|
depth_stencil_state.depthWriteEnable = depthTestAndWrite;
|
|
depth_stencil_state.depthTestEnable = depthTestAndWrite;
|
|
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
|
depth_stencil_state.front = depth_stencil_state.back;
|
|
|
|
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
|
pipeline_cache,
|
|
shader_stages,
|
|
vertex_input_state,
|
|
vk::PrimitiveTopology::eTriangleList,
|
|
0,
|
|
vk::PolygonMode::eFill,
|
|
cullMode,
|
|
vk::FrontFace::eCounterClockwise,
|
|
blend_attachment_states,
|
|
depth_stencil_state,
|
|
models.pipeline_layout,
|
|
offscreen.render_pass);
|
|
}
|
|
|
|
vk::RenderPass HPPHDR::create_offscreen_render_pass()
|
|
{
|
|
// Set up separate renderpass with references to the color and depth attachments
|
|
std::vector<vk::AttachmentDescription> attachment_descriptions(3);
|
|
|
|
// Init attachment properties
|
|
for (uint32_t i = 0; i < 3; ++i)
|
|
{
|
|
attachment_descriptions[i].samples = vk::SampleCountFlagBits::e1;
|
|
attachment_descriptions[i].loadOp = vk::AttachmentLoadOp::eClear;
|
|
attachment_descriptions[i].storeOp = vk::AttachmentStoreOp::eStore;
|
|
attachment_descriptions[i].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
|
attachment_descriptions[i].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
|
attachment_descriptions[i].initialLayout = vk::ImageLayout::eUndefined;
|
|
attachment_descriptions[i].finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
|
}
|
|
attachment_descriptions[2].finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
|
|
|
// Formats
|
|
attachment_descriptions[0].format = offscreen.color[0].format;
|
|
attachment_descriptions[1].format = offscreen.color[1].format;
|
|
attachment_descriptions[2].format = offscreen.depth.format;
|
|
|
|
std::array<vk::AttachmentReference, 2> color_references{{{0, vk::ImageLayout::eColorAttachmentOptimal},
|
|
{1, vk::ImageLayout::eColorAttachmentOptimal}}};
|
|
|
|
vk::AttachmentReference depth_reference{2, vk::ImageLayout::eDepthStencilAttachmentOptimal};
|
|
|
|
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
|
.colorAttachmentCount = static_cast<uint32_t>(color_references.size()),
|
|
.pColorAttachments = color_references.data(),
|
|
.pDepthStencilAttachment = &depth_reference};
|
|
|
|
return create_render_pass(attachment_descriptions, subpass);
|
|
}
|
|
|
|
vk::RenderPass HPPHDR::create_render_pass(std::vector<vk::AttachmentDescription> const &attachment_descriptions, vk::SubpassDescription const &subpass_description)
|
|
{
|
|
// Use subpass dependencies for attachment layout transitions
|
|
std::array<vk::SubpassDependency, 2> subpass_dependencies;
|
|
|
|
subpass_dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
subpass_dependencies[0].dstSubpass = 0;
|
|
// End of previous commands
|
|
subpass_dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eBottomOfPipe;
|
|
subpass_dependencies[0].srcAccessMask = vk::AccessFlagBits::eNoneKHR;
|
|
// Read/write from/to depth
|
|
subpass_dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests;
|
|
subpass_dependencies[0].dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
|
// Write to attachment
|
|
subpass_dependencies[0].dstStageMask |= vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
subpass_dependencies[0].dstAccessMask |= vk::AccessFlagBits::eColorAttachmentWrite;
|
|
|
|
subpass_dependencies[1].srcSubpass = 0;
|
|
subpass_dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
// End of write to attachment
|
|
subpass_dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
subpass_dependencies[1].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
|
// Attachment later read using sampler in 'bloom[0]' pipeline
|
|
subpass_dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eFragmentShader;
|
|
subpass_dependencies[1].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
|
|
|
vk::RenderPassCreateInfo render_pass_create_info{.attachmentCount = static_cast<uint32_t>(attachment_descriptions.size()),
|
|
.pAttachments = attachment_descriptions.data(),
|
|
.subpassCount = 1,
|
|
.pSubpasses = &subpass_description,
|
|
.dependencyCount = static_cast<uint32_t>(subpass_dependencies.size()),
|
|
.pDependencies = subpass_dependencies.data()};
|
|
|
|
return get_device().get_handle().createRenderPass(render_pass_create_info);
|
|
}
|
|
|
|
void HPPHDR::draw()
|
|
{
|
|
HPPApiVulkanSample::prepare_frame();
|
|
|
|
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
|
queue.submit(submit_info);
|
|
|
|
HPPApiVulkanSample::submit_frame();
|
|
}
|
|
|
|
void HPPHDR::load_assets()
|
|
{
|
|
// Models
|
|
models.skybox.meshes.emplace_back(load_model("scenes/cube.gltf"));
|
|
std::vector<std::string> filenames = {"geosphere.gltf", "teapot.gltf", "torusknot.gltf"};
|
|
object_names = {"Sphere", "Teapot", "Torusknot"};
|
|
for (auto file : filenames)
|
|
{
|
|
models.objects.meshes.emplace_back(load_model("scenes/" + file));
|
|
}
|
|
|
|
// Transforms
|
|
auto geosphere_matrix = glm::mat4(1.0f);
|
|
models.transforms.push_back(geosphere_matrix);
|
|
|
|
auto teapot_matrix = glm::mat4(1.0f);
|
|
teapot_matrix = glm::scale(teapot_matrix, glm::vec3(10.0f, 10.0f, 10.0f));
|
|
teapot_matrix = glm::rotate(teapot_matrix, glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f));
|
|
models.transforms.push_back(teapot_matrix);
|
|
|
|
auto torus_matrix = glm::mat4(1.0f);
|
|
models.transforms.push_back(torus_matrix);
|
|
|
|
// Load HDR cube map
|
|
textures.envmap = load_texture_cubemap("textures/uffizi_rgba16f_cube.ktx", vkb::scene_graph::components::HPPImage::Color);
|
|
}
|
|
|
|
void HPPHDR::prepare_bloom()
|
|
{
|
|
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
|
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
|
|
|
vk::Device device = get_device().get_handle();
|
|
bloom.descriptor_set_layout = device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
|
bloom.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &bloom.descriptor_set_layout});
|
|
bloom.pipelines[0] = create_bloom_pipeline(1);
|
|
bloom.pipelines[1] = create_bloom_pipeline(0);
|
|
bloom.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, bloom.descriptor_set_layout);
|
|
update_bloom_descriptor_set();
|
|
}
|
|
|
|
void HPPHDR::prepare_camera()
|
|
{
|
|
camera.type = vkb::CameraType::LookAt;
|
|
camera.set_position(glm::vec3(0.0f, 0.0f, -4.0f));
|
|
camera.set_rotation(glm::vec3(0.0f, 180.0f, 0.0f));
|
|
|
|
// Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped
|
|
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 256.0f, 0.1f);
|
|
}
|
|
|
|
void HPPHDR::prepare_composition()
|
|
{
|
|
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
|
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
|
|
|
vk::Device device = get_device().get_handle();
|
|
composition.descriptor_set_layout =
|
|
device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
|
composition.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &composition.descriptor_set_layout});
|
|
composition.pipeline = create_composition_pipeline();
|
|
composition.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, composition.descriptor_set_layout);
|
|
update_composition_descriptor_set();
|
|
}
|
|
|
|
void HPPHDR::prepare_models()
|
|
{
|
|
std::array<vk::DescriptorSetLayoutBinding, 3> bindings = {{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
|
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
|
{2, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment}}};
|
|
|
|
vk::Device device = get_device().get_handle();
|
|
models.descriptor_set_layout = device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
|
models.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &models.descriptor_set_layout});
|
|
|
|
models.objects.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, models.descriptor_set_layout);
|
|
update_model_descriptor_set(models.objects.descriptor_set);
|
|
models.objects.pipeline = create_models_pipeline(1, vk::CullModeFlagBits::eFront, true);
|
|
|
|
models.skybox.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, models.descriptor_set_layout);
|
|
update_model_descriptor_set(models.skybox.descriptor_set);
|
|
models.skybox.pipeline = create_models_pipeline(0, vk::CullModeFlagBits::eBack, false);
|
|
}
|
|
|
|
// Prepare a new framebuffer and attachments for offscreen rendering (G-Buffer)
|
|
void HPPHDR::prepare_offscreen_buffer()
|
|
{
|
|
// We need to select a format that supports the color attachment blending flag, so we iterate over multiple formats to find one that supports this flag
|
|
const std::vector<vk::Format> float_format_priority_list = {
|
|
vk::Format::eR32G32B32A32Sfloat,
|
|
vk::Format::eR16G16B16A16Sfloat // Guaranteed blend support for this
|
|
};
|
|
|
|
vk::Format color_format = vkb::common::choose_blendable_format(get_device().get_gpu().get_handle(), float_format_priority_list);
|
|
|
|
{
|
|
offscreen.extent = extent;
|
|
|
|
// Color attachments
|
|
|
|
// We are using two 128-Bit RGBA floating point color buffers for this sample
|
|
// In a performance or bandwidth-limited scenario you should consider using a format with lower precision
|
|
offscreen.color[0] = create_attachment(color_format, vk::ImageUsageFlagBits::eColorAttachment);
|
|
offscreen.color[1] = create_attachment(color_format, vk::ImageUsageFlagBits::eColorAttachment);
|
|
// Depth attachment
|
|
offscreen.depth = create_attachment(depth_format, vk::ImageUsageFlagBits::eDepthStencilAttachment);
|
|
|
|
offscreen.render_pass = create_offscreen_render_pass();
|
|
|
|
offscreen.framebuffer = vkb::common::create_framebuffer(
|
|
get_device().get_handle(), offscreen.render_pass, {offscreen.color[0].view, offscreen.color[1].view, offscreen.depth.view}, offscreen.extent);
|
|
|
|
// Create sampler to sample from the color attachments
|
|
offscreen.sampler = vkb::common::create_sampler(get_device().get_gpu().get_handle(), get_device().get_handle(), color_format,
|
|
vk::Filter::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
|
|
}
|
|
|
|
// Bloom separable filter pass
|
|
{
|
|
filter_pass.extent = extent;
|
|
|
|
// Color attachments
|
|
|
|
// Floating point color attachment
|
|
filter_pass.color = create_attachment(color_format, vk::ImageUsageFlagBits::eColorAttachment);
|
|
filter_pass.render_pass = create_filter_render_pass();
|
|
filter_pass.framebuffer = vkb::common::create_framebuffer(get_device().get_handle(), filter_pass.render_pass, {filter_pass.color.view}, filter_pass.extent);
|
|
filter_pass.sampler = vkb::common::create_sampler(get_device().get_gpu().get_handle(), get_device().get_handle(),
|
|
color_format, vk::Filter::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
// Prepare and initialize uniform buffer containing shader uniforms
|
|
void HPPHDR::prepare_uniform_buffers()
|
|
{
|
|
// Matrices vertex shader uniform buffer
|
|
uniform_buffers.matrices = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
|
sizeof(ubo_matrices),
|
|
vk::BufferUsageFlagBits::eUniformBuffer,
|
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
|
|
// Params
|
|
uniform_buffers.params = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
|
sizeof(ubo_params),
|
|
vk::BufferUsageFlagBits::eUniformBuffer,
|
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
|
|
update_uniform_buffers();
|
|
update_params();
|
|
}
|
|
|
|
void HPPHDR::update_composition_descriptor_set()
|
|
{
|
|
std::array<vk::DescriptorImageInfo, 2> color_descriptors = {{{offscreen.sampler, offscreen.color[0].view, vk::ImageLayout::eShaderReadOnlyOptimal},
|
|
{offscreen.sampler, filter_pass.color.view, vk::ImageLayout::eShaderReadOnlyOptimal}}};
|
|
|
|
std::array<vk::WriteDescriptorSet, 2> sampler_write_descriptor_sets = {{{.dstSet = composition.descriptor_set,
|
|
.dstBinding = 0,
|
|
.descriptorCount = 1,
|
|
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
.pImageInfo = &color_descriptors[0]},
|
|
{.dstSet = composition.descriptor_set,
|
|
.dstBinding = 1,
|
|
.descriptorCount = 1,
|
|
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
.pImageInfo = &color_descriptors[1]}}};
|
|
|
|
get_device().get_handle().updateDescriptorSets(sampler_write_descriptor_sets, {});
|
|
}
|
|
|
|
void HPPHDR::update_bloom_descriptor_set()
|
|
{
|
|
std::array<vk::DescriptorImageInfo, 2> color_descriptors = {{{offscreen.sampler, offscreen.color[0].view, vk::ImageLayout::eShaderReadOnlyOptimal},
|
|
{offscreen.sampler, offscreen.color[1].view, vk::ImageLayout::eShaderReadOnlyOptimal}}};
|
|
|
|
std::array<vk::WriteDescriptorSet, 2> sampler_write_descriptor_sets = {{{.dstSet = bloom.descriptor_set,
|
|
.dstBinding = 0,
|
|
.descriptorCount = 1,
|
|
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
.pImageInfo = &color_descriptors[0]},
|
|
{.dstSet = bloom.descriptor_set,
|
|
.dstBinding = 1,
|
|
.descriptorCount = 1,
|
|
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
.pImageInfo = &color_descriptors[1]}}};
|
|
|
|
get_device().get_handle().updateDescriptorSets(sampler_write_descriptor_sets, {});
|
|
}
|
|
|
|
void HPPHDR::update_model_descriptor_set(vk::DescriptorSet descriptor_set)
|
|
{
|
|
vk::DescriptorBufferInfo matrix_buffer_descriptor{uniform_buffers.matrices->get_handle(), 0, vk::WholeSize};
|
|
|
|
vk::DescriptorImageInfo environment_image_descriptor{textures.envmap.sampler,
|
|
textures.envmap.image->get_vk_image_view().get_handle(),
|
|
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
|
textures.envmap.image->get_vk_image_view().get_format())};
|
|
|
|
vk::DescriptorBufferInfo params_buffer_descriptor{uniform_buffers.params->get_handle(), 0, vk::WholeSize};
|
|
|
|
std::array<vk::WriteDescriptorSet, 3> write_descriptor_sets = {{{.dstSet = descriptor_set,
|
|
.dstBinding = 0,
|
|
.descriptorCount = 1,
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
|
.pBufferInfo = &matrix_buffer_descriptor},
|
|
{.dstSet = descriptor_set,
|
|
.dstBinding = 1,
|
|
.descriptorCount = 1,
|
|
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
.pImageInfo = &environment_image_descriptor},
|
|
{.dstSet = descriptor_set,
|
|
.dstBinding = 2,
|
|
.descriptorCount = 1,
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
|
.pBufferInfo = ¶ms_buffer_descriptor}}};
|
|
|
|
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
|
}
|
|
|
|
void HPPHDR::update_params()
|
|
{
|
|
uniform_buffers.params->convert_and_update(ubo_params);
|
|
}
|
|
|
|
void HPPHDR::update_uniform_buffers()
|
|
{
|
|
ubo_matrices.projection = camera.matrices.perspective;
|
|
ubo_matrices.modelview = camera.matrices.view * models.transforms[models.object_index];
|
|
ubo_matrices.skybox_modelview = camera.matrices.view;
|
|
ubo_matrices.inverse_modelview = glm::inverse(camera.matrices.view);
|
|
uniform_buffers.matrices->convert_and_update(ubo_matrices);
|
|
}
|
|
|
|
std::unique_ptr<vkb::Application> create_hpp_hdr()
|
|
{
|
|
return std::make_unique<HPPHDR>();
|
|
}
|