remove other sample and change apk to inewme
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2019-2024, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Compute N-Body simulation"
|
||||
DESCRIPTION "Multi-pass compute dispatch N-Body particle simulation"
|
||||
SHADER_FILES_GLSL
|
||||
"compute_nbody/glsl/particle.vert"
|
||||
"compute_nbody/glsl/particle.frag"
|
||||
"compute_nbody/glsl/particle_calculate.comp"
|
||||
"compute_nbody/glsl/particle_integrate.comp"
|
||||
SHADER_FILES_HLSL
|
||||
"compute_nbody/hlsl/particle.vert.hlsl"
|
||||
"compute_nbody/hlsl/particle.frag.hlsl"
|
||||
"compute_nbody/hlsl/particle_calculate.comp.hlsl"
|
||||
"compute_nbody/hlsl/particle_integrate.comp.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Compute shader N-Body simulation
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/compute_nbody[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
Compute shader example that uses two passes and shared compute shader memory for simulating a N-Body particle system.
|
||||
@@ -1,880 +0,0 @@
|
||||
/* Copyright (c) 2019-2025, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Compute shader N-body simulation using two passes and shared compute shader memory
|
||||
*/
|
||||
|
||||
#include "compute_nbody.h"
|
||||
|
||||
#include "benchmark_mode/benchmark_mode.h"
|
||||
|
||||
ComputeNBody::ComputeNBody()
|
||||
{
|
||||
title = "Compute shader N-body system";
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Z-Near and Z-Far are flipped
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 512.0f, 0.1f);
|
||||
camera.set_rotation(glm::vec3(-26.0f, 75.0f, 0.0f));
|
||||
camera.set_translation(glm::vec3(0.0f, 0.0f, -14.0f));
|
||||
camera.translation_speed = 2.5f;
|
||||
}
|
||||
|
||||
ComputeNBody::~ComputeNBody()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
// Graphics
|
||||
graphics.uniform_buffer.reset();
|
||||
vkDestroyPipeline(get_device().get_handle(), graphics.pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), graphics.pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), graphics.descriptor_set_layout, nullptr);
|
||||
vkDestroySemaphore(get_device().get_handle(), graphics.semaphore, nullptr);
|
||||
|
||||
// Compute
|
||||
compute.storage_buffer.reset();
|
||||
compute.uniform_buffer.reset();
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), compute.pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), compute.descriptor_set_layout, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), compute.pipeline_calculate, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), compute.pipeline_integrate, nullptr);
|
||||
vkDestroySemaphore(get_device().get_handle(), compute.semaphore, nullptr);
|
||||
vkDestroyCommandPool(get_device().get_handle(), compute.command_pool, nullptr);
|
||||
|
||||
vkDestroySampler(get_device().get_handle(), textures.particle.sampler, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), textures.gradient.sampler, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeNBody::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeNBody::load_assets()
|
||||
{
|
||||
textures.particle = load_texture("textures/particle_rgba.ktx", vkb::sg::Image::Color);
|
||||
textures.gradient = load_texture("textures/particle_gradient_rgba.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
void ComputeNBody::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
// Set target frame buffer
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
// Acquire
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
VkBufferMemoryBarrier buffer_barrier =
|
||||
{
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
0,
|
||||
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
|
||||
compute.queue_family_index,
|
||||
graphics.queue_family_index,
|
||||
compute.storage_buffer->get_handle(),
|
||||
0,
|
||||
compute.storage_buffer->get_size()};
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
draw_cmd_buffers[i],
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &buffer_barrier,
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
// Draw the particle system using the update vertex buffer
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline_layout, 0, 1, &graphics.descriptor_set, 0, NULL);
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, compute.storage_buffer->get(), offsets);
|
||||
vkCmdDraw(draw_cmd_buffers[i], num_particles, 1, 0, 0);
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
// Release barrier
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
VkBufferMemoryBarrier buffer_barrier =
|
||||
{
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
|
||||
0,
|
||||
graphics.queue_family_index,
|
||||
compute.queue_family_index,
|
||||
compute.storage_buffer->get_handle(),
|
||||
0,
|
||||
compute.storage_buffer->get_size()};
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
draw_cmd_buffers[i],
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &buffer_barrier,
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeNBody::build_compute_command_buffer()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(compute.command_buffer, &command_buffer_begin_info));
|
||||
|
||||
// Acquire
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
VkBufferMemoryBarrier buffer_barrier =
|
||||
{
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
0,
|
||||
VK_ACCESS_SHADER_WRITE_BIT,
|
||||
graphics.queue_family_index,
|
||||
compute.queue_family_index,
|
||||
compute.storage_buffer->get_handle(),
|
||||
0,
|
||||
compute.storage_buffer->get_size()};
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
compute.command_buffer,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &buffer_barrier,
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
// First pass: Calculate particle movement
|
||||
// -------------------------------------------------------------------------------------------------------
|
||||
vkCmdBindPipeline(compute.command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline_calculate);
|
||||
vkCmdBindDescriptorSets(compute.command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline_layout, 0, 1, &compute.descriptor_set, 0, 0);
|
||||
vkCmdDispatch(compute.command_buffer, num_particles / work_group_size, 1, 1);
|
||||
|
||||
// Add memory barrier to ensure that the computer shader has finished writing to the buffer
|
||||
VkBufferMemoryBarrier memory_barrier = vkb::initializers::buffer_memory_barrier();
|
||||
memory_barrier.buffer = compute.storage_buffer->get_handle();
|
||||
memory_barrier.size = compute.storage_buffer->get_size();
|
||||
memory_barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
compute.command_buffer,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_FLAGS_NONE,
|
||||
0, nullptr,
|
||||
1, &memory_barrier,
|
||||
0, nullptr);
|
||||
|
||||
// Second pass: Integrate particles
|
||||
// -------------------------------------------------------------------------------------------------------
|
||||
vkCmdBindPipeline(compute.command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline_integrate);
|
||||
vkCmdDispatch(compute.command_buffer, num_particles / work_group_size, 1, 1);
|
||||
|
||||
// Release
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
VkBufferMemoryBarrier buffer_barrier =
|
||||
{
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
VK_ACCESS_SHADER_WRITE_BIT,
|
||||
0,
|
||||
compute.queue_family_index,
|
||||
graphics.queue_family_index,
|
||||
compute.storage_buffer->get_handle(),
|
||||
0,
|
||||
compute.storage_buffer->get_size()};
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
compute.command_buffer,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &buffer_barrier,
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
vkEndCommandBuffer(compute.command_buffer);
|
||||
}
|
||||
|
||||
// Setup and fill the compute shader storage buffers containing the particles
|
||||
void ComputeNBody::prepare_storage_buffers()
|
||||
{
|
||||
#if 0
|
||||
std::vector<glm::vec3> attractors = {
|
||||
glm::vec3(2.5f, 1.5f, 0.0f),
|
||||
glm::vec3(-2.5f, -1.5f, 0.0f),
|
||||
};
|
||||
#else
|
||||
std::vector<glm::vec3> attractors = {
|
||||
glm::vec3(5.0f, 0.0f, 0.0f),
|
||||
glm::vec3(-5.0f, 0.0f, 0.0f),
|
||||
glm::vec3(0.0f, 0.0f, 5.0f),
|
||||
glm::vec3(0.0f, 0.0f, -5.0f),
|
||||
glm::vec3(0.0f, 4.0f, 0.0f),
|
||||
glm::vec3(0.0f, -8.0f, 0.0f),
|
||||
};
|
||||
#endif
|
||||
|
||||
num_particles = static_cast<uint32_t>(attractors.size()) * PARTICLES_PER_ATTRACTOR;
|
||||
|
||||
// Initial particle positions
|
||||
std::vector<Particle> particle_buffer(num_particles);
|
||||
|
||||
std::default_random_engine rnd_engine(lock_simulation_speed ? 0 : static_cast<unsigned>(time(nullptr)));
|
||||
std::normal_distribution<float> rnd_distribution(0.0f, 1.0f);
|
||||
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(attractors.size()); i++)
|
||||
{
|
||||
for (uint32_t j = 0; j < PARTICLES_PER_ATTRACTOR; j++)
|
||||
{
|
||||
Particle &particle = particle_buffer[i * PARTICLES_PER_ATTRACTOR + j];
|
||||
|
||||
// First particle in group as heavy center of gravity
|
||||
if (j == 0)
|
||||
{
|
||||
particle.pos = glm::vec4(attractors[i] * 1.5f, 90000.0f);
|
||||
particle.vel = glm::vec4(glm::vec4(0.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Position
|
||||
glm::vec3 position(attractors[i] + glm::vec3(rnd_distribution(rnd_engine), rnd_distribution(rnd_engine), rnd_distribution(rnd_engine)) * 0.75f);
|
||||
float len = glm::length(glm::normalize(position - attractors[i]));
|
||||
position.y *= 2.0f - (len * len);
|
||||
|
||||
// Velocity
|
||||
glm::vec3 angular = glm::vec3(0.5f, 1.5f, 0.5f) * (((i % 2) == 0) ? 1.0f : -1.0f);
|
||||
glm::vec3 velocity = glm::cross((position - attractors[i]), angular) + glm::vec3(rnd_distribution(rnd_engine), rnd_distribution(rnd_engine), rnd_distribution(rnd_engine) * 0.025f);
|
||||
|
||||
float mass = (rnd_distribution(rnd_engine) * 0.5f + 0.5f) * 75.0f;
|
||||
particle.pos = glm::vec4(position, mass);
|
||||
particle.vel = glm::vec4(velocity, 0.0f);
|
||||
}
|
||||
|
||||
// Color gradient offset
|
||||
particle.vel.w = static_cast<float>(i) * 1.0f / static_cast<uint32_t>(attractors.size());
|
||||
}
|
||||
}
|
||||
|
||||
compute.ubo.particle_count = num_particles;
|
||||
|
||||
VkDeviceSize storage_buffer_size = particle_buffer.size() * sizeof(Particle);
|
||||
|
||||
// Staging
|
||||
// SSBO won't be changed on the host after upload so copy to device local memory
|
||||
vkb::core::BufferC staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), particle_buffer);
|
||||
|
||||
compute.storage_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
storage_buffer_size,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
// Copy from staging buffer to storage buffer
|
||||
VkCommandBuffer copy_command = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
VkBufferCopy copy_region = {};
|
||||
copy_region.size = storage_buffer_size;
|
||||
vkCmdCopyBuffer(copy_command, staging_buffer.get_handle(), compute.storage_buffer->get_handle(), 1, ©_region);
|
||||
// Execute a transfer to the compute queue, if necessary
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
VkBufferMemoryBarrier buffer_barrier =
|
||||
{
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
|
||||
0,
|
||||
graphics.queue_family_index,
|
||||
compute.queue_family_index,
|
||||
compute.storage_buffer->get_handle(),
|
||||
0,
|
||||
compute.storage_buffer->get_size()};
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
copy_command,
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &buffer_barrier,
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
}
|
||||
|
||||
void ComputeNBody::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes =
|
||||
{
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
2);
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void ComputeNBody::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings;
|
||||
set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 2),
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout, nullptr, &graphics.descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&graphics.descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &graphics.pipeline_layout));
|
||||
}
|
||||
|
||||
void ComputeNBody::setup_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&graphics.descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &graphics.descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*graphics.uniform_buffer);
|
||||
VkDescriptorImageInfo particle_image_descriptor = create_descriptor(textures.particle);
|
||||
VkDescriptorImageInfo gradient_image_descriptor = create_descriptor(textures.gradient);
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets;
|
||||
write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(graphics.descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &particle_image_descriptor),
|
||||
vkb::initializers::write_descriptor_set(graphics.descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &gradient_image_descriptor),
|
||||
vkb::initializers::write_descriptor_set(graphics.descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &buffer_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
void ComputeNBody::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_NONE,
|
||||
VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_FALSE,
|
||||
VK_FALSE,
|
||||
VK_COMPARE_OP_ALWAYS);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamicState =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
// Rendering pipeline
|
||||
// Load shaders
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
|
||||
shader_stages[0] = load_shader("compute_nbody", "particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("compute_nbody", "particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex bindings and attributes
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, pos)),
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, vel))};
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(
|
||||
graphics.pipeline_layout,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamicState;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
pipeline_create_info.renderPass = render_pass;
|
||||
|
||||
// Additive blending
|
||||
blend_attachment_state.colorWriteMask = 0xF;
|
||||
blend_attachment_state.blendEnable = VK_TRUE;
|
||||
blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &graphics.pipeline));
|
||||
}
|
||||
|
||||
void ComputeNBody::prepare_graphics()
|
||||
{
|
||||
prepare_storage_buffers();
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_set();
|
||||
|
||||
// Semaphore for compute & graphics sync
|
||||
VkSemaphoreCreateInfo semaphore_create_info = vkb::initializers::semaphore_create_info();
|
||||
VK_CHECK(vkCreateSemaphore(get_device().get_handle(), &semaphore_create_info, nullptr, &graphics.semaphore));
|
||||
}
|
||||
|
||||
void ComputeNBody::prepare_compute()
|
||||
{
|
||||
// Get compute queue
|
||||
vkGetDeviceQueue(get_device().get_handle(), compute.queue_family_index, 0, &compute.queue);
|
||||
|
||||
// Create compute pipeline
|
||||
// Compute pipelines are created separate from graphics pipelines even if they use the same queue (family index)
|
||||
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings = {
|
||||
// Binding 0 : Particle position storage buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
0),
|
||||
// Binding 1 : Uniform buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
1),
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout, nullptr, &compute.descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&compute.descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &compute.pipeline_layout));
|
||||
|
||||
VkDescriptorSetAllocateInfo alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&compute.descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &compute.descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo storage_buffer_descriptor = create_descriptor(*compute.storage_buffer);
|
||||
VkDescriptorBufferInfo uniform_buffer_descriptor = create_descriptor(*compute.uniform_buffer);
|
||||
std::vector<VkWriteDescriptorSet> compute_write_descriptor_sets =
|
||||
{
|
||||
// Binding 0 : Particle position storage buffer
|
||||
vkb::initializers::write_descriptor_set(
|
||||
compute.descriptor_set,
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
0,
|
||||
&storage_buffer_descriptor),
|
||||
// Binding 1 : Uniform buffer
|
||||
vkb::initializers::write_descriptor_set(
|
||||
compute.descriptor_set,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
1,
|
||||
&uniform_buffer_descriptor)};
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(compute_write_descriptor_sets.size()), compute_write_descriptor_sets.data(), 0, NULL);
|
||||
|
||||
// Create pipelines
|
||||
VkComputePipelineCreateInfo compute_pipeline_create_info = vkb::initializers::compute_pipeline_create_info(compute.pipeline_layout, 0);
|
||||
|
||||
// 1st pass - Particle movement calculations
|
||||
compute_pipeline_create_info.stage = load_shader("compute_nbody", "particle_calculate.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
// Set some shader parameters via specialization constants
|
||||
struct SpecializationData
|
||||
{
|
||||
uint32_t workgroup_size;
|
||||
uint32_t shared_data_size;
|
||||
float gravity;
|
||||
float power;
|
||||
float soften;
|
||||
} specialization_data;
|
||||
|
||||
std::vector<VkSpecializationMapEntry> specialization_map_entries;
|
||||
specialization_map_entries.push_back(vkb::initializers::specialization_map_entry(0, offsetof(SpecializationData, workgroup_size), sizeof(uint32_t)));
|
||||
specialization_map_entries.push_back(vkb::initializers::specialization_map_entry(1, offsetof(SpecializationData, shared_data_size), sizeof(uint32_t)));
|
||||
specialization_map_entries.push_back(vkb::initializers::specialization_map_entry(2, offsetof(SpecializationData, gravity), sizeof(float)));
|
||||
specialization_map_entries.push_back(vkb::initializers::specialization_map_entry(3, offsetof(SpecializationData, power), sizeof(float)));
|
||||
specialization_map_entries.push_back(vkb::initializers::specialization_map_entry(4, offsetof(SpecializationData, soften), sizeof(float)));
|
||||
|
||||
specialization_data.workgroup_size = work_group_size;
|
||||
specialization_data.shared_data_size = shared_data_size;
|
||||
specialization_data.gravity = 0.002f;
|
||||
specialization_data.power = 0.75f;
|
||||
specialization_data.soften = 0.05f;
|
||||
|
||||
VkSpecializationInfo specialization_info =
|
||||
vkb::initializers::specialization_info(static_cast<uint32_t>(specialization_map_entries.size()), specialization_map_entries.data(), sizeof(specialization_data), &specialization_data);
|
||||
compute_pipeline_create_info.stage.pSpecializationInfo = &specialization_info;
|
||||
|
||||
VK_CHECK(vkCreateComputePipelines(get_device().get_handle(), pipeline_cache, 1, &compute_pipeline_create_info, nullptr, &compute.pipeline_calculate));
|
||||
|
||||
// 2nd pass - Particle integration
|
||||
compute_pipeline_create_info.stage = load_shader("compute_nbody", "particle_integrate.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
specialization_map_entries.clear();
|
||||
specialization_map_entries.push_back(vkb::initializers::specialization_map_entry(0, 0, sizeof(uint32_t)));
|
||||
specialization_info =
|
||||
vkb::initializers::specialization_info(1, specialization_map_entries.data(), sizeof(work_group_size), &work_group_size);
|
||||
|
||||
compute_pipeline_create_info.stage.pSpecializationInfo = &specialization_info;
|
||||
VK_CHECK(vkCreateComputePipelines(get_device().get_handle(), pipeline_cache, 1, &compute_pipeline_create_info, nullptr, &compute.pipeline_integrate));
|
||||
|
||||
// Separate command pool as queue family for compute may be different than graphics
|
||||
VkCommandPoolCreateInfo command_pool_create_info = {};
|
||||
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
command_pool_create_info.queueFamilyIndex = vkb::get_queue_family_index(get_device().get_gpu().get_queue_family_properties(), VK_QUEUE_COMPUTE_BIT);
|
||||
VK_CHECK(vkCreateCommandPool(get_device().get_handle(), &command_pool_create_info, nullptr, &compute.command_pool));
|
||||
|
||||
// Create a command buffer for compute operations
|
||||
VkCommandBufferAllocateInfo command_buffer_allocate_info =
|
||||
vkb::initializers::command_buffer_allocate_info(
|
||||
compute.command_pool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateCommandBuffers(get_device().get_handle(), &command_buffer_allocate_info, &compute.command_buffer));
|
||||
|
||||
// Semaphore for compute & graphics sync
|
||||
VkSemaphoreCreateInfo semaphore_create_info = vkb::initializers::semaphore_create_info();
|
||||
VK_CHECK(vkCreateSemaphore(get_device().get_handle(), &semaphore_create_info, nullptr, &compute.semaphore));
|
||||
|
||||
// Signal the semaphore
|
||||
VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||
submit_info.signalSemaphoreCount = 1;
|
||||
submit_info.pSignalSemaphores = &compute.semaphore;
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
VK_CHECK(vkQueueWaitIdle(queue));
|
||||
|
||||
// Build a single command buffer containing the compute dispatch commands
|
||||
build_compute_command_buffer();
|
||||
|
||||
// If necessary, acquire and immediately release the storage buffer, so that the initial acquire
|
||||
// from the graphics command buffers are matched up properly.
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
VkCommandBuffer transfer_command;
|
||||
|
||||
// Create a transient command buffer for setting up the initial buffer transfer state
|
||||
VkCommandBufferAllocateInfo command_buffer_allocate_info =
|
||||
vkb::initializers::command_buffer_allocate_info(
|
||||
compute.command_pool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateCommandBuffers(get_device().get_handle(), &command_buffer_allocate_info, &transfer_command));
|
||||
|
||||
VkCommandBufferBeginInfo command_buffer_info{};
|
||||
command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
VK_CHECK(vkBeginCommandBuffer(transfer_command, &command_buffer_info));
|
||||
|
||||
VkBufferMemoryBarrier acquire_buffer_barrier =
|
||||
{
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
0,
|
||||
VK_ACCESS_SHADER_WRITE_BIT,
|
||||
graphics.queue_family_index,
|
||||
compute.queue_family_index,
|
||||
compute.storage_buffer->get_handle(),
|
||||
0,
|
||||
compute.storage_buffer->get_size()};
|
||||
vkCmdPipelineBarrier(
|
||||
transfer_command,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &acquire_buffer_barrier,
|
||||
0, nullptr);
|
||||
|
||||
VkBufferMemoryBarrier release_buffer_barrier =
|
||||
{
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
VK_ACCESS_SHADER_WRITE_BIT,
|
||||
0,
|
||||
compute.queue_family_index,
|
||||
graphics.queue_family_index,
|
||||
compute.storage_buffer->get_handle(),
|
||||
0,
|
||||
compute.storage_buffer->get_size()};
|
||||
vkCmdPipelineBarrier(
|
||||
transfer_command,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &release_buffer_barrier,
|
||||
0, nullptr);
|
||||
|
||||
// Copied from Device::flush_command_buffer, which we can't use because it would be
|
||||
// working with the wrong command pool
|
||||
VK_CHECK(vkEndCommandBuffer(transfer_command));
|
||||
|
||||
// Submit compute commands
|
||||
VkSubmitInfo submit_info{};
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &transfer_command;
|
||||
|
||||
// Create fence to ensure that the command buffer has finished executing
|
||||
VkFenceCreateInfo fence_info{};
|
||||
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence_info.flags = VK_FLAGS_NONE;
|
||||
|
||||
VkFence fence;
|
||||
VK_CHECK(vkCreateFence(get_device().get_handle(), &fence_info, nullptr, &fence));
|
||||
// Submit to the *compute* queue
|
||||
VkResult result = vkQueueSubmit(compute.queue, 1, &submit_info, fence);
|
||||
// Wait for the fence to signal that command buffer has finished executing
|
||||
VK_CHECK(vkWaitForFences(get_device().get_handle(), 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT));
|
||||
vkDestroyFence(get_device().get_handle(), fence, nullptr);
|
||||
|
||||
vkFreeCommandBuffers(get_device().get_handle(), compute.command_pool, 1, &transfer_command);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void ComputeNBody::prepare_uniform_buffers()
|
||||
{
|
||||
// Compute shader uniform buffer block
|
||||
compute.uniform_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(compute.ubo),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Vertex shader uniform buffer block
|
||||
graphics.uniform_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(graphics.ubo),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_compute_uniform_buffers(1.0f);
|
||||
update_graphics_uniform_buffers();
|
||||
}
|
||||
|
||||
void ComputeNBody::update_compute_uniform_buffers(float delta_time)
|
||||
{
|
||||
compute.ubo.delta_time = paused ? 0.0f : delta_time;
|
||||
compute.uniform_buffer->convert_and_update(compute.ubo);
|
||||
}
|
||||
|
||||
void ComputeNBody::update_graphics_uniform_buffers()
|
||||
{
|
||||
graphics.ubo.projection = camera.matrices.perspective;
|
||||
graphics.ubo.view = camera.matrices.view;
|
||||
graphics.ubo.screenDim = glm::vec2(static_cast<float>(width), static_cast<float>(height));
|
||||
graphics.uniform_buffer->convert_and_update(graphics.ubo);
|
||||
}
|
||||
|
||||
void ComputeNBody::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
|
||||
VkPipelineStageFlags graphics_wait_stage_masks[] = {VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
||||
VkSemaphore graphics_wait_semaphores[] = {compute.semaphore, semaphores.acquired_image_ready};
|
||||
VkSemaphore graphics_signal_semaphores[] = {graphics.semaphore, semaphores.render_complete};
|
||||
|
||||
// Submit graphics commands
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
submit_info.waitSemaphoreCount = 2;
|
||||
submit_info.pWaitSemaphores = graphics_wait_semaphores;
|
||||
submit_info.pWaitDstStageMask = graphics_wait_stage_masks;
|
||||
submit_info.signalSemaphoreCount = 2;
|
||||
submit_info.pSignalSemaphores = graphics_signal_semaphores;
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
|
||||
ApiVulkanSample::submit_frame();
|
||||
|
||||
// Wait for rendering finished
|
||||
VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||
|
||||
// Submit compute commands
|
||||
VkSubmitInfo compute_submit_info = vkb::initializers::submit_info();
|
||||
compute_submit_info.commandBufferCount = 1;
|
||||
compute_submit_info.pCommandBuffers = &compute.command_buffer;
|
||||
compute_submit_info.waitSemaphoreCount = 1;
|
||||
compute_submit_info.pWaitSemaphores = &graphics.semaphore;
|
||||
compute_submit_info.pWaitDstStageMask = &wait_stage_mask;
|
||||
compute_submit_info.signalSemaphoreCount = 1;
|
||||
compute_submit_info.pSignalSemaphores = &compute.semaphore;
|
||||
VK_CHECK(vkQueueSubmit(compute.queue, 1, &compute_submit_info, VK_NULL_HANDLE));
|
||||
}
|
||||
|
||||
bool ComputeNBody::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const &queue_family_properties = get_device().get_gpu().get_queue_family_properties();
|
||||
graphics.queue_family_index = vkb::get_queue_family_index(queue_family_properties, VK_QUEUE_GRAPHICS_BIT);
|
||||
compute.queue_family_index = vkb::get_queue_family_index(queue_family_properties, VK_QUEUE_COMPUTE_BIT);
|
||||
|
||||
// Not all implementations support a work group size of 256, so we need to check with the device limits
|
||||
work_group_size = std::min(static_cast<uint32_t>(256), get_device().get_gpu().get_properties().limits.maxComputeWorkGroupSize[0]);
|
||||
// Same for shared data size for passing data between shader invocations
|
||||
shared_data_size = std::min(static_cast<uint32_t>(1024), static_cast<uint32_t>(get_device().get_gpu().get_properties().limits.maxComputeSharedMemorySize / sizeof(glm::vec4)));
|
||||
|
||||
load_assets();
|
||||
setup_descriptor_pool();
|
||||
prepare_graphics();
|
||||
prepare_compute();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ComputeNBody::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
update_compute_uniform_buffers(delta_time);
|
||||
if (camera.updated)
|
||||
{
|
||||
update_graphics_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
bool ComputeNBody::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
update_graphics_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_compute_nbody()
|
||||
{
|
||||
return std::make_unique<ComputeNBody>();
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/* Copyright (c) 2019-2024, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Compute shader N-body simulation using two passes and shared compute shader memory
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Lower particle count on Android for performance reasons
|
||||
# define PARTICLES_PER_ATTRACTOR 3 * 1024
|
||||
#else
|
||||
# define PARTICLES_PER_ATTRACTOR 4 * 1024
|
||||
#endif
|
||||
|
||||
class ComputeNBody : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
uint32_t num_particles;
|
||||
uint32_t work_group_size = 128;
|
||||
uint32_t shared_data_size = 1024;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture particle;
|
||||
Texture gradient;
|
||||
} textures;
|
||||
|
||||
// Resources for the graphics part of the example
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer; // Contains scene matrices
|
||||
VkDescriptorSetLayout descriptor_set_layout; // Particle system rendering shader binding layout
|
||||
VkDescriptorSet descriptor_set; // Particle system rendering shader bindings
|
||||
VkPipelineLayout pipeline_layout; // Layout of the graphics pipeline
|
||||
VkPipeline pipeline; // Particle rendering pipeline
|
||||
VkSemaphore semaphore; // Execution dependency between compute & graphic submission
|
||||
uint32_t queue_family_index;
|
||||
struct
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
glm::vec2 screenDim;
|
||||
} ubo;
|
||||
} graphics;
|
||||
|
||||
// Resources for the compute part of the example
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> storage_buffer; // (Shader) storage buffer object containing the particles
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer; // Uniform buffer object containing particle system parameters
|
||||
VkQueue queue; // Separate queue for compute commands (queue family may differ from the one used for graphics)
|
||||
VkCommandPool command_pool; // Use a separate command pool (queue family may differ from the one used for graphics)
|
||||
VkCommandBuffer command_buffer; // Command buffer storing the dispatch commands and barriers
|
||||
VkSemaphore semaphore; // Execution dependency between compute & graphic submission
|
||||
VkDescriptorSetLayout descriptor_set_layout; // Compute shader binding layout
|
||||
VkDescriptorSet descriptor_set; // Compute shader bindings
|
||||
VkPipelineLayout pipeline_layout; // Layout of the compute pipeline
|
||||
VkPipeline pipeline_calculate; // Compute pipeline for N-Body velocity calculation (1st pass)
|
||||
VkPipeline pipeline_integrate; // Compute pipeline for euler integration (2nd pass)
|
||||
VkPipeline blur;
|
||||
VkPipelineLayout pipeline_layout_blur;
|
||||
VkDescriptorSetLayout descriptor_set_layout_blur;
|
||||
VkDescriptorSet descriptor_set_blur;
|
||||
uint32_t queue_family_index;
|
||||
struct ComputeUBO
|
||||
{ // Compute shader uniform block object
|
||||
float delta_time; // Frame delta time
|
||||
int32_t particle_count;
|
||||
} ubo;
|
||||
} compute;
|
||||
|
||||
// SSBO particle declaration
|
||||
struct Particle
|
||||
{
|
||||
glm::vec4 pos; // xyz = position, w = mass
|
||||
glm::vec4 vel; // xyz = velocity, w = gradient texture position
|
||||
};
|
||||
|
||||
ComputeNBody();
|
||||
~ComputeNBody();
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void load_assets();
|
||||
void build_command_buffers() override;
|
||||
void build_compute_command_buffer();
|
||||
void prepare_storage_buffers();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
void prepare_pipelines();
|
||||
void prepare_graphics();
|
||||
void prepare_compute();
|
||||
void prepare_uniform_buffers();
|
||||
void update_compute_uniform_buffers(float delta_time);
|
||||
void update_graphics_uniform_buffers();
|
||||
void draw();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_compute_nbody();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2019-2024, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Dynamic uniform buffers"
|
||||
DESCRIPTION "Demonstrates the use of dynamic offsets into one single uniform buffers for rendering multiple objects"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_uniform_buffers/glsl/base.vert"
|
||||
"dynamic_uniform_buffers/glsl/base.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"dynamic_uniform_buffers/hlsl/base.vert.hlsl"
|
||||
"dynamic_uniform_buffers/hlsl/base.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Dynamic Uniform buffers
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/dynamic_uniform_buffers[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
Dynamic uniform buffers are used for rendering multiple objects with separate matrices stored in a single uniform buffer object, that are addressed dynamically.
|
||||
@@ -1,541 +0,0 @@
|
||||
/* Copyright (c) 2019-2025, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Demonstrates the use of dynamic uniform buffers.
|
||||
*
|
||||
* Instead of using one uniform buffer per-object, this example allocates one big uniform buffer
|
||||
* with respect to the alignment reported by the device via minUniformBufferOffsetAlignment that
|
||||
* contains all matrices for the objects in the scene.
|
||||
*
|
||||
* The used descriptor type VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC then allows to set a dynamic
|
||||
* offset used to pass data from the single uniform buffer to the connected shader binding point.
|
||||
*/
|
||||
|
||||
#include "dynamic_uniform_buffers.h"
|
||||
|
||||
#include "benchmark_mode/benchmark_mode.h"
|
||||
|
||||
DynamicUniformBuffers::DynamicUniformBuffers()
|
||||
{
|
||||
title = "Dynamic uniform buffers";
|
||||
}
|
||||
|
||||
DynamicUniformBuffers ::~DynamicUniformBuffers()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
if (ubo_data_dynamic.model)
|
||||
{
|
||||
aligned_free(ubo_data_dynamic.model);
|
||||
}
|
||||
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper functions for aligned memory allocation
|
||||
// There is currently no standard for this in C++ that works across all platforms and vendors, so we abstract this
|
||||
void *DynamicUniformBuffers::aligned_alloc(size_t size, size_t alignment)
|
||||
{
|
||||
void *data = nullptr;
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
data = _aligned_malloc(size, alignment);
|
||||
#else
|
||||
int res = posix_memalign(&data, alignment, size);
|
||||
if (res != 0)
|
||||
{
|
||||
data = nullptr;
|
||||
}
|
||||
#endif
|
||||
return data;
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::aligned_free(void *data)
|
||||
{
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
_aligned_free(data);
|
||||
#else
|
||||
free(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = default_clear_color;
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < static_cast<int32_t>(draw_cmd_buffers.size()); ++i)
|
||||
{
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer->get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
|
||||
// Render multiple objects using different model matrices by dynamically offsetting into one uniform buffer
|
||||
for (uint32_t j = 0; j < OBJECT_INSTANCES; j++)
|
||||
{
|
||||
// One dynamic offset per dynamic descriptor to offset into the ubo containing all model matrices
|
||||
uint32_t dynamic_offset = j * static_cast<uint32_t>(dynamic_alignment);
|
||||
// Bind the descriptor set for rendering a mesh using the dynamic offset
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 1, &dynamic_offset);
|
||||
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], index_count, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
// Submit to queue
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
|
||||
ApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::generate_cube()
|
||||
{
|
||||
// Setup vertices indices for a colored cube
|
||||
std::vector<Vertex> vertices = {
|
||||
{{-1.0f, -1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}},
|
||||
{{1.0f, -1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}},
|
||||
{{1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}},
|
||||
{{-1.0f, -1.0f, -1.0f}, {1.0f, 0.0f, 0.0f}},
|
||||
{{1.0f, -1.0f, -1.0f}, {0.0f, 1.0f, 0.0f}},
|
||||
{{1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}},
|
||||
};
|
||||
|
||||
std::vector<uint32_t> indices = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
5,
|
||||
6,
|
||||
6,
|
||||
2,
|
||||
1,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
5,
|
||||
4,
|
||||
7,
|
||||
4,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
7,
|
||||
4,
|
||||
4,
|
||||
5,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
3,
|
||||
};
|
||||
|
||||
index_count = static_cast<uint32_t>(indices.size());
|
||||
|
||||
auto vertex_buffer_size = vertices.size() * sizeof(Vertex);
|
||||
auto index_buffer_size = indices.size() * sizeof(uint32_t);
|
||||
|
||||
// Create buffers
|
||||
// For the sake of simplicity we won't stage the vertex data to the gpu memory
|
||||
// Vertex buffer
|
||||
vertex_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
vertex_buffer_size,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
vertex_buffer->update(vertices.data(), vertex_buffer_size);
|
||||
|
||||
index_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
index_buffer_size,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
index_buffer->update(indices.data(), index_buffer_size);
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::setup_descriptor_pool()
|
||||
{
|
||||
// Example uses one ubo and one image sampler
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes =
|
||||
{
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
2);
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings =
|
||||
{
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, VK_SHADER_STAGE_VERTEX_BIT, 1),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2)};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout, nullptr, &descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::setup_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo view_buffer_descriptor = create_descriptor(*uniform_buffers.view);
|
||||
|
||||
// Pass the actual dynamic alignment as the descriptor's size
|
||||
VkDescriptorBufferInfo dynamic_buffer_descriptor = create_descriptor(*uniform_buffers.dynamic, dynamic_alignment);
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
// Binding 0 : Projection/View matrix uniform buffer
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &view_buffer_descriptor),
|
||||
// Binding 1 : Instance matrix as dynamic uniform buffer
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, &dynamic_buffer_descriptor),
|
||||
};
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_NONE,
|
||||
VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_TRUE,
|
||||
VK_TRUE,
|
||||
VK_COMPARE_OP_GREATER);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
// Load shaders
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
|
||||
shader_stages[0] = load_shader("dynamic_uniform_buffers", "base.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("dynamic_uniform_buffers", "base.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex bindings and attributes
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), // Location 0 : Position
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, color)), // Location 1 : Color
|
||||
};
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(
|
||||
pipeline_layout,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void DynamicUniformBuffers::prepare_uniform_buffers()
|
||||
{
|
||||
// Allocate data for the dynamic uniform buffer object
|
||||
// We allocate this manually as the alignment of the offset differs between GPUs
|
||||
|
||||
// Calculate required alignment based on minimum device offset alignment
|
||||
size_t min_ubo_alignment = static_cast<size_t>(get_device().get_gpu().get_properties().limits.minUniformBufferOffsetAlignment);
|
||||
dynamic_alignment = sizeof(glm::mat4);
|
||||
if (min_ubo_alignment > 0)
|
||||
{
|
||||
dynamic_alignment = (dynamic_alignment + min_ubo_alignment - 1) & ~(min_ubo_alignment - 1);
|
||||
}
|
||||
|
||||
size_t buffer_size = OBJECT_INSTANCES * dynamic_alignment;
|
||||
|
||||
ubo_data_dynamic.model = static_cast<glm::mat4 *>(aligned_alloc(buffer_size, dynamic_alignment));
|
||||
assert(ubo_data_dynamic.model);
|
||||
|
||||
std::cout << "minUniformBufferOffsetAlignment = " << min_ubo_alignment << std::endl;
|
||||
std::cout << "dynamicAlignment = " << dynamic_alignment << std::endl;
|
||||
|
||||
// Vertex shader uniform buffer block
|
||||
|
||||
// Static shared uniform buffer object with projection and view matrix
|
||||
uniform_buffers.view = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_vs),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
uniform_buffers.dynamic = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
buffer_size,
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Prepare per-object matrices with offsets and random rotations
|
||||
std::default_random_engine rnd_engine(lock_simulation_speed ? 0 : static_cast<unsigned>(time(nullptr)));
|
||||
std::normal_distribution<float> rnd_dist(-1.0f, 1.0f);
|
||||
for (uint32_t i = 0; i < OBJECT_INSTANCES; i++)
|
||||
{
|
||||
rotations[i] = glm::vec3(rnd_dist(rnd_engine), rnd_dist(rnd_engine), rnd_dist(rnd_engine)) * 2.0f * glm::pi<float>();
|
||||
rotation_speeds[i] = glm::vec3(rnd_dist(rnd_engine), rnd_dist(rnd_engine), rnd_dist(rnd_engine));
|
||||
}
|
||||
|
||||
update_uniform_buffers();
|
||||
update_dynamic_uniform_buffer(0.0f, true);
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::update_uniform_buffers()
|
||||
{
|
||||
// Fixed ubo with projection and view matrices
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.view = camera.matrices.view;
|
||||
|
||||
uniform_buffers.view->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::update_dynamic_uniform_buffer(float delta_time, bool force)
|
||||
{
|
||||
// Update at max. 60 fps
|
||||
animation_timer += delta_time;
|
||||
if ((animation_timer + 0.0025 < (1.0f / 60.0f)) && (!force))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Dynamic ubo with per-object model matrices indexed by offsets in the command buffer
|
||||
auto dim = static_cast<uint32_t>(pow(OBJECT_INSTANCES, (1.0f / 3.0f)));
|
||||
auto fdim = static_cast<float>(dim);
|
||||
glm::vec3 offset(5.0f);
|
||||
|
||||
for (uint32_t x = 0; x < dim; x++)
|
||||
{
|
||||
auto fx = static_cast<float>(x);
|
||||
for (uint32_t y = 0; y < dim; y++)
|
||||
{
|
||||
auto fy = static_cast<float>(y);
|
||||
for (uint32_t z = 0; z < dim; z++)
|
||||
{
|
||||
auto fz = static_cast<float>(z);
|
||||
auto index = x * dim * dim + y * dim + z;
|
||||
|
||||
// Aligned offset
|
||||
auto model_mat = (glm::mat4 *) (((uint64_t) ubo_data_dynamic.model + (index * dynamic_alignment)));
|
||||
|
||||
// Update rotations
|
||||
rotations[index] += animation_timer * rotation_speeds[index];
|
||||
|
||||
// Update matrices
|
||||
glm::vec3 pos(-((fdim * offset.x) / 2.0f) + offset.x / 2.0f + fx * offset.x,
|
||||
-((fdim * offset.y) / 2.0f) + offset.y / 2.0f + fy * offset.y,
|
||||
-((fdim * offset.z) / 2.0f) + offset.z / 2.0f + fz * offset.z);
|
||||
*model_mat = glm::translate(glm::mat4(1.0f), pos);
|
||||
*model_mat = glm::rotate(*model_mat, rotations[index].x, glm::vec3(1.0f, 1.0f, 0.0f));
|
||||
*model_mat = glm::rotate(*model_mat, rotations[index].y, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
*model_mat = glm::rotate(*model_mat, rotations[index].z, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animation_timer = 0.0f;
|
||||
|
||||
uniform_buffers.dynamic->update(ubo_data_dynamic.model, static_cast<size_t>(uniform_buffers.dynamic->get_size()));
|
||||
|
||||
// Flush to make changes visible to the device
|
||||
uniform_buffers.dynamic->flush();
|
||||
}
|
||||
|
||||
bool DynamicUniformBuffers::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position(glm::vec3(0.0f, 0.0f, -30.0f));
|
||||
camera.set_rotation(glm::vec3(0.0f));
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 256.0f, 0.1f);
|
||||
|
||||
generate_cube();
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_set();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DynamicUniformBuffers::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicUniformBuffers::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (!paused)
|
||||
{
|
||||
update_dynamic_uniform_buffer(delta_time);
|
||||
}
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_dynamic_uniform_buffers()
|
||||
{
|
||||
return std::make_unique<DynamicUniformBuffers>();
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/* Copyright (c) 2019-2024, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Demonstrates the use of dynamic uniform buffers.
|
||||
*
|
||||
* Instead of using one uniform buffer per-object, this example allocates one big uniform buffer
|
||||
* with respect to the alignment reported by the device via minUniformBufferOffsetAlignment that
|
||||
* contains all matrices for the objects in the scene.
|
||||
*
|
||||
* The used descriptor type VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC then allows to set a dynamic
|
||||
* offset used to pass data from the single uniform buffer to the connected shader binding point.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
#define OBJECT_INSTANCES 125
|
||||
|
||||
class DynamicUniformBuffers : public ApiVulkanSample
|
||||
{
|
||||
private:
|
||||
void *aligned_alloc(size_t size, size_t alignment);
|
||||
void aligned_free(void *data);
|
||||
|
||||
public:
|
||||
struct Vertex
|
||||
{
|
||||
float pos[3];
|
||||
float color[3];
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer;
|
||||
uint32_t index_count = 0;
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> view;
|
||||
std::unique_ptr<vkb::core::BufferC> dynamic;
|
||||
} uniform_buffers;
|
||||
|
||||
struct UboVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
} ubo_vs;
|
||||
|
||||
// Store random per-object rotations
|
||||
glm::vec3 rotations[OBJECT_INSTANCES];
|
||||
glm::vec3 rotation_speeds[OBJECT_INSTANCES];
|
||||
|
||||
// One big uniform buffer that contains all matrices
|
||||
// Note that we need to manually allocate the data to cope for GPU-specific uniform buffer offset alignments
|
||||
struct UboDataDynamic
|
||||
{
|
||||
glm::mat4 *model = nullptr;
|
||||
} ubo_data_dynamic;
|
||||
|
||||
VkPipeline pipeline;
|
||||
VkPipelineLayout pipeline_layout;
|
||||
VkDescriptorSet descriptor_set;
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
|
||||
float animation_timer = 0.0f;
|
||||
|
||||
size_t dynamic_alignment = 0;
|
||||
|
||||
DynamicUniformBuffers();
|
||||
~DynamicUniformBuffers();
|
||||
void build_command_buffers() override;
|
||||
void generate_cube();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void update_dynamic_uniform_buffer(float delta_time, bool force = false);
|
||||
void draw();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_dynamic_uniform_buffers();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2019-2024, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HDR"
|
||||
DESCRIPTION "High-dynamic-range rendering"
|
||||
SHADER_FILES_GLSL
|
||||
"hdr/glsl/composition.vert"
|
||||
"hdr/glsl/composition.frag"
|
||||
"hdr/glsl/bloom.vert"
|
||||
"hdr/glsl/bloom.frag"
|
||||
"hdr/glsl/gbuffer.vert"
|
||||
"hdr/glsl/gbuffer.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hdr/hlsl/composition.vert.hlsl"
|
||||
"hdr/hlsl/composition.frag.hlsl"
|
||||
"hdr/hlsl/bloom.vert.hlsl"
|
||||
"hdr/hlsl/bloom.frag.hlsl"
|
||||
"hdr/hlsl/gbuffer.vert.hlsl"
|
||||
"hdr/hlsl/gbuffer.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hdr[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
Implements a high dynamic range rendering pipeline using 16/32 bit floating point precision for all calculations.
|
||||
@@ -1,961 +0,0 @@
|
||||
/* Copyright (c) 2019-2025, Sascha Willems
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "hdr.h"
|
||||
|
||||
#include "scene_graph/components/sub_mesh.h"
|
||||
|
||||
HDR::HDR()
|
||||
{
|
||||
title = "High dynamic range rendering";
|
||||
}
|
||||
|
||||
HDR::~HDR()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.skybox, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.reflect, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.composition, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.bloom[0], nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.bloom[1], nullptr);
|
||||
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.models, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.composition, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.bloom_filter, nullptr);
|
||||
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.models, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.composition, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.bloom_filter, nullptr);
|
||||
|
||||
vkDestroyRenderPass(get_device().get_handle(), offscreen.render_pass, nullptr);
|
||||
vkDestroyRenderPass(get_device().get_handle(), filter_pass.render_pass, nullptr);
|
||||
|
||||
vkDestroyFramebuffer(get_device().get_handle(), offscreen.framebuffer, nullptr);
|
||||
vkDestroyFramebuffer(get_device().get_handle(), filter_pass.framebuffer, nullptr);
|
||||
|
||||
vkDestroySampler(get_device().get_handle(), offscreen.sampler, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), filter_pass.sampler, nullptr);
|
||||
|
||||
offscreen.depth.destroy(get_device().get_handle());
|
||||
offscreen.color[0].destroy(get_device().get_handle());
|
||||
offscreen.color[1].destroy(get_device().get_handle());
|
||||
|
||||
filter_pass.color[0].destroy(get_device().get_handle());
|
||||
|
||||
vkDestroySampler(get_device().get_handle(), textures.envmap.sampler, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void HDR::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void HDR::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
{
|
||||
/*
|
||||
First pass: Render scene to offscreen framebuffer
|
||||
*/
|
||||
|
||||
std::array<VkClearValue, 3> clear_values;
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[2].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = offscreen.render_pass;
|
||||
render_pass_begin_info.framebuffer = offscreen.framebuffer;
|
||||
render_pass_begin_info.renderArea.extent.width = offscreen.width;
|
||||
render_pass_begin_info.renderArea.extent.height = offscreen.height;
|
||||
render_pass_begin_info.clearValueCount = 3;
|
||||
render_pass_begin_info.pClearValues = clear_values.data();
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(offscreen.width), static_cast<float>(offscreen.height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(offscreen.width, offscreen.height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
|
||||
// Skybox
|
||||
if (display_skybox)
|
||||
{
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.models, 0, 1, &descriptor_sets.skybox, 0, NULL);
|
||||
|
||||
draw_model(models.skybox, draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
// 3D object
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.models, 0, 1, &descriptor_sets.object, 0, NULL);
|
||||
|
||||
draw_model(models.objects[models.object_index], draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
Second render pass: First bloom pass
|
||||
*/
|
||||
if (bloom)
|
||||
{
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
// Bloom filter
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.framebuffer = filter_pass.framebuffer;
|
||||
render_pass_begin_info.renderPass = filter_pass.render_pass;
|
||||
render_pass_begin_info.clearValueCount = 1;
|
||||
render_pass_begin_info.renderArea.extent.width = filter_pass.width;
|
||||
render_pass_begin_info.renderArea.extent.height = filter_pass.height;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(filter_pass.width), static_cast<float>(filter_pass.height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(filter_pass.width, filter_pass.height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.bloom_filter, 0, 1, &descriptor_sets.bloom_filter, 0, NULL);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[1]);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
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)
|
||||
*/
|
||||
{
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
// Final composition
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.composition, 0, 1, &descriptor_sets.composition, 0, NULL);
|
||||
|
||||
// Scene
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
// Bloom
|
||||
if (bloom)
|
||||
{
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[0]);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
}
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void HDR::create_attachment(VkFormat format, VkImageUsageFlagBits usage, FrameBufferAttachment *attachment)
|
||||
{
|
||||
VkImageAspectFlags aspect_mask = 0;
|
||||
VkImageLayout image_layout;
|
||||
|
||||
attachment->format = format;
|
||||
|
||||
if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
{
|
||||
aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
// Stencil aspect should only be set on depth + stencil formats (VK_FORMAT_D16_UNORM_S8_UINT..VK_FORMAT_D32_SFLOAT_S8_UINT
|
||||
if (format >= VK_FORMAT_D16_UNORM_S8_UINT)
|
||||
{
|
||||
aspect_mask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
image_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
|
||||
assert(aspect_mask > 0);
|
||||
|
||||
VkImageCreateInfo image = vkb::initializers::image_create_info();
|
||||
image.imageType = VK_IMAGE_TYPE_2D;
|
||||
image.format = format;
|
||||
image.extent.width = offscreen.width;
|
||||
image.extent.height = offscreen.height;
|
||||
image.extent.depth = 1;
|
||||
image.mipLevels = 1;
|
||||
image.arrayLayers = 1;
|
||||
image.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image.usage = usage | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
VkMemoryAllocateInfo memory_allocate_info = vkb::initializers::memory_allocate_info();
|
||||
VkMemoryRequirements memory_requirements;
|
||||
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image, nullptr, &attachment->image));
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), attachment->image, &memory_requirements);
|
||||
memory_allocate_info.allocationSize = memory_requirements.size;
|
||||
memory_allocate_info.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocate_info, nullptr, &attachment->mem));
|
||||
VK_CHECK(vkBindImageMemory(get_device().get_handle(), attachment->image, attachment->mem, 0));
|
||||
|
||||
VkImageViewCreateInfo image_view_create_info = vkb::initializers::image_view_create_info();
|
||||
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
image_view_create_info.format = format;
|
||||
image_view_create_info.subresourceRange = {};
|
||||
image_view_create_info.subresourceRange.aspectMask = aspect_mask;
|
||||
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 = attachment->image;
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view_create_info, nullptr, &attachment->view));
|
||||
}
|
||||
|
||||
// Prepare a new framebuffer and attachments for offscreen rendering (G-Buffer)
|
||||
void HDR::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<VkFormat> float_format_priority_list = {
|
||||
VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
VK_FORMAT_R16G16B16A16_SFLOAT // Guaranteed blend support for this
|
||||
};
|
||||
|
||||
VkFormat color_format = vkb::choose_blendable_format(get_device().get_gpu().get_handle(), float_format_priority_list);
|
||||
|
||||
{
|
||||
offscreen.width = width;
|
||||
offscreen.height = height;
|
||||
|
||||
// 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
|
||||
create_attachment(color_format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &offscreen.color[0]);
|
||||
create_attachment(color_format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &offscreen.color[1]);
|
||||
// Depth attachment
|
||||
create_attachment(depth_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &offscreen.depth);
|
||||
|
||||
// Set up separate renderpass with references to the color and depth attachments
|
||||
std::array<VkAttachmentDescription, 3> attachment_descriptions = {};
|
||||
|
||||
// Init attachment properties
|
||||
for (uint32_t i = 0; i < 3; ++i)
|
||||
{
|
||||
attachment_descriptions[i].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachment_descriptions[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment_descriptions[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment_descriptions[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment_descriptions[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
if (i == 2)
|
||||
{
|
||||
attachment_descriptions[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachment_descriptions[i].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment_descriptions[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachment_descriptions[i].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
}
|
||||
|
||||
// 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::vector<VkAttachmentReference> color_references;
|
||||
color_references.push_back({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL});
|
||||
color_references.push_back({1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL});
|
||||
|
||||
VkAttachmentReference depth_reference = {};
|
||||
depth_reference.attachment = 2;
|
||||
depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkSubpassDescription subpass = {};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.pColorAttachments = color_references.data();
|
||||
subpass.colorAttachmentCount = 2;
|
||||
subpass.pDepthStencilAttachment = &depth_reference;
|
||||
|
||||
// Use subpass dependencies for attachment layout transitions
|
||||
std::array<VkSubpassDependency, 2> dependencies;
|
||||
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
// End of previous commands
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[0].srcAccessMask = 0;
|
||||
// Read/write from/to depth
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
// Write to attachment
|
||||
dependencies[0].dstStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[0].dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
// End of write to attachment
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
// Attachment later read using sampler in 'composition' pipeline
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
VkRenderPassCreateInfo render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_create_info.pAttachments = attachment_descriptions.data();
|
||||
render_pass_create_info.attachmentCount = static_cast<uint32_t>(attachment_descriptions.size());
|
||||
render_pass_create_info.subpassCount = 1;
|
||||
render_pass_create_info.pSubpasses = &subpass;
|
||||
render_pass_create_info.dependencyCount = 2;
|
||||
render_pass_create_info.pDependencies = dependencies.data();
|
||||
|
||||
VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &offscreen.render_pass));
|
||||
|
||||
std::array<VkImageView, 3> attachments;
|
||||
attachments[0] = offscreen.color[0].view;
|
||||
attachments[1] = offscreen.color[1].view;
|
||||
attachments[2] = offscreen.depth.view;
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = {};
|
||||
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebuffer_create_info.pNext = NULL;
|
||||
framebuffer_create_info.renderPass = offscreen.render_pass;
|
||||
framebuffer_create_info.pAttachments = attachments.data();
|
||||
framebuffer_create_info.attachmentCount = static_cast<uint32_t>(attachments.size());
|
||||
framebuffer_create_info.width = offscreen.width;
|
||||
framebuffer_create_info.height = offscreen.height;
|
||||
framebuffer_create_info.layers = 1;
|
||||
VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, &offscreen.framebuffer));
|
||||
|
||||
// Calculate valid filter and mipmap modes
|
||||
VkFilter filter = VK_FILTER_NEAREST;
|
||||
VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
vkb::make_filters_valid(get_device().get_gpu().get_handle(), color_format, &filter, &mipmap_mode);
|
||||
|
||||
// Create sampler to sample from the color attachments
|
||||
VkSamplerCreateInfo sampler = vkb::initializers::sampler_create_info();
|
||||
sampler.magFilter = filter;
|
||||
sampler.minFilter = filter;
|
||||
sampler.mipmapMode = mipmap_mode;
|
||||
sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler.addressModeV = sampler.addressModeU;
|
||||
sampler.addressModeW = sampler.addressModeU;
|
||||
sampler.mipLodBias = 0.0f;
|
||||
sampler.maxAnisotropy = 1.0f;
|
||||
sampler.minLod = 0.0f;
|
||||
sampler.maxLod = 1.0f;
|
||||
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler, nullptr, &offscreen.sampler));
|
||||
}
|
||||
|
||||
// Bloom separable filter pass
|
||||
{
|
||||
filter_pass.width = width;
|
||||
filter_pass.height = height;
|
||||
|
||||
// Floating point color attachment
|
||||
create_attachment(color_format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &filter_pass.color[0]);
|
||||
|
||||
// Set up separate renderpass with references to the color and depth attachments
|
||||
std::array<VkAttachmentDescription, 1> attachment_descriptions = {};
|
||||
|
||||
// Init attachment properties
|
||||
attachment_descriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachment_descriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment_descriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment_descriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment_descriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachment_descriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachment_descriptions[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
attachment_descriptions[0].format = filter_pass.color[0].format;
|
||||
|
||||
std::vector<VkAttachmentReference> color_references;
|
||||
color_references.push_back({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL});
|
||||
|
||||
VkSubpassDescription subpass = {};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.pColorAttachments = color_references.data();
|
||||
subpass.colorAttachmentCount = 1;
|
||||
|
||||
// Use subpass dependencies for attachment layout transitions
|
||||
std::array<VkSubpassDependency, 2> dependencies;
|
||||
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
// End of previous commands
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[0].srcAccessMask = 0;
|
||||
// Read from image in fragment shader
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
// Write to attachment
|
||||
dependencies[0].dstStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[0].dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
// End of write to attachment
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
// Attachment later read using sampler in 'bloom[0]' pipeline
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
VkRenderPassCreateInfo render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_create_info.pAttachments = attachment_descriptions.data();
|
||||
render_pass_create_info.attachmentCount = static_cast<uint32_t>(attachment_descriptions.size());
|
||||
render_pass_create_info.subpassCount = 1;
|
||||
render_pass_create_info.pSubpasses = &subpass;
|
||||
render_pass_create_info.dependencyCount = 2;
|
||||
render_pass_create_info.pDependencies = dependencies.data();
|
||||
|
||||
VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &filter_pass.render_pass));
|
||||
|
||||
std::array<VkImageView, 1> attachments;
|
||||
attachments[0] = filter_pass.color[0].view;
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = {};
|
||||
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebuffer_create_info.pNext = NULL;
|
||||
framebuffer_create_info.renderPass = filter_pass.render_pass;
|
||||
framebuffer_create_info.pAttachments = attachments.data();
|
||||
framebuffer_create_info.attachmentCount = static_cast<uint32_t>(attachments.size());
|
||||
framebuffer_create_info.width = filter_pass.width;
|
||||
framebuffer_create_info.height = filter_pass.height;
|
||||
framebuffer_create_info.layers = 1;
|
||||
VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, &filter_pass.framebuffer));
|
||||
|
||||
// Calculate valid filter and mipmap modes
|
||||
VkFilter filter = VK_FILTER_NEAREST;
|
||||
VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
vkb::make_filters_valid(get_device().get_gpu().get_handle(), color_format, &filter, &mipmap_mode);
|
||||
|
||||
// Create sampler to sample from the color attachments
|
||||
VkSamplerCreateInfo sampler = vkb::initializers::sampler_create_info();
|
||||
sampler.magFilter = filter;
|
||||
sampler.minFilter = filter;
|
||||
sampler.mipmapMode = mipmap_mode;
|
||||
sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler.addressModeV = sampler.addressModeU;
|
||||
sampler.addressModeW = sampler.addressModeU;
|
||||
sampler.mipLodBias = 0.0f;
|
||||
sampler.maxAnisotropy = 1.0f;
|
||||
sampler.minLod = 0.0f;
|
||||
sampler.maxLod = 1.0f;
|
||||
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler, nullptr, &filter_pass.sampler));
|
||||
}
|
||||
}
|
||||
|
||||
void HDR::load_assets()
|
||||
{
|
||||
// Models
|
||||
models.skybox = 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)
|
||||
{
|
||||
auto object = load_model("scenes/" + file);
|
||||
models.objects.emplace_back(std::move(object));
|
||||
}
|
||||
|
||||
// Transforms
|
||||
auto geosphere_matrix = glm::mat4(1.0f);
|
||||
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));
|
||||
auto torus_matrix = glm::mat4(1.0f);
|
||||
models.transforms.push_back(geosphere_matrix);
|
||||
models.transforms.push_back(teapot_matrix);
|
||||
models.transforms.push_back(torus_matrix);
|
||||
|
||||
// Load HDR cube map
|
||||
textures.envmap = load_texture_cubemap("textures/uffizi_rgba16f_cube.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
void HDR::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6)};
|
||||
uint32_t num_descriptor_sets = 4;
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(static_cast<uint32_t>(pool_sizes.size()), pool_sizes.data(), num_descriptor_sets);
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void HDR::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info =
|
||||
vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layouts.models));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layouts.models,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.models));
|
||||
|
||||
// Bloom filter
|
||||
set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
|
||||
};
|
||||
|
||||
descriptor_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layouts.bloom_filter));
|
||||
|
||||
pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.bloom_filter, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.bloom_filter));
|
||||
|
||||
// G-Buffer composition
|
||||
set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
|
||||
};
|
||||
|
||||
descriptor_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layouts.composition));
|
||||
|
||||
pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.composition, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.composition));
|
||||
}
|
||||
|
||||
void HDR::setup_descriptor_sets()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&descriptor_set_layouts.models,
|
||||
1);
|
||||
|
||||
// 3D object descriptor set
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.object));
|
||||
|
||||
VkDescriptorBufferInfo matrix_buffer_descriptor = create_descriptor(*uniform_buffers.matrices);
|
||||
VkDescriptorImageInfo environment_image_descriptor = create_descriptor(textures.envmap);
|
||||
VkDescriptorBufferInfo params_buffer_descriptor = create_descriptor(*uniform_buffers.params);
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_buffer_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &environment_image_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, ¶ms_buffer_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
|
||||
// Sky box descriptor set
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.skybox));
|
||||
|
||||
matrix_buffer_descriptor = create_descriptor(*uniform_buffers.matrices);
|
||||
environment_image_descriptor = create_descriptor(textures.envmap);
|
||||
params_buffer_descriptor = create_descriptor(*uniform_buffers.params);
|
||||
write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_buffer_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &environment_image_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, ¶ms_buffer_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
|
||||
// Bloom filter
|
||||
alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layouts.bloom_filter, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.bloom_filter));
|
||||
|
||||
std::vector<VkDescriptorImageInfo> color_descriptors = {
|
||||
vkb::initializers::descriptor_image_info(offscreen.sampler, offscreen.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
|
||||
vkb::initializers::descriptor_image_info(offscreen.sampler, offscreen.color[1].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
|
||||
};
|
||||
|
||||
write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.bloom_filter, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &color_descriptors[0]),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.bloom_filter, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &color_descriptors[1]),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
|
||||
// Composition descriptor set
|
||||
alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layouts.composition, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.composition));
|
||||
|
||||
color_descriptors = {
|
||||
vkb::initializers::descriptor_image_info(offscreen.sampler, offscreen.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
|
||||
vkb::initializers::descriptor_image_info(offscreen.sampler, filter_pass.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
|
||||
};
|
||||
|
||||
write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &color_descriptors[0]),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &color_descriptors[1]),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
void HDR::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_BACK_BIT,
|
||||
VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_FALSE,
|
||||
VK_FALSE,
|
||||
VK_COMPARE_OP_GREATER);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(
|
||||
pipeline_layouts.models,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
std::vector<VkPipelineColorBlendAttachmentState> blend_attachment_states = {
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(0xf, VK_FALSE),
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(0xf, VK_FALSE),
|
||||
};
|
||||
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
VkSpecializationInfo specialization_info;
|
||||
std::array<VkSpecializationMapEntry, 1> specialization_map_entries;
|
||||
|
||||
// Full screen pipelines
|
||||
|
||||
// Empty vertex input state, full screen triangles are generated by the vertex shader
|
||||
VkPipelineVertexInputStateCreateInfo empty_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
pipeline_create_info.pVertexInputState = &empty_input_state;
|
||||
|
||||
// Final fullscreen composition pass pipeline
|
||||
shader_stages[0] = load_shader("hdr", "composition.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("hdr", "composition.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
pipeline_create_info.layout = pipeline_layouts.composition;
|
||||
pipeline_create_info.renderPass = render_pass;
|
||||
rasterization_state.cullMode = VK_CULL_MODE_FRONT_BIT;
|
||||
color_blend_state.attachmentCount = 1;
|
||||
color_blend_state.pAttachments = blend_attachment_states.data();
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.composition));
|
||||
|
||||
// Bloom pass
|
||||
shader_stages[0] = load_shader("hdr", "bloom.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("hdr", "bloom.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
color_blend_state.pAttachments = &blend_attachment_state;
|
||||
blend_attachment_state.colorWriteMask = 0xF;
|
||||
blend_attachment_state.blendEnable = VK_TRUE;
|
||||
blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
|
||||
|
||||
// Set constant parameters via specialization constants
|
||||
specialization_map_entries[0] = vkb::initializers::specialization_map_entry(0, 0, sizeof(uint32_t));
|
||||
uint32_t dir = 1;
|
||||
specialization_info = vkb::initializers::specialization_info(1, specialization_map_entries.data(), sizeof(dir), &dir);
|
||||
shader_stages[1].pSpecializationInfo = &specialization_info;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.bloom[0]));
|
||||
|
||||
// Second blur pass (into separate framebuffer)
|
||||
pipeline_create_info.renderPass = filter_pass.render_pass;
|
||||
dir = 0;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.bloom[1]));
|
||||
|
||||
// Object rendering pipelines
|
||||
rasterization_state.cullMode = VK_CULL_MODE_BACK_BIT;
|
||||
|
||||
// Vertex bindings an attributes for model rendering
|
||||
// Binding description
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
|
||||
// Attribute descriptions
|
||||
std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3) // Normal
|
||||
};
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
|
||||
// Skybox pipeline (background cube)
|
||||
blend_attachment_state.blendEnable = VK_FALSE;
|
||||
pipeline_create_info.layout = pipeline_layouts.models;
|
||||
pipeline_create_info.renderPass = offscreen.render_pass;
|
||||
color_blend_state.attachmentCount = 2;
|
||||
color_blend_state.pAttachments = blend_attachment_states.data();
|
||||
|
||||
shader_stages[0] = load_shader("hdr", "gbuffer.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("hdr", "gbuffer.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Set constant parameters via specialization constants
|
||||
specialization_map_entries[0] = vkb::initializers::specialization_map_entry(0, 0, sizeof(uint32_t));
|
||||
uint32_t shadertype = 0;
|
||||
specialization_info = vkb::initializers::specialization_info(1, specialization_map_entries.data(), sizeof(shadertype), &shadertype);
|
||||
shader_stages[0].pSpecializationInfo = &specialization_info;
|
||||
shader_stages[1].pSpecializationInfo = &specialization_info;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.skybox));
|
||||
|
||||
// Object rendering pipeline
|
||||
shadertype = 1;
|
||||
|
||||
// Enable depth test and write
|
||||
depth_stencil_state.depthWriteEnable = VK_TRUE;
|
||||
depth_stencil_state.depthTestEnable = VK_TRUE;
|
||||
// Flip cull mode
|
||||
rasterization_state.cullMode = VK_CULL_MODE_FRONT_BIT;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.reflect));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void HDR::prepare_uniform_buffers()
|
||||
{
|
||||
// Matrices vertex shader uniform buffer
|
||||
uniform_buffers.matrices = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_vs),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Params
|
||||
uniform_buffers.params = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_params),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
update_params();
|
||||
}
|
||||
|
||||
void HDR::update_uniform_buffers()
|
||||
{
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.modelview = camera.matrices.view * models.transforms[models.object_index];
|
||||
ubo_vs.skybox_modelview = camera.matrices.view;
|
||||
ubo_vs.inverse_modelview = glm::inverse(camera.matrices.view);
|
||||
uniform_buffers.matrices->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
void HDR::update_params()
|
||||
{
|
||||
uniform_buffers.params->convert_and_update(ubo_params);
|
||||
}
|
||||
|
||||
void HDR::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
ApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
bool HDR::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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>(width) / static_cast<float>(height), 256.0f, 0.1f);
|
||||
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
prepare_offscreen_buffer();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_sets();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HDR::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void HDR::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))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Skybox", &display_skybox))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HDR::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hdr()
|
||||
{
|
||||
return std::make_unique<HDR>();
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/* Copyright (c) 2019-2024, Sascha Willems
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class HDR : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
bool bloom = true;
|
||||
bool display_skybox = true;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture envmap;
|
||||
} textures;
|
||||
|
||||
struct Models
|
||||
{
|
||||
std::unique_ptr<vkb::sg::SubMesh> skybox;
|
||||
std::vector<std::unique_ptr<vkb::sg::SubMesh>> objects;
|
||||
std::vector<glm::mat4> transforms;
|
||||
int32_t object_index = 0;
|
||||
} models;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> matrices;
|
||||
std::unique_ptr<vkb::core::BufferC> params;
|
||||
} uniform_buffers;
|
||||
|
||||
struct UBOVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skybox_modelview;
|
||||
glm::mat4 inverse_modelview;
|
||||
float modelscale = 0.05f;
|
||||
} ubo_vs;
|
||||
|
||||
struct UBOParams
|
||||
{
|
||||
float exposure = 1.0f;
|
||||
} ubo_params;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipeline skybox;
|
||||
VkPipeline reflect;
|
||||
VkPipeline composition;
|
||||
VkPipeline bloom[2];
|
||||
} pipelines;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout models;
|
||||
VkPipelineLayout composition;
|
||||
VkPipelineLayout bloom_filter;
|
||||
} pipeline_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet object;
|
||||
VkDescriptorSet skybox;
|
||||
VkDescriptorSet composition;
|
||||
VkDescriptorSet bloom_filter;
|
||||
} descriptor_sets;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSetLayout models;
|
||||
VkDescriptorSetLayout composition;
|
||||
VkDescriptorSetLayout bloom_filter;
|
||||
} descriptor_set_layouts;
|
||||
|
||||
// Framebuffer for offscreen rendering
|
||||
struct FrameBufferAttachment
|
||||
{
|
||||
VkImage image;
|
||||
VkDeviceMemory mem;
|
||||
VkImageView view;
|
||||
VkFormat format;
|
||||
void destroy(VkDevice device)
|
||||
{
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
vkDestroyImage(device, image, nullptr);
|
||||
vkFreeMemory(device, mem, nullptr);
|
||||
}
|
||||
};
|
||||
struct FrameBuffer
|
||||
{
|
||||
int32_t width, height;
|
||||
VkFramebuffer framebuffer;
|
||||
FrameBufferAttachment color[2];
|
||||
FrameBufferAttachment depth;
|
||||
VkRenderPass render_pass;
|
||||
VkSampler sampler;
|
||||
} offscreen;
|
||||
|
||||
struct
|
||||
{
|
||||
int32_t width, height;
|
||||
VkFramebuffer framebuffer;
|
||||
FrameBufferAttachment color[1];
|
||||
VkRenderPass render_pass;
|
||||
VkSampler sampler;
|
||||
} filter_pass;
|
||||
|
||||
std::vector<std::string> object_names;
|
||||
|
||||
HDR();
|
||||
~HDR();
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void build_command_buffers() override;
|
||||
void create_attachment(VkFormat format, VkImageUsageFlagBits usage, FrameBufferAttachment *attachment);
|
||||
void prepare_offscreen_buffer();
|
||||
void load_assets();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_sets();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void update_params();
|
||||
void draw();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hdr();
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2019-2025, Arm Limited and Contributors
|
||||
# Copyright (c) 2025, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Arm"
|
||||
NAME "Hello Triangle"
|
||||
DESCRIPTION "An introduction into Vulkan and its respective objects."
|
||||
SHADER_FILES_GLSL
|
||||
"hello_triangle/glsl/triangle.vert"
|
||||
"hello_triangle/glsl/triangle.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hello_triangle/hlsl/triangle.vert.hlsl"
|
||||
"hello_triangle/hlsl/triangle.frag.hlsl"
|
||||
SHADER_FILES_SLANG
|
||||
"hello_triangle/slang/triangle.vert.slang"
|
||||
"hello_triangle/slang/triangle.frag.slang")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Hello Triangle
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hello_triangle[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
A self-contained (minimal use of framework) sample that illustrates the rendering of a triangle.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,179 +0,0 @@
|
||||
/* Copyright (c) 2018-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2025, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "core/instance.h"
|
||||
#include "platform/application.h"
|
||||
|
||||
/**
|
||||
* @brief A self-contained (minimal use of framework) sample that illustrates
|
||||
* the rendering of a triangle
|
||||
*/
|
||||
class HelloTriangle : public vkb::Application
|
||||
{
|
||||
/**
|
||||
* @brief Swapchain state
|
||||
*/
|
||||
struct SwapchainDimensions
|
||||
{
|
||||
/// Width of the swapchain.
|
||||
uint32_t width = 0;
|
||||
|
||||
/// Height of the swapchain.
|
||||
uint32_t height = 0;
|
||||
|
||||
/// Pixel format of the swapchain.
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Per-frame data
|
||||
*/
|
||||
struct PerFrame
|
||||
{
|
||||
VkFence queue_submit_fence = VK_NULL_HANDLE;
|
||||
VkCommandPool primary_command_pool = VK_NULL_HANDLE;
|
||||
VkCommandBuffer primary_command_buffer = VK_NULL_HANDLE;
|
||||
VkSemaphore swapchain_acquire_semaphore = VK_NULL_HANDLE;
|
||||
VkSemaphore swapchain_release_semaphore = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Vulkan objects and global state
|
||||
*/
|
||||
struct Context
|
||||
{
|
||||
/// The Vulkan instance.
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
|
||||
/// The Vulkan physical device.
|
||||
VkPhysicalDevice gpu = VK_NULL_HANDLE;
|
||||
|
||||
/// The Vulkan device.
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
|
||||
/// The Vulkan device queue.
|
||||
VkQueue queue = VK_NULL_HANDLE;
|
||||
|
||||
/// The swapchain.
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
|
||||
/// The swapchain dimensions.
|
||||
SwapchainDimensions swapchain_dimensions;
|
||||
|
||||
/// The surface we will render to.
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
|
||||
/// The queue family index where graphics work will be submitted.
|
||||
int32_t graphics_queue_index = -1;
|
||||
|
||||
/// The image view for each swapchain image.
|
||||
std::vector<VkImageView> swapchain_image_views;
|
||||
|
||||
/// The framebuffer for each swapchain image view.
|
||||
std::vector<VkFramebuffer> swapchain_framebuffers;
|
||||
|
||||
/// The renderpass description.
|
||||
VkRenderPass render_pass = VK_NULL_HANDLE;
|
||||
|
||||
/// The graphics pipeline.
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
|
||||
/**
|
||||
* The pipeline layout for resources.
|
||||
* Not used in this sample, but we still need to provide a dummy one.
|
||||
*/
|
||||
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
|
||||
|
||||
/// The debug utility callback.
|
||||
VkDebugUtilsMessengerEXT debug_callback = VK_NULL_HANDLE;
|
||||
|
||||
/// A set of semaphores that can be reused.
|
||||
std::vector<VkSemaphore> recycled_semaphores;
|
||||
|
||||
/// A set of per-frame data.
|
||||
std::vector<PerFrame> per_frame;
|
||||
|
||||
VmaAllocator vma_allocator = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
/// Properties of the vertices used in this sample.
|
||||
struct Vertex
|
||||
{
|
||||
glm::vec3 position;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
/// The Vulkan buffer object that holds the vertex data for the triangle.
|
||||
VkBuffer vertex_buffer = VK_NULL_HANDLE;
|
||||
|
||||
/// The device memory allocated for the vertex buffer.
|
||||
VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE;
|
||||
|
||||
/// Vulkan Memory Allocator (VMA) allocation info for the vertex buffer.
|
||||
VmaAllocation vertex_buffer_allocation = VK_NULL_HANDLE;
|
||||
|
||||
public:
|
||||
HelloTriangle();
|
||||
|
||||
virtual ~HelloTriangle();
|
||||
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
virtual void update(float delta_time) override;
|
||||
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
bool validate_extensions(const std::vector<const char *> &required,
|
||||
const std::vector<VkExtensionProperties> &available);
|
||||
|
||||
void init_instance();
|
||||
|
||||
void init_device();
|
||||
|
||||
void init_vertex_buffer();
|
||||
|
||||
void init_per_frame(PerFrame &per_frame);
|
||||
|
||||
void teardown_per_frame(PerFrame &per_frame);
|
||||
|
||||
void init_swapchain();
|
||||
|
||||
void init_render_pass();
|
||||
|
||||
VkShaderModule load_shader_module(const std::string &path);
|
||||
|
||||
void init_pipeline();
|
||||
|
||||
VkResult acquire_next_image(uint32_t *image);
|
||||
|
||||
void render_triangle(uint32_t swapchain_index);
|
||||
|
||||
VkResult present_image(uint32_t index);
|
||||
|
||||
void init_framebuffers();
|
||||
|
||||
private:
|
||||
Context context;
|
||||
|
||||
std::unique_ptr<vkb::core::InstanceC> vk_instance;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hello_triangle();
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2024-2025, Huawei Technologies Co., Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Huawei Technologies Co., Ltd."
|
||||
NAME "Vulkan 1.3 Hello Triangle"
|
||||
DESCRIPTION "An introduction into Vulkan using Vulkan 1.3"
|
||||
SHADER_FILES_GLSL
|
||||
"hello_triangle_1_3/glsl/triangle.vert"
|
||||
"hello_triangle_1_3/glsl/triangle.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hello_triangle_1_3/hlsl/triangle.vert.hlsl"
|
||||
"hello_triangle_1_3/hlsl/triangle.frag.hlsl"
|
||||
SHADER_FILES_SLANG
|
||||
"hello_triangle_1_3/slang/triangle.vert.slang"
|
||||
"hello_triangle_1_3/slang/triangle.frag.slang")
|
||||
@@ -1,213 +0,0 @@
|
||||
////
|
||||
* Copyright (c) 2024, Huawei Technologies Co., Ltd.
|
||||
*
|
||||
* 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.
|
||||
////
|
||||
= Hello Triangle with Vulkan 1.3 Features
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hello_triangle_1_3[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
This sample demonstrates how to render a simple triangle using Vulkan 1.3 core features. It modernizes the traditional "Hello Triangle" Vulkan sample by incorporating:
|
||||
|
||||
- **Dynamic Rendering**
|
||||
- **Synchronization2**
|
||||
- **Extended Dynamic State**
|
||||
- **Vertex Buffers**
|
||||
|
||||
## Overview
|
||||
|
||||
The sample renders a colored triangle to the screen using Vulkan 1.3. It showcases how to:
|
||||
|
||||
- Initialize Vulkan with Vulkan 1.3 features enabled.
|
||||
- Use dynamic rendering to simplify the rendering pipeline.
|
||||
- Employ the Synchronization2 API for improved synchronization.
|
||||
- Utilize extended dynamic states to reduce pipeline complexity.
|
||||
- Manage vertex data using vertex buffers instead of hard-coded vertices.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Dynamic Rendering
|
||||
|
||||
**What is Dynamic Rendering?**
|
||||
|
||||
Dynamic Rendering is a feature introduced in Vulkan 1.3 that allows rendering without pre-defined render passes and framebuffers. It simplifies the rendering process by enabling you to specify rendering states directly during command buffer recording.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **No Render Passes or Framebuffers:** The sample does not create `VkRenderPass` or `VkFramebuffer` objects.
|
||||
- **`vkCmdBeginRendering()` and `vkCmdEndRendering()`:** These functions are used to begin and end rendering operations dynamically.
|
||||
- **Pipeline Creation:** Uses `VkPipelineRenderingCreateInfo` during pipeline creation to specify rendering details.
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Simplifies code by reducing boilerplate associated with render passes and framebuffers.
|
||||
- Increases flexibility by allowing rendering to different attachments without recreating render passes.
|
||||
|
||||
### 2. Synchronization2
|
||||
|
||||
**What is Synchronization2?**
|
||||
|
||||
Synchronization2 is an improved synchronization API introduced in Vulkan 1.3. It provides more granular control over synchronization primitives and simplifies the synchronization process.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **`vkCmdPipelineBarrier2()`:** Replaces the older `vkCmdPipelineBarrier()` for more detailed synchronization.
|
||||
- **`VkDependencyInfo` and `VkImageMemoryBarrier2`:** Used to specify precise memory dependencies and image layout transitions.
|
||||
|
||||
**Example Usage:**
|
||||
|
||||
```cpp
|
||||
VkImageMemoryBarrier2 image_barrier = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
|
||||
// ... other members ...
|
||||
};
|
||||
|
||||
VkDependencyInfo dependency_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.imageMemoryBarrierCount = 1,
|
||||
.pImageMemoryBarriers = &image_barrier,
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier2(cmd, &dependency_info);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Provides more expressive and flexible synchronization.
|
||||
- Reduces the potential for synchronization errors.
|
||||
- Simplifies the specification of pipeline stages and access masks.
|
||||
|
||||
### 3. Extended Dynamic State
|
||||
|
||||
**What is Extended Dynamic State?**
|
||||
|
||||
Extended Dynamic State allows more pipeline states to be set dynamically at command buffer recording time rather than during pipeline creation. This reduces the number of pipeline objects needed.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **Dynamic States Enabled:** The sample enables dynamic states like `VK_DYNAMIC_STATE_CULL_MODE`, `VK_DYNAMIC_STATE_FRONT_FACE`, and `VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY`.
|
||||
- **Dynamic State Commands:** Uses `vkCmdSetCullMode()`, `vkCmdSetFrontFace()`, and `vkCmdSetPrimitiveTopology()` to set these states dynamically.
|
||||
|
||||
**Example Usage:**
|
||||
|
||||
```cpp
|
||||
vkCmdSetCullMode(cmd, VK_CULL_MODE_NONE);
|
||||
vkCmdSetFrontFace(cmd, VK_FRONT_FACE_CLOCKWISE);
|
||||
vkCmdSetPrimitiveTopology(cmd, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Reduces the need to create multiple pipelines for different state configurations.
|
||||
- Enhances flexibility by allowing state changes without pipeline recreation.
|
||||
|
||||
### 4. Vertex Buffers
|
||||
|
||||
**What Changed?**
|
||||
|
||||
Unlike the original sample, which used hard-coded vertices in the shader, this sample uses a vertex buffer to store vertex data.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **Vertex Structure Defined:**
|
||||
|
||||
```cpp
|
||||
struct Vertex {
|
||||
glm::vec2 position;
|
||||
glm::vec3 color;
|
||||
};
|
||||
```
|
||||
|
||||
- **Vertex Data Stored in a Buffer:**
|
||||
|
||||
```cpp
|
||||
std::vector<Vertex> vertices = {
|
||||
{{0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, // Red Vertex
|
||||
// ... other vertices ...
|
||||
};
|
||||
```
|
||||
|
||||
- **Buffer Creation and Memory Allocation:**
|
||||
|
||||
```cpp
|
||||
VkBufferCreateInfo buffer_info = { /* ... */ };
|
||||
vkCreateBuffer(device, &buffer_info, nullptr, &vertex_buffer);
|
||||
|
||||
VkMemoryAllocateInfo alloc_info = { /* ... */ };
|
||||
vkAllocateMemory(device, &alloc_info, nullptr, &vertex_buffer_memory);
|
||||
```
|
||||
|
||||
- **Binding the Vertex Buffer:**
|
||||
|
||||
```cpp
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &vertex_buffer, &offset);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- **Flexibility:** Easier to modify vertex data without changing shaders.
|
||||
- **Performance:** Potentially better performance due to efficient memory usage.
|
||||
- **Scalability:** Simplifies rendering more complex geometries.
|
||||
|
||||
## How the Sample Works
|
||||
|
||||
1. **Initialization:**
|
||||
|
||||
- **Instance Creation:** Initializes a Vulkan instance with Vulkan 1.3 API version and required extensions.
|
||||
- **Device Selection:** Chooses a physical device that supports Vulkan 1.3 and required features.
|
||||
- **Logical Device Creation:** Creates a logical device with enabled Vulkan 1.3 features like dynamic rendering, synchronization2, and extended dynamic state.
|
||||
- **Surface and Swapchain Creation:** Sets up the window surface and initializes the swapchain for presenting images.
|
||||
|
||||
2. **Vertex Buffer Setup:**
|
||||
|
||||
- **Vertex Data Definition:** Defines vertices with positions and colors.
|
||||
- **Buffer Creation:** Creates a buffer to store vertex data.
|
||||
- **Memory Allocation:** Allocates memory for the buffer and maps the vertex data into it.
|
||||
|
||||
3. **Pipeline Setup:**
|
||||
|
||||
- **Shader Modules:** Loads and compiles vertex and fragment shaders.
|
||||
- **Pipeline Layout:** Creates a pipeline layout (empty in this case as no descriptors are used).
|
||||
- **Dynamic States Specification:** Specifies which states will be dynamic.
|
||||
- **Graphics Pipeline Creation:** Creates the graphics pipeline with dynamic rendering info and dynamic states enabled.
|
||||
|
||||
4. **Rendering Loop:**
|
||||
|
||||
- **Acquire Swapchain Image:** Gets the next available image from the swapchain.
|
||||
- **Command Buffer Recording:**
|
||||
|
||||
- **Begin Rendering:** Uses `vkCmdBeginRendering()` with dynamic rendering info.
|
||||
- **Set Dynamic States:** Sets viewport, scissor, cull mode, front face, and primitive topology dynamically.
|
||||
- **Bind Pipeline and Vertex Buffer:** Binds the graphics pipeline and the vertex buffer.
|
||||
- **Draw Call:** Issues a draw call to render the triangle.
|
||||
- **End Rendering:** Uses `vkCmdEndRendering()` to finish rendering.
|
||||
- **Image Layout Transition:** Transitions the swapchain image layout for presentation using `vkCmdPipelineBarrier2()`.
|
||||
|
||||
- **Queue Submission:** Submits the command buffer to the graphics queue.
|
||||
- **Present Image:** Presents the rendered image to the screen.
|
||||
|
||||
5. **Cleanup:**
|
||||
|
||||
- **Resource Destruction:** Cleans up Vulkan resources like pipelines, buffers, and swapchain images upon application exit.
|
||||
|
||||
## Dependencies and Requirements
|
||||
|
||||
- **Vulkan SDK 1.3 or Later:** Ensure you have the Vulkan SDK that supports Vulkan 1.3.
|
||||
- **Hardware Support:** A GPU that supports Vulkan 1.3 features, including dynamic rendering, synchronization2, and extended dynamic state.
|
||||
- **GLM Library:** Used for vector and matrix operations.
|
||||
- **Shader Compiler:** GLSL shaders are compiled at runtime using a GLSL compiler.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,177 +0,0 @@
|
||||
/* Copyright (c) 2024-2025, Huawei Technologies Co., Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "core/instance.h"
|
||||
#include "platform/application.h"
|
||||
|
||||
/**
|
||||
* @brief A self-contained (minimal use of framework) sample that illustrates
|
||||
* the rendering of a triangle
|
||||
*/
|
||||
class HelloTriangleV13 : public vkb::Application
|
||||
{
|
||||
// Define the Vertex structure
|
||||
struct Vertex
|
||||
{
|
||||
glm::vec2 position;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
// Define the vertex data
|
||||
const std::vector<Vertex> vertices = {
|
||||
{{0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, // Vertex 1: Red
|
||||
{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, // Vertex 2: Green
|
||||
{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} // Vertex 3: Blue
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Swapchain state
|
||||
*/
|
||||
struct SwapchainDimensions
|
||||
{
|
||||
/// Width of the swapchain.
|
||||
uint32_t width = 0;
|
||||
|
||||
/// Height of the swapchain.
|
||||
uint32_t height = 0;
|
||||
|
||||
/// Pixel format of the swapchain.
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Per-frame data
|
||||
*/
|
||||
struct PerFrame
|
||||
{
|
||||
VkFence queue_submit_fence = VK_NULL_HANDLE;
|
||||
VkCommandPool primary_command_pool = VK_NULL_HANDLE;
|
||||
VkCommandBuffer primary_command_buffer = VK_NULL_HANDLE;
|
||||
VkSemaphore swapchain_acquire_semaphore = VK_NULL_HANDLE;
|
||||
VkSemaphore swapchain_release_semaphore = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Vulkan objects and global state
|
||||
*/
|
||||
struct Context
|
||||
{
|
||||
/// The Vulkan instance.
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
|
||||
/// The Vulkan physical device.
|
||||
VkPhysicalDevice gpu = VK_NULL_HANDLE;
|
||||
|
||||
/// The Vulkan device.
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
|
||||
/// The Vulkan device queue.
|
||||
VkQueue queue = VK_NULL_HANDLE;
|
||||
|
||||
/// The swapchain.
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
|
||||
/// The swapchain dimensions.
|
||||
SwapchainDimensions swapchain_dimensions;
|
||||
|
||||
/// The surface we will render to.
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
|
||||
/// The queue family index where graphics work will be submitted.
|
||||
int32_t graphics_queue_index = -1;
|
||||
|
||||
/// The image view for each swapchain image.
|
||||
std::vector<VkImageView> swapchain_image_views;
|
||||
|
||||
/// The handles to the images in the swapchain.
|
||||
std::vector<VkImage> swapchain_images;
|
||||
|
||||
/// The graphics pipeline.
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
|
||||
/**
|
||||
* The pipeline layout for resources.
|
||||
* Not used in this sample, but we still need to provide a dummy one.
|
||||
*/
|
||||
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
|
||||
|
||||
/// The debug utility messenger callback.
|
||||
VkDebugUtilsMessengerEXT debug_callback = VK_NULL_HANDLE;
|
||||
|
||||
/// A set of semaphores that can be reused.
|
||||
std::vector<VkSemaphore> recycled_semaphores;
|
||||
|
||||
/// A set of per-frame data.
|
||||
std::vector<PerFrame> per_frame;
|
||||
|
||||
/// The Vulkan buffer object that holds the vertex data for the triangle.
|
||||
VkBuffer vertex_buffer = VK_NULL_HANDLE;
|
||||
|
||||
/// The device memory allocated for the vertex buffer.
|
||||
VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
public:
|
||||
HelloTriangleV13() = default;
|
||||
|
||||
virtual ~HelloTriangleV13();
|
||||
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
virtual void update(float delta_time) override;
|
||||
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
bool validate_extensions(const std::vector<const char *> &required,
|
||||
const std::vector<VkExtensionProperties> &available);
|
||||
|
||||
void init_instance();
|
||||
|
||||
void init_device();
|
||||
|
||||
void init_vertex_buffer();
|
||||
|
||||
void init_per_frame(PerFrame &per_frame);
|
||||
|
||||
void teardown_per_frame(PerFrame &per_frame);
|
||||
|
||||
void init_swapchain();
|
||||
|
||||
VkShaderModule load_shader_module(const std::string &path, VkShaderStageFlagBits shader_stage);
|
||||
|
||||
void init_pipeline();
|
||||
|
||||
VkResult acquire_next_swapchain_image(uint32_t *image);
|
||||
|
||||
void render_triangle(uint32_t swapchain_index);
|
||||
|
||||
VkResult present_image(uint32_t index);
|
||||
|
||||
void transition_image_layout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, VkAccessFlags2 srcAccessMask, VkAccessFlags2 dstAccessMask, VkPipelineStageFlags2 srcStage, VkPipelineStageFlags2 dstStage);
|
||||
|
||||
uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags properties);
|
||||
|
||||
private:
|
||||
Context context;
|
||||
|
||||
std::unique_ptr<vkb::core::InstanceC> vk_instance;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hello_triangle_1_3();
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2022-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Compute N-Body simulation"
|
||||
DESCRIPTION "Multi-pass compute dispatch N-Body particle simulation, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"compute_nbody/glsl/particle.vert"
|
||||
"compute_nbody/glsl/particle.frag"
|
||||
"compute_nbody/glsl/particle_calculate.comp"
|
||||
"compute_nbody/glsl/particle_integrate.comp"
|
||||
SHADER_FILES_HLSL
|
||||
"compute_nbody/hlsl/particle.vert.hlsl"
|
||||
"compute_nbody/hlsl/particle.frag.hlsl"
|
||||
"compute_nbody/hlsl/particle_calculate.comp.hlsl"
|
||||
"compute_nbody/hlsl/particle_integrate.comp.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= HPP Compute shader N-Body simulation
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_compute_nbody[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/compute_nbody[Compute N-Body] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp.
|
||||
@@ -1,666 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Compute shader N-body simulation using two passes and shared compute shader memory, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_compute_nbody.h"
|
||||
#include "benchmark_mode/benchmark_mode.h"
|
||||
#include "core/command_pool.h"
|
||||
#include <random>
|
||||
|
||||
HPPComputeNBody::HPPComputeNBody()
|
||||
{
|
||||
title = "Compute shader N-body system";
|
||||
initializeCamera();
|
||||
}
|
||||
|
||||
HPPComputeNBody::~HPPComputeNBody()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
compute.destroy(device);
|
||||
graphics.destroy(device);
|
||||
textures.destroy(device);
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPComputeNBody::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
load_assets();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
prepare_graphics();
|
||||
prepare_compute();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool HPPComputeNBody::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
HPPApiVulkanSample::resize(width, height);
|
||||
update_graphics_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HPPComputeNBody::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
|
||||
{
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void HPPComputeNBody::build_command_buffers()
|
||||
{
|
||||
std::array<vk::ClearValue, 2> clear_values = {{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 1.0f}})),
|
||||
vk::ClearDepthStencilValue{0.0f, 0}}};
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
// Set target frame buffer
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
vk::CommandBuffer command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(vk::CommandBufferBeginInfo());
|
||||
|
||||
// Acquire
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
vk::BufferMemoryBarrier buffer_barrier{.dstAccessMask = vk::AccessFlagBits::eVertexAttributeRead,
|
||||
.srcQueueFamilyIndex = compute.queue_family_index,
|
||||
.dstQueueFamilyIndex = graphics.queue_family_index,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
|
||||
command_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eVertexInput, {}, nullptr, buffer_barrier, nullptr);
|
||||
}
|
||||
|
||||
// Draw the particle system using the update vertex buffer
|
||||
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
command_buffer.setViewport(0, {{0.0f, 0.0f, static_cast<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f}});
|
||||
command_buffer.setScissor(0, {{{0, 0}, extent}});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphics.pipeline);
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, graphics.pipeline_layout, 0, graphics.descriptor_set, nullptr);
|
||||
command_buffer.bindVertexBuffers(0, compute.storage_buffer->get_handle(), {0});
|
||||
command_buffer.draw(compute.ubo.particle_count, 1, 0, 0);
|
||||
draw_ui(command_buffer);
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
// Release barrier
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
vk::BufferMemoryBarrier buffer_barrier{.srcAccessMask = vk::AccessFlagBits::eVertexAttributeRead,
|
||||
.srcQueueFamilyIndex = graphics.queue_family_index,
|
||||
.dstQueueFamilyIndex = compute.queue_family_index,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
|
||||
command_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eComputeShader, {}, nullptr, buffer_barrier, nullptr);
|
||||
}
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPComputeNBody::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
update_compute_uniform_buffers(delta_time);
|
||||
if (camera.updated)
|
||||
{
|
||||
update_graphics_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPComputeNBody::build_compute_command_buffer()
|
||||
{
|
||||
compute.command_buffer.begin(vk::CommandBufferBeginInfo());
|
||||
|
||||
// Acquire
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
vk::BufferMemoryBarrier buffer_barrier{.dstAccessMask = vk::AccessFlagBits::eShaderWrite,
|
||||
.srcQueueFamilyIndex = graphics.queue_family_index,
|
||||
.dstQueueFamilyIndex = compute.queue_family_index,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
|
||||
compute.command_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader, {}, nullptr, buffer_barrier, nullptr);
|
||||
}
|
||||
|
||||
// First pass: Calculate particle movement
|
||||
// -------------------------------------------------------------------------------------------------------
|
||||
compute.command_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, compute.pipeline_calculate);
|
||||
compute.command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, compute.pipeline_layout, 0, compute.descriptor_set, nullptr);
|
||||
compute.command_buffer.dispatch(compute.ubo.particle_count / compute.work_group_size, 1, 1);
|
||||
|
||||
// Add memory barrier to ensure that the computer shader has finished writing to the buffer
|
||||
vk::BufferMemoryBarrier memory_barrier{.srcAccessMask = vk::AccessFlagBits::eShaderWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eShaderRead,
|
||||
.srcQueueFamilyIndex = vk::QueueFamilyIgnored,
|
||||
.dstQueueFamilyIndex = vk::QueueFamilyIgnored,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
|
||||
compute.command_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eComputeShader, {}, nullptr, memory_barrier, nullptr);
|
||||
|
||||
// Second pass: Integrate particles
|
||||
// -------------------------------------------------------------------------------------------------------
|
||||
compute.command_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, compute.pipeline_integrate);
|
||||
compute.command_buffer.dispatch(compute.ubo.particle_count / compute.work_group_size, 1, 1);
|
||||
|
||||
// Release
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
vk::BufferMemoryBarrier buffer_barrier{.srcAccessMask = vk::AccessFlagBits::eShaderWrite,
|
||||
.srcQueueFamilyIndex = compute.queue_family_index,
|
||||
.dstQueueFamilyIndex = graphics.queue_family_index,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
|
||||
compute.command_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, buffer_barrier, nullptr);
|
||||
}
|
||||
|
||||
compute.command_buffer.end();
|
||||
}
|
||||
|
||||
void HPPComputeNBody::build_compute_transfer_command_buffer(vk::CommandBuffer command_buffer) const
|
||||
{
|
||||
command_buffer.begin(vk::CommandBufferBeginInfo());
|
||||
|
||||
vk::BufferMemoryBarrier acquire_buffer_barrier{.dstAccessMask = vk::AccessFlagBits::eShaderWrite,
|
||||
.srcQueueFamilyIndex = graphics.queue_family_index,
|
||||
.dstQueueFamilyIndex = compute.queue_family_index,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
command_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader, {}, nullptr, acquire_buffer_barrier, nullptr);
|
||||
|
||||
vk::BufferMemoryBarrier release_buffer_barrier{.srcAccessMask = vk::AccessFlagBits::eShaderWrite,
|
||||
.srcQueueFamilyIndex = compute.queue_family_index,
|
||||
.dstQueueFamilyIndex = graphics.queue_family_index,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
command_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, release_buffer_barrier, nullptr);
|
||||
|
||||
// Copied from Device::flush_command_buffer, which we can't use because it would be
|
||||
// working with the wrong command pool
|
||||
command_buffer.end();
|
||||
}
|
||||
|
||||
void HPPComputeNBody::build_copy_command_buffer(vk::CommandBuffer command_buffer, vk::Buffer staging_buffer, vk::DeviceSize buffer_size) const
|
||||
{
|
||||
command_buffer.begin(vk::CommandBufferBeginInfo());
|
||||
command_buffer.copyBuffer(staging_buffer, compute.storage_buffer->get_handle(), {{0, 0, buffer_size}});
|
||||
// Execute a transfer to the compute queue, if necessary
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
vk::BufferMemoryBarrier buffer_barrier{.srcAccessMask = vk::AccessFlagBits::eVertexAttributeRead,
|
||||
.srcQueueFamilyIndex = graphics.queue_family_index,
|
||||
.dstQueueFamilyIndex = compute.queue_family_index,
|
||||
.buffer = compute.storage_buffer->get_handle(),
|
||||
.size = compute.storage_buffer->get_size()};
|
||||
|
||||
command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eComputeShader, {}, nullptr, buffer_barrier, nullptr);
|
||||
}
|
||||
command_buffer.end();
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPComputeNBody::create_compute_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{{0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute},
|
||||
{1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute}}};
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
||||
}
|
||||
|
||||
vk::Pipeline HPPComputeNBody::create_compute_pipeline(vk::PipelineShaderStageCreateInfo const &stage)
|
||||
{
|
||||
vk::ComputePipelineCreateInfo compute_pipeline_create_info{.stage = stage, .layout = compute.pipeline_layout};
|
||||
|
||||
vk::Result result;
|
||||
vk::Pipeline pipeline;
|
||||
std::tie(result, pipeline) = get_device().get_handle().createComputePipeline(pipeline_cache, compute_pipeline_create_info);
|
||||
assert(result == vk::Result::eSuccess);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPComputeNBody::create_descriptor_pool()
|
||||
{
|
||||
std::array<vk::DescriptorPoolSize, 3> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 2},
|
||||
{vk::DescriptorType::eStorageBuffer, 1},
|
||||
{vk::DescriptorType::eCombinedImageSampler, 2}}};
|
||||
|
||||
return get_device().get_handle().createDescriptorPool(
|
||||
{.maxSets = 2, .poolSizeCount = static_cast<uint32_t>(pool_sizes.size()), .pPoolSizes = pool_sizes.data()});
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPComputeNBody::create_graphics_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> bindings = {{{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex}}};
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
||||
}
|
||||
|
||||
vk::Pipeline HPPComputeNBody::create_graphics_pipeline()
|
||||
{
|
||||
// Load shaders
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("compute_nbody", "particle.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("compute_nbody", "particle.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex bindings and attributes
|
||||
vk::VertexInputBindingDescription vertex_input_bindings{0, sizeof(Particle), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {
|
||||
{{0, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, pos)}, // Location 0 : Position
|
||||
{1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, vel)}}}; // Location 1 : Velocity
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input_state{.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &vertex_input_bindings,
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size()),
|
||||
.pVertexAttributeDescriptions = vertex_input_attributes.data()};
|
||||
|
||||
// Additive blending
|
||||
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};
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
||||
depth_stencil_state.depthTestEnable = false;
|
||||
depth_stencil_state.depthWriteEnable = false;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eAlways;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
||||
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::ePointList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
graphics.pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPComputeNBody::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
std::array<vk::PipelineStageFlags, 2> graphics_wait_stage_masks = {vk::PipelineStageFlagBits::eVertexInput,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput};
|
||||
std::array<vk::Semaphore, 2> graphics_wait_semaphores = {compute.semaphore, semaphores.acquired_image_ready};
|
||||
std::array<vk::Semaphore, 2> graphics_signal_semaphores = {graphics.semaphore, semaphores.render_complete};
|
||||
|
||||
// Submit graphics commands
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
submit_info.setWaitSemaphores(graphics_wait_semaphores);
|
||||
submit_info.setWaitDstStageMask(graphics_wait_stage_masks);
|
||||
submit_info.setSignalSemaphores(graphics_signal_semaphores);
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
|
||||
// Submit compute commands, waiting for rendering finished
|
||||
vk::PipelineStageFlags wait_stage_mask = vk::PipelineStageFlagBits::eComputeShader;
|
||||
vk::SubmitInfo compute_submit_info{.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &graphics.semaphore,
|
||||
.pWaitDstStageMask = &wait_stage_mask,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &compute.command_buffer,
|
||||
.signalSemaphoreCount = 1,
|
||||
.pSignalSemaphores = &compute.semaphore};
|
||||
compute.queue.submit(compute_submit_info);
|
||||
}
|
||||
|
||||
void HPPComputeNBody::initializeCamera()
|
||||
{
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Z-Near and Z-Far are flipped
|
||||
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 512.0f, 0.1f);
|
||||
camera.set_rotation(glm::vec3(-26.0f, 75.0f, 0.0f));
|
||||
camera.set_translation(glm::vec3(0.0f, 0.0f, -14.0f));
|
||||
camera.translation_speed = 2.5f;
|
||||
}
|
||||
|
||||
void HPPComputeNBody::load_assets()
|
||||
{
|
||||
textures.particle = load_texture("textures/particle_rgba.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
textures.gradient = load_texture("textures/particle_gradient_rgba.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
}
|
||||
|
||||
void HPPComputeNBody::prepare_compute()
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
compute.queue_family_index = vkb::common::get_queue_family_index(get_device().get_gpu().get_queue_family_properties(), vk::QueueFlagBits::eCompute);
|
||||
|
||||
vk::PhysicalDeviceLimits const &limits = get_device().get_gpu().get_properties().limits;
|
||||
// Not all implementations support a work group size of 256, so we need to check with the device limits
|
||||
compute.work_group_size = std::min<uint32_t>(256, limits.maxComputeWorkGroupSize[0]);
|
||||
// Same for shared data size for passing data between shader invocations
|
||||
compute.shared_data_size = std::min<uint32_t>(1024, limits.maxComputeSharedMemorySize / sizeof(glm::vec4));
|
||||
|
||||
prepare_compute_storage_buffers();
|
||||
|
||||
// Compute shader uniform buffer block
|
||||
compute.uniform_buffer =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(compute.ubo), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
update_compute_uniform_buffers(1.0f);
|
||||
|
||||
// Get compute queue
|
||||
// Compute pipelines are created separate from graphics pipelines even if they use the same queue (family index)
|
||||
compute.queue = device.getQueue(compute.queue_family_index, 0);
|
||||
|
||||
compute.descriptor_set_layout = create_compute_descriptor_set_layout();
|
||||
|
||||
compute.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, compute.descriptor_set_layout);
|
||||
update_compute_descriptor_set();
|
||||
compute.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &compute.descriptor_set_layout});
|
||||
|
||||
// create the compute pipelines
|
||||
// 1st pass - Particle movement calculations
|
||||
{
|
||||
vk::PipelineShaderStageCreateInfo stage = load_shader("compute_nbody", "particle_calculate.comp.spv", vk::ShaderStageFlagBits::eCompute);
|
||||
|
||||
// Set some shader parameters via specialization constants
|
||||
struct MovementSpecializationData
|
||||
{
|
||||
uint32_t workgroup_size;
|
||||
uint32_t shared_data_size;
|
||||
float gravity;
|
||||
float power;
|
||||
float soften;
|
||||
};
|
||||
|
||||
std::array<vk::SpecializationMapEntry, 5> movement_specialization_map_entries = {
|
||||
{{0, offsetof(MovementSpecializationData, workgroup_size), sizeof(uint32_t)},
|
||||
{1, offsetof(MovementSpecializationData, shared_data_size), sizeof(uint32_t)},
|
||||
{2, offsetof(MovementSpecializationData, gravity), sizeof(float)},
|
||||
{3, offsetof(MovementSpecializationData, power), sizeof(float)},
|
||||
{4, offsetof(MovementSpecializationData, soften), sizeof(float)}}};
|
||||
|
||||
MovementSpecializationData movement_specialization_data{compute.work_group_size, compute.shared_data_size, 0.002f, 0.75f, 0.05f};
|
||||
|
||||
vk::SpecializationInfo specialization_info{static_cast<uint32_t>(movement_specialization_map_entries.size()),
|
||||
movement_specialization_map_entries.data(),
|
||||
sizeof(movement_specialization_data),
|
||||
&movement_specialization_data};
|
||||
|
||||
stage.pSpecializationInfo = &specialization_info;
|
||||
|
||||
compute.pipeline_calculate = create_compute_pipeline(stage);
|
||||
}
|
||||
|
||||
// 2nd pass - Particle integration
|
||||
{
|
||||
vk::PipelineShaderStageCreateInfo stage = load_shader("compute_nbody", "particle_integrate.comp.spv", vk::ShaderStageFlagBits::eCompute);
|
||||
|
||||
vk::SpecializationMapEntry integration_specialization_entry{0, 0, sizeof(compute.work_group_size)};
|
||||
vk::SpecializationInfo specialization_info{1, &integration_specialization_entry, sizeof(compute.work_group_size), &compute.work_group_size};
|
||||
stage.pSpecializationInfo = &specialization_info;
|
||||
|
||||
compute.pipeline_integrate = create_compute_pipeline(stage);
|
||||
}
|
||||
|
||||
// Separate command pool as queue family for compute may be different than graphics
|
||||
compute.command_pool = device.createCommandPool({.queueFamilyIndex = compute.queue_family_index});
|
||||
|
||||
// Create a command buffer for compute operations
|
||||
compute.command_buffer = vkb::common::allocate_command_buffer(device, compute.command_pool);
|
||||
|
||||
// Semaphore for compute & graphics sync
|
||||
compute.semaphore = device.createSemaphore({});
|
||||
|
||||
// Signal the semaphore
|
||||
vkb::common::submit_and_wait(device, queue, {}, {compute.semaphore});
|
||||
|
||||
// Build a single command buffer containing the compute dispatch commands
|
||||
build_compute_command_buffer();
|
||||
|
||||
// If necessary, acquire and immediately release the storage buffer, so that the initial acquire
|
||||
// from the graphics command buffers are matched up properly.
|
||||
if (graphics.queue_family_index != compute.queue_family_index)
|
||||
{
|
||||
// Create a transient command buffer for setting up the initial buffer transfer state
|
||||
vk::CommandBuffer transfer_command = vkb::common::allocate_command_buffer(device, compute.command_pool);
|
||||
|
||||
build_compute_transfer_command_buffer(transfer_command);
|
||||
|
||||
// Submit and wait for compute commands
|
||||
vkb::common::submit_and_wait(device, compute.queue, {transfer_command});
|
||||
|
||||
// free the transfer command buffer
|
||||
device.freeCommandBuffers(compute.command_pool, transfer_command);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup and fill the compute shader storage buffers containing the particles
|
||||
void HPPComputeNBody::prepare_compute_storage_buffers()
|
||||
{
|
||||
#if 0
|
||||
std::vector<glm::vec3> attractors = {
|
||||
glm::vec3(2.5f, 1.5f, 0.0f),
|
||||
glm::vec3(-2.5f, -1.5f, 0.0f),
|
||||
};
|
||||
#else
|
||||
std::vector<glm::vec3> attractors = {
|
||||
glm::vec3(5.0f, 0.0f, 0.0f),
|
||||
glm::vec3(-5.0f, 0.0f, 0.0f),
|
||||
glm::vec3(0.0f, 0.0f, 5.0f),
|
||||
glm::vec3(0.0f, 0.0f, -5.0f),
|
||||
glm::vec3(0.0f, 4.0f, 0.0f),
|
||||
glm::vec3(0.0f, -8.0f, 0.0f),
|
||||
};
|
||||
#endif
|
||||
|
||||
compute.ubo.particle_count = static_cast<uint32_t>(attractors.size()) * PARTICLES_PER_ATTRACTOR;
|
||||
|
||||
// Initial particle positions
|
||||
std::vector<Particle> particle_buffer(compute.ubo.particle_count);
|
||||
|
||||
std::default_random_engine rnd_engine(lock_simulation_speed ? 0 : static_cast<unsigned>(time(nullptr)));
|
||||
std::normal_distribution<float> rnd_distribution(0.0f, 1.0f);
|
||||
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(attractors.size()); i++)
|
||||
{
|
||||
for (uint32_t j = 0; j < PARTICLES_PER_ATTRACTOR; j++)
|
||||
{
|
||||
Particle &particle = particle_buffer[i * PARTICLES_PER_ATTRACTOR + j];
|
||||
|
||||
// First particle in group as heavy center of gravity
|
||||
if (j == 0)
|
||||
{
|
||||
particle.pos = glm::vec4(attractors[i] * 1.5f, 90000.0f);
|
||||
particle.vel = glm::vec4(glm::vec4(0.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Position
|
||||
glm::vec3 position(attractors[i] +
|
||||
glm::vec3(rnd_distribution(rnd_engine), rnd_distribution(rnd_engine), rnd_distribution(rnd_engine)) * 0.75f);
|
||||
float len = glm::length(glm::normalize(position - attractors[i]));
|
||||
position.y *= 2.0f - (len * len);
|
||||
|
||||
// Velocity
|
||||
glm::vec3 angular = glm::vec3(0.5f, 1.5f, 0.5f) * (((i % 2) == 0) ? 1.0f : -1.0f);
|
||||
glm::vec3 velocity = glm::cross((position - attractors[i]), angular) +
|
||||
glm::vec3(rnd_distribution(rnd_engine), rnd_distribution(rnd_engine), rnd_distribution(rnd_engine) * 0.025f);
|
||||
|
||||
float mass = (rnd_distribution(rnd_engine) * 0.5f + 0.5f) * 75.0f;
|
||||
particle.pos = glm::vec4(position, mass);
|
||||
particle.vel = glm::vec4(velocity, 0.0f);
|
||||
}
|
||||
|
||||
// Color gradient offset
|
||||
particle.vel.w = static_cast<float>(i) * 1.0f / static_cast<uint32_t>(attractors.size());
|
||||
}
|
||||
}
|
||||
|
||||
vk::DeviceSize storage_buffer_size = particle_buffer.size() * sizeof(Particle);
|
||||
|
||||
// Staging
|
||||
// SSBO won't be changed on the host after upload so copy to device local memory
|
||||
vkb::core::BufferCpp staging_buffer = vkb::core::BufferCpp::create_staging_buffer(get_device(), particle_buffer);
|
||||
|
||||
compute.storage_buffer = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
||||
storage_buffer_size,
|
||||
vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eTransferDst,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
// Copy from staging buffer to storage buffer
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(get_device().get_handle(), get_device().get_command_pool().get_handle());
|
||||
|
||||
build_copy_command_buffer(copy_command, staging_buffer.get_handle(), storage_buffer_size);
|
||||
|
||||
vkb::common::submit_and_wait(device, queue, {copy_command});
|
||||
|
||||
device.freeCommandBuffers(get_device().get_command_pool().get_handle(), copy_command);
|
||||
}
|
||||
|
||||
void HPPComputeNBody::prepare_graphics()
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
graphics.queue_family_index = vkb::common::get_queue_family_index(get_device().get_gpu().get_queue_family_properties(), vk::QueueFlagBits::eGraphics);
|
||||
|
||||
// Vertex shader uniform buffer block
|
||||
graphics.uniform_buffer =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(graphics.ubo), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
update_graphics_uniform_buffers();
|
||||
|
||||
graphics.descriptor_set_layout = create_graphics_descriptor_set_layout();
|
||||
graphics.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, graphics.descriptor_set_layout);
|
||||
update_graphics_descriptor_set();
|
||||
graphics.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &graphics.descriptor_set_layout});
|
||||
|
||||
graphics.pipeline = create_graphics_pipeline();
|
||||
|
||||
// Semaphore for compute & graphics sync
|
||||
graphics.semaphore = device.createSemaphore({});
|
||||
}
|
||||
|
||||
void HPPComputeNBody::update_compute_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo storage_buffer_descriptor{compute.storage_buffer->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorBufferInfo uniform_buffer_descriptor{compute.uniform_buffer->get_handle(), 0, vk::WholeSize};
|
||||
std::array<vk::WriteDescriptorSet, 2> compute_write_descriptor_sets = {{// Binding 0 : Particle position storage buffer
|
||||
{.dstSet = compute.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageBuffer,
|
||||
.pBufferInfo = &storage_buffer_descriptor},
|
||||
// Binding 1 : Uniform buffer
|
||||
{.dstSet = compute.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &uniform_buffer_descriptor}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(compute_write_descriptor_sets, nullptr);
|
||||
}
|
||||
|
||||
void HPPComputeNBody::update_compute_uniform_buffers(float delta_time)
|
||||
{
|
||||
compute.ubo.delta_time = paused ? 0.0f : delta_time;
|
||||
compute.uniform_buffer->convert_and_update(compute.ubo);
|
||||
}
|
||||
|
||||
void HPPComputeNBody::update_graphics_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo buffer_descriptor{graphics.uniform_buffer->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
vk::DescriptorImageInfo particle_image_descriptor{textures.particle.sampler,
|
||||
textures.particle.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
textures.particle.image->get_vk_image_view().get_format())};
|
||||
vk::DescriptorImageInfo gradient_image_descriptor{textures.gradient.sampler,
|
||||
textures.gradient.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
textures.gradient.image->get_vk_image_view().get_format())};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> write_descriptor_sets = {{{.dstSet = graphics.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &particle_image_descriptor},
|
||||
{.dstSet = graphics.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &gradient_image_descriptor},
|
||||
{.dstSet = graphics.descriptor_set,
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &buffer_descriptor}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, nullptr);
|
||||
}
|
||||
|
||||
void HPPComputeNBody::update_graphics_uniform_buffers()
|
||||
{
|
||||
graphics.ubo.projection = camera.matrices.perspective;
|
||||
graphics.ubo.view = camera.matrices.view;
|
||||
graphics.ubo.screenDim = glm::vec2(static_cast<float>(extent.width), static_cast<float>(extent.height));
|
||||
graphics.uniform_buffer->convert_and_update(graphics.ubo);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_compute_nbody()
|
||||
{
|
||||
return std::make_unique<HPPComputeNBody>();
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/* Copyright (c) 2022-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Compute shader N-body simulation using two passes and shared compute shader memory, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Lower particle count on Android for performance reasons
|
||||
# define PARTICLES_PER_ATTRACTOR 3 * 1024
|
||||
#else
|
||||
# define PARTICLES_PER_ATTRACTOR 4 * 1024
|
||||
#endif
|
||||
|
||||
class HPPComputeNBody : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPComputeNBody();
|
||||
~HPPComputeNBody();
|
||||
|
||||
private:
|
||||
// Resources for the compute part of the example
|
||||
struct ComputeUBO
|
||||
{ // Compute shader uniform block object
|
||||
float delta_time; // Frame delta time
|
||||
int32_t particle_count;
|
||||
};
|
||||
|
||||
struct Compute
|
||||
{
|
||||
vk::CommandBuffer command_buffer; // Command buffer storing the dispatch commands and barriers
|
||||
vk::CommandPool command_pool; // Use a separate command pool (queue family may differ from the one used for graphics)
|
||||
vk::DescriptorSet descriptor_set; // Compute shader bindings
|
||||
vk::DescriptorSetLayout descriptor_set_layout; // Compute shader binding layout
|
||||
vk::Pipeline pipeline_calculate; // Compute pipeline for N-Body velocity calculation (1st pass)
|
||||
vk::Pipeline pipeline_integrate; // Compute pipeline for euler integration (2nd pass)
|
||||
vk::PipelineLayout pipeline_layout; // Layout of the compute pipeline
|
||||
vk::Queue queue; // Separate queue for compute commands (queue family may differ from the one used for graphics)
|
||||
uint32_t queue_family_index = ~0;
|
||||
vk::Semaphore semaphore; // Execution dependency between compute & graphic submission
|
||||
uint32_t shared_data_size = 1024;
|
||||
std::unique_ptr<vkb::core::BufferCpp> storage_buffer; // (Shader) storage buffer object containing the particles
|
||||
ComputeUBO ubo;
|
||||
std::unique_ptr<vkb::core::BufferCpp> uniform_buffer; // Uniform buffer object containing particle system parameters
|
||||
uint32_t work_group_size = 128;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
storage_buffer.reset();
|
||||
uniform_buffer.reset();
|
||||
device.destroyPipeline(pipeline_calculate);
|
||||
device.destroyPipeline(pipeline_integrate);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
device.destroySemaphore(semaphore);
|
||||
device.freeCommandBuffers(command_pool, command_buffer);
|
||||
device.destroyCommandPool(command_pool);
|
||||
}
|
||||
};
|
||||
|
||||
// Resources for the graphics part of the example
|
||||
struct GraphicsUBO
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
glm::vec2 screenDim;
|
||||
};
|
||||
|
||||
struct Graphics
|
||||
{
|
||||
vk::DescriptorSet descriptor_set; // Particle system rendering shader bindings
|
||||
vk::DescriptorSetLayout descriptor_set_layout; // Particle system rendering shader binding layout
|
||||
vk::Pipeline pipeline; // Particle rendering pipeline
|
||||
vk::PipelineLayout pipeline_layout; // Layout of the graphics pipeline
|
||||
uint32_t queue_family_index = ~0;
|
||||
vk::Semaphore semaphore; // Execution dependency between compute & graphic submission
|
||||
GraphicsUBO ubo;
|
||||
std::unique_ptr<vkb::core::BufferCpp> uniform_buffer; // Contains scene matrices
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
uniform_buffer.reset();
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
device.destroySemaphore(semaphore);
|
||||
}
|
||||
};
|
||||
|
||||
// SSBO particle declaration
|
||||
struct Particle
|
||||
{
|
||||
glm::vec4 pos; // xyz = position, w = mass
|
||||
glm::vec4 vel; // xyz = velocity, w = gradient texture position
|
||||
};
|
||||
|
||||
struct Textures
|
||||
{
|
||||
HPPTexture gradient;
|
||||
HPPTexture particle;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(particle.sampler);
|
||||
device.destroySampler(gradient.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void render(float delta_time) override;
|
||||
|
||||
void build_compute_command_buffer();
|
||||
void build_compute_transfer_command_buffer(vk::CommandBuffer command_buffer) const;
|
||||
void build_copy_command_buffer(vk::CommandBuffer command_buffer, vk::Buffer staging_buffer, vk::DeviceSize buffer_size) const;
|
||||
vk::DescriptorSetLayout create_compute_descriptor_set_layout();
|
||||
vk::Pipeline create_compute_pipeline(vk::PipelineShaderStageCreateInfo const &stage);
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::DescriptorSetLayout create_graphics_descriptor_set_layout();
|
||||
vk::Pipeline create_graphics_pipeline();
|
||||
void draw();
|
||||
void initializeCamera();
|
||||
void load_assets();
|
||||
void prepare_compute();
|
||||
void prepare_compute_storage_buffers();
|
||||
void prepare_graphics();
|
||||
void update_compute_descriptor_set();
|
||||
void update_compute_uniform_buffers(float delta_time);
|
||||
void update_graphics_descriptor_set();
|
||||
void update_graphics_uniform_buffers();
|
||||
|
||||
private:
|
||||
Compute compute;
|
||||
Graphics graphics;
|
||||
Textures textures;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_compute_nbody();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2021-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Dynamic uniform buffers"
|
||||
DESCRIPTION "Demonstrates the use of dynamic offsets into one single uniform buffers for rendering multiple objects, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_uniform_buffers/glsl/base.vert"
|
||||
"dynamic_uniform_buffers/glsl/base.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"dynamic_uniform_buffers/hlsl/base.vert.hlsl"
|
||||
"dynamic_uniform_buffers/hlsl/base.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= HPP Dynamic Uniform Buffers
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_dynamic_uniform_buffers[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/dynamic_uniform_buffers[Dynamic Uniform buffers] that illustrates the usage of the C{pp} bindings of Vulkan provided by vulkan.hpp.
|
||||
@@ -1,414 +0,0 @@
|
||||
/* Copyright (c) 2021-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Demonstrates the use of dynamic uniform buffers, using vulkan.hpp
|
||||
*
|
||||
* Instead of using one uniform buffer per-object, this example allocates one big uniform buffer
|
||||
* with respect to the alignment reported by the device via minUniformBufferOffsetAlignment that
|
||||
* contains all matrices for the objects in the scene.
|
||||
*
|
||||
* The used descriptor type vk::DescriptorType::eUniformBufferDynamic then allows to set a dynamic
|
||||
* offset used to pass data from the single uniform buffer to the connected shader binding point.
|
||||
*/
|
||||
|
||||
#include "hpp_dynamic_uniform_buffers.h"
|
||||
#include <benchmark_mode/benchmark_mode.h>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
|
||||
HPPDynamicUniformBuffers::HPPDynamicUniformBuffers()
|
||||
{
|
||||
title = "HPP Dynamic uniform buffers";
|
||||
}
|
||||
|
||||
HPPDynamicUniformBuffers ::~HPPDynamicUniformBuffers()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
if (ubo_data_dynamic.model)
|
||||
{
|
||||
aligned_free(ubo_data_dynamic.model);
|
||||
}
|
||||
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper functions for aligned memory allocation
|
||||
// There is currently no standard for this in C++ that works across all platforms and vendors, so we abstract this
|
||||
void *HPPDynamicUniformBuffers::aligned_alloc(size_t size, size_t alignment)
|
||||
{
|
||||
void *data = nullptr;
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
data = _aligned_malloc(size, alignment);
|
||||
#else
|
||||
int res = posix_memalign(&data, alignment, size);
|
||||
if (res != 0)
|
||||
{
|
||||
data = nullptr;
|
||||
}
|
||||
#endif
|
||||
return data;
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::aligned_free(void *data)
|
||||
{
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
_aligned_free(data);
|
||||
#else
|
||||
free(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HPPDynamicUniformBuffers::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
prepare_camera();
|
||||
generate_cube();
|
||||
prepare_uniform_buffers();
|
||||
descriptor_set_layout = create_descriptor_set_layout();
|
||||
pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &descriptor_set_layout});
|
||||
pipeline = create_pipeline();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
descriptor_set = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, descriptor_set_layout);
|
||||
update_descriptor_set();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool HPPDynamicUniformBuffers::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
HPPApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::build_command_buffers()
|
||||
{
|
||||
vk::DeviceSize offset = 0;
|
||||
|
||||
std::array<vk::ClearValue, 2> clear_values = {default_clear_color, vk::ClearDepthStencilValue{0.0f, 0}};
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
|
||||
for (size_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
vk::CommandBuffer command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(vk::CommandBufferBeginInfo());
|
||||
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
command_buffer.setViewport(0, {{0.0f, 0.0f, static_cast<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f}});
|
||||
command_buffer.setScissor(0, {{{0, 0}, extent}});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
command_buffer.bindVertexBuffers(0, static_cast<vk::Buffer>(vertex_buffer->get_handle()), offset);
|
||||
command_buffer.bindIndexBuffer(index_buffer->get_handle(), 0, vk::IndexType::eUint32);
|
||||
|
||||
// Render multiple objects using different model matrices by dynamically offsetting into one uniform buffer
|
||||
for (uint32_t j = 0; j < OBJECT_INSTANCES; j++)
|
||||
{
|
||||
// One dynamic offset per dynamic descriptor to offset into the ubo containing all model matrices
|
||||
uint32_t dynamic_offset = j * static_cast<uint32_t>(dynamic_alignment);
|
||||
|
||||
// Bind the descriptor set for rendering a mesh using the dynamic offset
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set, dynamic_offset);
|
||||
command_buffer.drawIndexed(index_count, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
draw_ui(command_buffer);
|
||||
command_buffer.endRenderPass();
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
if (!paused)
|
||||
{
|
||||
update_dynamic_uniform_buffer(delta_time);
|
||||
}
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPDynamicUniformBuffers::create_descriptor_pool()
|
||||
{
|
||||
// Example uses one ubo, on dynamic ubo, and one combined image sampler
|
||||
std::array<vk::DescriptorPoolSize, 3> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 1},
|
||||
{vk::DescriptorType::eUniformBufferDynamic, 1},
|
||||
{vk::DescriptorType::eCombinedImageSampler, 1}}};
|
||||
return get_device().get_handle().createDescriptorPool(
|
||||
{.maxSets = 2, .poolSizeCount = static_cast<uint32_t>(pool_sizes.size()), .pPoolSizes = pool_sizes.data()});
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPDynamicUniformBuffers::create_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> bindings = {{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
{1, vk::DescriptorType::eUniformBufferDynamic, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
return get_device().get_handle().createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
||||
}
|
||||
|
||||
vk::Pipeline HPPDynamicUniformBuffers::create_pipeline()
|
||||
{
|
||||
// Load shaders
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("dynamic_uniform_buffers", "base.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("dynamic_uniform_buffers", "base.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex bindings and attributes
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(Vertex), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {
|
||||
{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)}, // Location 0 : Position
|
||||
{1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)}}}; // Location 1 : Color
|
||||
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()};
|
||||
|
||||
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.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = true;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eGreater;
|
||||
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
// Submit to queue
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::generate_cube()
|
||||
{
|
||||
// Setup vertices indices for a colored cube
|
||||
std::vector<Vertex> vertices = {
|
||||
{{-1.0f, -1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}},
|
||||
{{1.0f, -1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}},
|
||||
{{1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}},
|
||||
{{-1.0f, -1.0f, -1.0f}, {1.0f, 0.0f, 0.0f}},
|
||||
{{1.0f, -1.0f, -1.0f}, {0.0f, 1.0f, 0.0f}},
|
||||
{{1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}},
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
std::vector<uint32_t> indices = { 0, 1, 2, 2, 3, 0, 1, 5, 6, 6, 2, 1, 7, 6, 5, 5, 4, 7,
|
||||
4, 0, 3, 3, 7, 4, 4, 5, 1, 1, 0, 4, 3, 2, 6, 6, 7, 3 };
|
||||
// clang-format on
|
||||
|
||||
index_count = static_cast<uint32_t>(indices.size());
|
||||
|
||||
auto vertex_buffer_size = vertices.size() * sizeof(Vertex);
|
||||
auto index_buffer_size = indices.size() * sizeof(uint32_t);
|
||||
|
||||
// Create buffers
|
||||
// For the sake of simplicity we won't stage the vertex data to the gpu memory
|
||||
// Vertex buffer
|
||||
vertex_buffer =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
vertex_buffer->update(vertices.data(), vertex_buffer_size);
|
||||
|
||||
index_buffer = std::make_unique<vkb::core::BufferCpp>(get_device(), index_buffer_size, vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
index_buffer->update(indices.data(), index_buffer_size);
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::prepare_camera()
|
||||
{
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position(glm::vec3(0.0f, 0.0f, -30.0f));
|
||||
camera.set_rotation(glm::vec3(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);
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void HPPDynamicUniformBuffers::prepare_uniform_buffers()
|
||||
{
|
||||
// Allocate data for the dynamic uniform buffer object
|
||||
// We allocate this manually as the alignment of the offset differs between GPUs
|
||||
|
||||
// Calculate required alignment based on minimum device offset alignment
|
||||
vk::DeviceSize min_ubo_alignment = get_device().get_gpu().get_handle().getProperties().limits.minUniformBufferOffsetAlignment;
|
||||
dynamic_alignment = sizeof(glm::mat4);
|
||||
if (min_ubo_alignment > 0)
|
||||
{
|
||||
dynamic_alignment = (dynamic_alignment + min_ubo_alignment - 1) & ~(min_ubo_alignment - 1);
|
||||
}
|
||||
|
||||
size_t buffer_size = OBJECT_INSTANCES * dynamic_alignment;
|
||||
|
||||
ubo_data_dynamic.model = static_cast<glm::mat4 *>(aligned_alloc(buffer_size, dynamic_alignment));
|
||||
assert(ubo_data_dynamic.model);
|
||||
|
||||
std::cout << "minUniformBufferOffsetAlignment = " << min_ubo_alignment << std::endl;
|
||||
std::cout << "dynamicAlignment = " << dynamic_alignment << std::endl;
|
||||
|
||||
// Vertex shader uniform buffer block
|
||||
|
||||
// Static shared uniform buffer object with projection and view matrix
|
||||
uniform_buffers.view =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(ubo_vs), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
uniform_buffers.dynamic =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), buffer_size, vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Prepare per-object matrices with offsets and random rotations
|
||||
std::default_random_engine rnd_engine(lock_simulation_speed ? 0 : static_cast<unsigned>(time(nullptr)));
|
||||
std::normal_distribution<float> rnd_dist(-1.0f, 1.0f);
|
||||
for (uint32_t i = 0; i < OBJECT_INSTANCES; i++)
|
||||
{
|
||||
rotations[i] = glm::vec3(rnd_dist(rnd_engine), rnd_dist(rnd_engine), rnd_dist(rnd_engine)) * 2.0f * glm::pi<float>();
|
||||
rotation_speeds[i] = glm::vec3(rnd_dist(rnd_engine), rnd_dist(rnd_engine), rnd_dist(rnd_engine));
|
||||
}
|
||||
|
||||
update_uniform_buffers();
|
||||
update_dynamic_uniform_buffer(0.0f, true);
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::update_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo view_buffer_descriptor{uniform_buffers.view->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorBufferInfo dynamic_buffer_descriptor{uniform_buffers.dynamic->get_handle(), 0, dynamic_alignment};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {{// Binding 0 : Projection/View matrix uniform buffer
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &view_buffer_descriptor},
|
||||
// Binding 1 : Instance matrix as dynamic uniform buffer
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBufferDynamic,
|
||||
.pBufferInfo = &dynamic_buffer_descriptor}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::update_dynamic_uniform_buffer(float delta_time, bool force)
|
||||
{
|
||||
// Update at max. 60 fps
|
||||
animation_timer += delta_time;
|
||||
if ((animation_timer + 0.0025 < (1.0f / 60.0f)) && (!force))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Dynamic ubo with per-object model matrices indexed by offsets in the command buffer
|
||||
auto dim = static_cast<uint32_t>(pow(OBJECT_INSTANCES, (1.0f / 3.0f)));
|
||||
auto fdim = static_cast<float>(dim);
|
||||
glm::vec3 offset(5.0f);
|
||||
|
||||
for (uint32_t x = 0; x < dim; x++)
|
||||
{
|
||||
auto fx = static_cast<float>(x);
|
||||
for (uint32_t y = 0; y < dim; y++)
|
||||
{
|
||||
auto fy = static_cast<float>(y);
|
||||
for (uint32_t z = 0; z < dim; z++)
|
||||
{
|
||||
auto fz = static_cast<float>(z);
|
||||
auto index = x * dim * dim + y * dim + z;
|
||||
|
||||
// Aligned offset
|
||||
auto model_mat = reinterpret_cast<glm::mat4 *>((reinterpret_cast<uint64_t>(ubo_data_dynamic.model) + (index * dynamic_alignment)));
|
||||
|
||||
// Update rotations
|
||||
rotations[index] += animation_timer * rotation_speeds[index];
|
||||
|
||||
// Update matrices
|
||||
glm::vec3 pos(-((fdim * offset.x) / 2.0f) + offset.x / 2.0f + fx * offset.x,
|
||||
-((fdim * offset.y) / 2.0f) + offset.y / 2.0f + fy * offset.y,
|
||||
-((fdim * offset.z) / 2.0f) + offset.z / 2.0f + fz * offset.z);
|
||||
*model_mat = glm::translate(glm::mat4(1.0f), pos);
|
||||
*model_mat = glm::rotate(*model_mat, rotations[index].x, glm::vec3(1.0f, 1.0f, 0.0f));
|
||||
*model_mat = glm::rotate(*model_mat, rotations[index].y, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
*model_mat = glm::rotate(*model_mat, rotations[index].z, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animation_timer = 0.0f;
|
||||
|
||||
uniform_buffers.dynamic->update(ubo_data_dynamic.model, static_cast<size_t>(uniform_buffers.dynamic->get_size()));
|
||||
|
||||
// Flush to make changes visible to the device
|
||||
uniform_buffers.dynamic->flush();
|
||||
}
|
||||
|
||||
void HPPDynamicUniformBuffers::update_uniform_buffers()
|
||||
{
|
||||
// Fixed ubo with projection and view matrices
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.view = camera.matrices.view;
|
||||
|
||||
uniform_buffers.view->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_dynamic_uniform_buffers()
|
||||
{
|
||||
return std::make_unique<HPPDynamicUniformBuffers>();
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/* Copyright (c) 2021-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Demonstrates the use of dynamic uniform buffers, using vulkan.hpp
|
||||
*
|
||||
* Instead of using one uniform buffer per-object, this example allocates one big uniform buffer
|
||||
* with respect to the alignment reported by the device via minUniformBufferOffsetAlignment that
|
||||
* contains all matrices for the objects in the scene.
|
||||
*
|
||||
* The used descriptor type vk::DescriptorType::eUniformBufferDynamic then allows to set a dynamic
|
||||
* offset used to pass data from the single uniform buffer to the connected shader binding point.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
#define OBJECT_INSTANCES 125
|
||||
|
||||
class HPPDynamicUniformBuffers : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPDynamicUniformBuffers();
|
||||
~HPPDynamicUniformBuffers();
|
||||
|
||||
private:
|
||||
// One big uniform buffer that contains all matrices
|
||||
// Note that we need to manually allocate the data to cope for GPU-specific uniform buffer offset alignments
|
||||
struct UboDataDynamic
|
||||
{
|
||||
glm::mat4 *model = nullptr;
|
||||
};
|
||||
|
||||
struct UboVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
};
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferCpp> view;
|
||||
std::unique_ptr<vkb::core::BufferCpp> dynamic;
|
||||
};
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
float pos[3];
|
||||
float color[3];
|
||||
};
|
||||
|
||||
private:
|
||||
static void *aligned_alloc(size_t size, size_t alignment);
|
||||
static void aligned_free(void *data);
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::DescriptorSetLayout create_descriptor_set_layout();
|
||||
vk::Pipeline create_pipeline();
|
||||
void draw();
|
||||
void generate_cube();
|
||||
void prepare_camera();
|
||||
void prepare_uniform_buffers();
|
||||
void update_descriptor_set();
|
||||
void update_dynamic_uniform_buffer(float delta_time, bool force = false);
|
||||
void update_uniform_buffers();
|
||||
|
||||
private:
|
||||
float animation_timer = 0.0f;
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
size_t dynamic_alignment = 0;
|
||||
std::unique_ptr<vkb::core::BufferCpp> index_buffer;
|
||||
uint32_t index_count = 0;
|
||||
vk::Pipeline pipeline;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
glm::vec3 rotations[OBJECT_INSTANCES]; // Store random per-object rotations
|
||||
glm::vec3 rotation_speeds[OBJECT_INSTANCES];
|
||||
UboDataDynamic ubo_data_dynamic;
|
||||
UboVS ubo_vs;
|
||||
UniformBuffers uniform_buffers;
|
||||
std::unique_ptr<vkb::core::BufferCpp> vertex_buffer;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_dynamic_uniform_buffers();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2022-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP HDR"
|
||||
DESCRIPTION "High-dynamic-range rendering, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"hdr/glsl/composition.vert"
|
||||
"hdr/glsl/composition.frag"
|
||||
"hdr/glsl/bloom.vert"
|
||||
"hdr/glsl/bloom.frag"
|
||||
"hdr/glsl/gbuffer.vert"
|
||||
"hdr/glsl/gbuffer.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hdr/hlsl/composition.vert.hlsl"
|
||||
"hdr/hlsl/composition.frag.hlsl"
|
||||
"hdr/hlsl/bloom.vert.hlsl"
|
||||
"hdr/hlsl/bloom.frag.hlsl"
|
||||
"hdr/hlsl/gbuffer.vert.hlsl"
|
||||
"hdr/hlsl/gbuffer.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= HPP High dynamic range
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_hdr[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hdr[High dynamic range] that illustrates the usage of the C{pp} bindings of Vulkan provided by vulkan.hpp.
|
||||
@@ -1,741 +0,0 @@
|
||||
/* 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>();
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
/* Copyright (c) 2022-2024, 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
class HPPHDR : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPHDR();
|
||||
~HPPHDR();
|
||||
|
||||
private:
|
||||
struct Bloom
|
||||
{
|
||||
bool enabled = true;
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipelines[2];
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipelines[0]);
|
||||
device.destroyPipeline(pipelines[1]);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
}
|
||||
};
|
||||
|
||||
struct Composition
|
||||
{
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::DescriptorSet descriptor_set = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
}
|
||||
};
|
||||
|
||||
// Framebuffer for offscreen rendering
|
||||
struct FramebufferAttachment
|
||||
{
|
||||
vk::Format format = {};
|
||||
vk::Image image = {};
|
||||
vk::DeviceMemory mem = {};
|
||||
vk::ImageView view = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyImageView(view);
|
||||
device.destroyImage(image);
|
||||
device.freeMemory(mem);
|
||||
}
|
||||
};
|
||||
|
||||
struct FilterPass
|
||||
{
|
||||
vk::Extent2D extent = {};
|
||||
vk::Framebuffer framebuffer = {};
|
||||
FramebufferAttachment color = {};
|
||||
vk::RenderPass render_pass = {};
|
||||
vk::Sampler sampler = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
device.destroyRenderPass(render_pass);
|
||||
color.destroy(device);
|
||||
}
|
||||
};
|
||||
|
||||
struct Geometry
|
||||
{
|
||||
vk::DescriptorSet descriptor_set = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
std::vector<std::unique_ptr<vkb::scene_graph::components::HPPSubMesh>> meshes = {};
|
||||
|
||||
void destroy(vk::Device device, vk::DescriptorPool descriptor_pool)
|
||||
{
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
device.destroyPipeline(pipeline);
|
||||
}
|
||||
};
|
||||
|
||||
struct Models
|
||||
{
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
Geometry objects = {};
|
||||
Geometry skybox = {};
|
||||
std::vector<glm::mat4> transforms = {};
|
||||
int32_t object_index = 0;
|
||||
|
||||
void destroy(vk::Device device, vk::DescriptorPool descriptor_pool)
|
||||
{
|
||||
objects.destroy(device, descriptor_pool);
|
||||
skybox.destroy(device, descriptor_pool);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
}
|
||||
};
|
||||
|
||||
struct Offscreen
|
||||
{
|
||||
vk::Extent2D extent = {};
|
||||
vk::Framebuffer framebuffer = {};
|
||||
FramebufferAttachment color[2] = {};
|
||||
FramebufferAttachment depth = {};
|
||||
vk::RenderPass render_pass = {};
|
||||
vk::Sampler sampler = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
device.destroyRenderPass(render_pass);
|
||||
color[0].destroy(device);
|
||||
color[1].destroy(device);
|
||||
depth.destroy(device);
|
||||
}
|
||||
};
|
||||
|
||||
struct Textures
|
||||
{
|
||||
HPPTexture envmap;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(envmap.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
struct UBOMatrices
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skybox_modelview;
|
||||
glm::mat4 inverse_modelview;
|
||||
float modelscale = 0.05f;
|
||||
};
|
||||
|
||||
struct UBOParams
|
||||
{
|
||||
float exposure = 1.0f;
|
||||
};
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferCpp> matrices;
|
||||
std::unique_ptr<vkb::core::BufferCpp> params;
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
virtual void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
virtual void build_command_buffers() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
virtual void render(float delta_time) override;
|
||||
|
||||
vk::DeviceMemory allocate_memory(vk::Image image);
|
||||
FramebufferAttachment create_attachment(vk::Format format, vk::ImageUsageFlagBits usage);
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::Pipeline create_bloom_pipeline(uint32_t direction);
|
||||
vk::Pipeline create_composition_pipeline();
|
||||
vk::RenderPass create_filter_render_pass();
|
||||
vk::Image create_image(vk::Format format, vk::ImageUsageFlagBits usage);
|
||||
vk::Pipeline create_models_pipeline(uint32_t shaderType, vk::CullModeFlagBits cullMode, bool depthTestAndWrite);
|
||||
vk::RenderPass create_offscreen_render_pass();
|
||||
vk::RenderPass create_render_pass(std::vector<vk::AttachmentDescription> const &attachment_descriptions, vk::SubpassDescription const &subpass_description);
|
||||
void draw();
|
||||
void load_assets();
|
||||
void prepare_bloom();
|
||||
void prepare_camera();
|
||||
void prepare_composition();
|
||||
void prepare_models();
|
||||
void prepare_offscreen_buffer();
|
||||
void prepare_uniform_buffers();
|
||||
void update_composition_descriptor_set();
|
||||
void update_bloom_descriptor_set();
|
||||
void update_model_descriptor_set(vk::DescriptorSet descriptor_set);
|
||||
void update_params();
|
||||
void update_uniform_buffers();
|
||||
|
||||
private:
|
||||
Bloom bloom;
|
||||
Composition composition;
|
||||
bool display_skybox = true;
|
||||
FilterPass filter_pass;
|
||||
Models models;
|
||||
std::vector<std::string> object_names;
|
||||
Offscreen offscreen;
|
||||
Textures textures;
|
||||
UBOMatrices ubo_matrices;
|
||||
UBOParams ubo_params;
|
||||
UniformBuffers uniform_buffers;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_hdr();
|
||||
@@ -1,40 +0,0 @@
|
||||
# Copyright (c) 2021-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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Arm"
|
||||
NAME "HPP Hello Triangle"
|
||||
DESCRIPTION "An introduction into Vulkan and its respective objects using vulkan.hpp."
|
||||
SHADER_FILES_GLSL
|
||||
"hello_triangle/glsl/triangle.vert"
|
||||
"hello_triangle/glsl/triangle.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hello_triangle/hlsl/triangle.vert.hlsl"
|
||||
"hello_triangle/hlsl/triangle.frag.hlsl"
|
||||
SHADER_FILES_SLANG
|
||||
"hello_triangle/slang/triangle.vert.slang"
|
||||
"hello_triangle/slang/triangle.frag.slang")
|
||||
|
||||
if(${VKB_${FOLDER_NAME}})
|
||||
target_compile_definitions(${FOLDER_NAME} PUBLIC VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
||||
endif()
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= HPP Hello Triangle
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_hello_triangle[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hello_triangle[Hello Triangle] that illustrates the usage of the C{pp} bindings of Vulkan provided by vulkan.hpp.
|
||||
@@ -1,968 +0,0 @@
|
||||
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
* Copyright (c) 2024-2025, Arm Limited and Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "hpp_hello_triangle.h"
|
||||
|
||||
#include <common/hpp_error.h>
|
||||
#include <common/hpp_vk_common.h>
|
||||
#include <core/util/logging.hpp>
|
||||
#include <filesystem/legacy.h>
|
||||
#include <platform/window.h>
|
||||
|
||||
// Note: the default dispatcher is instantiated in hpp_api_vulkan_sample.cpp.
|
||||
// Even though, that file is not part of this sample, it's part of the sample-project!
|
||||
|
||||
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
||||
/// @brief A debug callback called from Vulkan validation layers.
|
||||
VKAPI_ATTR vk::Bool32 VKAPI_CALL debug_utils_messenger_callback(vk::DebugUtilsMessageSeverityFlagBitsEXT message_severity,
|
||||
vk::DebugUtilsMessageTypeFlagsEXT message_type,
|
||||
const vk::DebugUtilsMessengerCallbackDataEXT *callback_data,
|
||||
void *user_data)
|
||||
{
|
||||
// Log debug message
|
||||
if (message_severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning)
|
||||
{
|
||||
LOGW("{} - {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage);
|
||||
}
|
||||
else if (message_severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eError)
|
||||
{
|
||||
LOGE("{} - {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage);
|
||||
}
|
||||
return VK_FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Validates a list of required extensions, comparing it with the available ones.
|
||||
*
|
||||
* @param required A vector containing required extension names.
|
||||
* @param available A vk::ExtensionProperties object containing available extensions.
|
||||
* @return true if all required extensions are available
|
||||
* @return false otherwise
|
||||
*/
|
||||
bool validate_extensions(const std::vector<const char *> &required,
|
||||
const std::vector<vk::ExtensionProperties> &available)
|
||||
{
|
||||
// inner find_if gives true if the extension was not found
|
||||
// outer find_if gives true if none of the extensions were not found, that is if all extensions were found
|
||||
return std::ranges::find_if(required,
|
||||
[&available](auto extension) {
|
||||
return std::ranges::find_if(available,
|
||||
[&extension](auto const &ep) {
|
||||
return strcmp(ep.extensionName, extension) == 0;
|
||||
}) == available.end();
|
||||
}) == required.end();
|
||||
}
|
||||
|
||||
HPPHelloTriangle::HPPHelloTriangle()
|
||||
{
|
||||
}
|
||||
|
||||
HPPHelloTriangle::~HPPHelloTriangle()
|
||||
{
|
||||
// Don't release anything until the GPU is completely idle.
|
||||
device.waitIdle();
|
||||
|
||||
teardown_framebuffers();
|
||||
|
||||
for (auto &pfd : per_frame_data)
|
||||
{
|
||||
teardown_per_frame(pfd);
|
||||
}
|
||||
|
||||
per_frame_data.clear();
|
||||
|
||||
for (auto semaphore : recycled_semaphores)
|
||||
{
|
||||
device.destroySemaphore(semaphore);
|
||||
}
|
||||
|
||||
if (pipeline)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
}
|
||||
|
||||
if (pipeline_layout)
|
||||
{
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
}
|
||||
|
||||
if (render_pass)
|
||||
{
|
||||
device.destroyRenderPass(render_pass);
|
||||
}
|
||||
|
||||
for (auto image_view : swapchain_data.image_views)
|
||||
{
|
||||
device.destroyImageView(image_view);
|
||||
}
|
||||
|
||||
if (swapchain_data.swapchain)
|
||||
{
|
||||
device.destroySwapchainKHR(swapchain_data.swapchain);
|
||||
}
|
||||
|
||||
if (surface)
|
||||
{
|
||||
instance.destroySurfaceKHR(surface);
|
||||
}
|
||||
|
||||
if (vertex_buffer_allocation != VK_NULL_HANDLE)
|
||||
{
|
||||
vmaDestroyBuffer(vma_allocator, vertex_buffer, vertex_buffer_allocation);
|
||||
}
|
||||
|
||||
if (vma_allocator != VK_NULL_HANDLE)
|
||||
{
|
||||
vmaDestroyAllocator(vma_allocator);
|
||||
}
|
||||
|
||||
if (device)
|
||||
{
|
||||
device.destroy();
|
||||
}
|
||||
|
||||
if (debug_utils_messenger)
|
||||
{
|
||||
instance.destroyDebugUtilsMessengerEXT(debug_utils_messenger);
|
||||
}
|
||||
|
||||
instance.destroy();
|
||||
}
|
||||
|
||||
bool HPPHelloTriangle::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
// Headless is not supported to keep this sample as simple as possible
|
||||
assert(options.window != nullptr);
|
||||
assert(options.window->get_window_mode() != vkb::Window::Mode::Headless);
|
||||
|
||||
if (Application::prepare(options))
|
||||
{
|
||||
instance = create_instance({VK_KHR_SURFACE_EXTENSION_NAME}, {});
|
||||
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
||||
debug_utils_messenger = instance.createDebugUtilsMessengerEXT(debug_utils_create_info);
|
||||
#endif
|
||||
|
||||
select_physical_device_and_surface();
|
||||
|
||||
const vkb::Window::Extent &extent = options.window->get_extent();
|
||||
swapchain_data.extent.width = extent.width;
|
||||
swapchain_data.extent.height = extent.height;
|
||||
|
||||
// create a device
|
||||
device = create_device({VK_KHR_SWAPCHAIN_EXTENSION_NAME});
|
||||
|
||||
// get the (graphics) queue
|
||||
queue = device.getQueue(graphics_queue_index, 0);
|
||||
|
||||
vma_allocator = create_vma_allocator();
|
||||
std::tie(vertex_buffer, vertex_buffer_allocation) = create_vertex_buffer();
|
||||
|
||||
init_swapchain();
|
||||
|
||||
// Create the necessary objects for rendering.
|
||||
render_pass = create_render_pass();
|
||||
|
||||
// Create a blank pipeline layout.
|
||||
// We are not binding any resources to the pipeline in this first sample.
|
||||
pipeline_layout = device.createPipelineLayout({});
|
||||
|
||||
pipeline = create_graphics_pipeline();
|
||||
|
||||
init_framebuffers();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HPPHelloTriangle::update(float delta_time)
|
||||
{
|
||||
vk::Result res;
|
||||
uint32_t index;
|
||||
std::tie(res, index) = acquire_next_image();
|
||||
|
||||
// Handle outdated error in acquire.
|
||||
if (res == vk::Result::eSuboptimalKHR || res == vk::Result::eErrorOutOfDateKHR)
|
||||
{
|
||||
resize(swapchain_data.extent.width, swapchain_data.extent.height);
|
||||
std::tie(res, index) = acquire_next_image();
|
||||
}
|
||||
|
||||
if (res != vk::Result::eSuccess)
|
||||
{
|
||||
queue.waitIdle();
|
||||
return;
|
||||
}
|
||||
|
||||
render_triangle(index);
|
||||
|
||||
// Present swapchain image
|
||||
vk::PresentInfoKHR present_info{.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &per_frame_data[index].swapchain_release_semaphore,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &swapchain_data.swapchain,
|
||||
.pImageIndices = &index};
|
||||
res = queue.presentKHR(present_info);
|
||||
|
||||
// Handle Outdated error in present.
|
||||
if (res == vk::Result::eSuboptimalKHR || res == vk::Result::eErrorOutOfDateKHR)
|
||||
{
|
||||
resize(swapchain_data.extent.width, swapchain_data.extent.height);
|
||||
}
|
||||
else if (res != vk::Result::eSuccess)
|
||||
{
|
||||
LOGE("Failed to present swapchain image.");
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPHelloTriangle::resize(const uint32_t, const uint32_t)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::SurfaceCapabilitiesKHR surface_properties = gpu.getSurfaceCapabilitiesKHR(surface);
|
||||
|
||||
// Only rebuild the swapchain if the dimensions have changed
|
||||
if (surface_properties.currentExtent == swapchain_data.extent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
device.waitIdle();
|
||||
teardown_framebuffers();
|
||||
|
||||
init_swapchain();
|
||||
init_framebuffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Acquires an image from the swapchain.
|
||||
* @param[out] image The swapchain index for the acquired image.
|
||||
* @returns Vulkan result code
|
||||
*/
|
||||
std::pair<vk::Result, uint32_t> HPPHelloTriangle::acquire_next_image()
|
||||
{
|
||||
vk::Semaphore acquire_semaphore;
|
||||
if (recycled_semaphores.empty())
|
||||
{
|
||||
acquire_semaphore = device.createSemaphore({});
|
||||
}
|
||||
else
|
||||
{
|
||||
acquire_semaphore = recycled_semaphores.back();
|
||||
recycled_semaphores.pop_back();
|
||||
}
|
||||
|
||||
vk::Result res;
|
||||
uint32_t image;
|
||||
std::tie(res, image) = device.acquireNextImageKHR(swapchain_data.swapchain, UINT64_MAX, acquire_semaphore);
|
||||
|
||||
if (res != vk::Result::eSuccess)
|
||||
{
|
||||
recycled_semaphores.push_back(acquire_semaphore);
|
||||
return {res, image};
|
||||
}
|
||||
|
||||
// If we have outstanding fences for this swapchain image, wait for them to complete first.
|
||||
// After begin frame returns, it is safe to reuse or delete resources which
|
||||
// were used previously.
|
||||
//
|
||||
// We wait for fences which completes N frames earlier, so we do not stall,
|
||||
// waiting for all GPU work to complete before this returns.
|
||||
// Normally, this doesn't really block at all,
|
||||
// since we're waiting for old frames to have been completed, but just in case.
|
||||
if (per_frame_data[image].queue_submit_fence)
|
||||
{
|
||||
(void) device.waitForFences(per_frame_data[image].queue_submit_fence, true, UINT64_MAX);
|
||||
device.resetFences(per_frame_data[image].queue_submit_fence);
|
||||
}
|
||||
|
||||
if (per_frame_data[image].primary_command_pool)
|
||||
{
|
||||
device.resetCommandPool(per_frame_data[image].primary_command_pool);
|
||||
}
|
||||
|
||||
// Recycle the old semaphore back into the semaphore manager.
|
||||
vk::Semaphore old_semaphore = per_frame_data[image].swapchain_acquire_semaphore;
|
||||
|
||||
if (old_semaphore)
|
||||
{
|
||||
recycled_semaphores.push_back(old_semaphore);
|
||||
}
|
||||
|
||||
per_frame_data[image].swapchain_acquire_semaphore = acquire_semaphore;
|
||||
|
||||
return {vk::Result::eSuccess, image};
|
||||
}
|
||||
|
||||
vk::Device HPPHelloTriangle::create_device(const std::vector<const char *> &required_device_extensions)
|
||||
{
|
||||
std::vector<vk::ExtensionProperties> device_extensions = gpu.enumerateDeviceExtensionProperties();
|
||||
|
||||
if (!validate_extensions(required_device_extensions, device_extensions))
|
||||
{
|
||||
throw std::runtime_error("Required device extensions are missing, will try without.");
|
||||
}
|
||||
|
||||
std::vector<const char *> active_device_extensions(required_device_extensions);
|
||||
|
||||
#if (defined(VKB_ENABLE_PORTABILITY))
|
||||
// VK_KHR_portability_subset must be enabled if present in the implementation (e.g on macOS/iOS with beta extensions enabled)
|
||||
if (std::ranges::any_of(device_extensions,
|
||||
[](vk::ExtensionProperties const &extension) { return strcmp(extension.extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0; }))
|
||||
{
|
||||
active_device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create a device with one queue
|
||||
float queue_priority = 1.0f;
|
||||
vk::DeviceQueueCreateInfo queue_info{.queueFamilyIndex = graphics_queue_index, .queueCount = 1, .pQueuePriorities = &queue_priority};
|
||||
vk::DeviceCreateInfo device_info{.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &queue_info,
|
||||
.enabledExtensionCount = static_cast<uint32_t>(active_device_extensions.size()),
|
||||
.ppEnabledExtensionNames = active_device_extensions.data()};
|
||||
vk::Device device = gpu.createDevice(device_info);
|
||||
|
||||
// initialize function pointers for device
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(device);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
vk::Pipeline HPPHelloTriangle::create_graphics_pipeline()
|
||||
{
|
||||
// Load our SPIR-V shaders.
|
||||
|
||||
// Samples support different shading languages, all of which are offline compiled to SPIR-V, the shader format that Vulkan uses.
|
||||
// The shading language to load for can be selected via command line
|
||||
std::string shader_folder{""};
|
||||
switch (get_shading_language())
|
||||
{
|
||||
case vkb::ShadingLanguage::HLSL:
|
||||
shader_folder = "hlsl";
|
||||
break;
|
||||
case vkb::ShadingLanguage::SLANG:
|
||||
shader_folder = "slang";
|
||||
break;
|
||||
default:
|
||||
shader_folder = "glsl";
|
||||
}
|
||||
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{
|
||||
{.stage = vk::ShaderStageFlagBits::eVertex, .module = create_shader_module("hello_triangle/" + shader_folder + "/triangle.vert.spv"), .pName = "main"},
|
||||
{.stage = vk::ShaderStageFlagBits::eFragment, .module = create_shader_module("hello_triangle/" + shader_folder + "/triangle.frag.spv"), .pName = "main"}};
|
||||
|
||||
// Define the vertex input binding.
|
||||
vk::VertexInputBindingDescription binding_description{.binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex};
|
||||
|
||||
// Define the vertex input attribute.
|
||||
std::array<vk::VertexInputAttributeDescription, 2> attribute_descriptions{
|
||||
{{.location = 0, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, position)},
|
||||
{.location = 1, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, color)}}};
|
||||
|
||||
// Define the pipeline vertex input.
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input{
|
||||
.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &binding_description,
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(attribute_descriptions.size()),
|
||||
.pVertexAttributeDescriptions = attribute_descriptions.data()};
|
||||
|
||||
// Our attachment will write to all color channels, but no blending is enabled.
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment{.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
||||
|
||||
// Disable all depth testing.
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil;
|
||||
|
||||
vk::Pipeline pipeline = vkb::common::create_graphics_pipeline(device,
|
||||
nullptr,
|
||||
shader_stages,
|
||||
vertex_input,
|
||||
vk::PrimitiveTopology::eTriangleList, // We will use triangle lists to draw geometry.
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eBack,
|
||||
vk::FrontFace::eClockwise,
|
||||
{blend_attachment},
|
||||
depth_stencil,
|
||||
pipeline_layout, // We need to specify the pipeline layout
|
||||
render_pass); // and the render pass up front as well
|
||||
|
||||
// Pipeline is baked, we can delete the shader modules now.
|
||||
device.destroyShaderModule(shader_stages[0].module);
|
||||
device.destroyShaderModule(shader_stages[1].module);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
vk::ImageView HPPHelloTriangle::create_image_view(vk::Image image)
|
||||
{
|
||||
vk::ImageViewCreateInfo image_view_create_info{
|
||||
.image = image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = swapchain_data.format,
|
||||
.components = {.r = vk::ComponentSwizzle::eR, .g = vk::ComponentSwizzle::eG, .b = vk::ComponentSwizzle::eB, .a = vk::ComponentSwizzle::eA},
|
||||
.subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}};
|
||||
return device.createImageView(image_view_create_info);
|
||||
}
|
||||
|
||||
vk::Instance HPPHelloTriangle::create_instance(std::vector<const char *> const &required_instance_extensions, std::vector<const char *> const &required_validation_layers)
|
||||
{
|
||||
#if defined(_HPP_VULKAN_LIBRARY)
|
||||
static vk::detail::DynamicLoader dl(_HPP_VULKAN_LIBRARY);
|
||||
#else
|
||||
static vk::detail::DynamicLoader dl;
|
||||
#endif
|
||||
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
|
||||
|
||||
std::vector<vk::ExtensionProperties> available_instance_extensions = vk::enumerateInstanceExtensionProperties();
|
||||
|
||||
std::vector<const char *> active_instance_extensions(required_instance_extensions);
|
||||
|
||||
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
||||
active_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
#endif
|
||||
|
||||
#if (defined(VKB_ENABLE_PORTABILITY))
|
||||
active_instance_extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
bool portability_enumeration_available = false;
|
||||
if (std::ranges::any_of(available_instance_extensions,
|
||||
[](vk::ExtensionProperties const &extension) { return strcmp(extension.extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0; }))
|
||||
{
|
||||
active_instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
portability_enumeration_available = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
active_instance_extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
active_instance_extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
active_instance_extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
active_instance_extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
active_instance_extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
active_instance_extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_DISPLAY_KHR)
|
||||
active_instance_extensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME);
|
||||
#else
|
||||
# pragma error Platform not supported
|
||||
#endif
|
||||
|
||||
if (!validate_extensions(active_instance_extensions, available_instance_extensions))
|
||||
{
|
||||
throw std::runtime_error("Required instance extensions are missing.");
|
||||
}
|
||||
|
||||
std::vector<const char *> requested_instance_layers(required_validation_layers);
|
||||
|
||||
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
||||
char const *validationLayer = "VK_LAYER_KHRONOS_validation";
|
||||
|
||||
std::vector<vk::LayerProperties> supported_instance_layers = vk::enumerateInstanceLayerProperties();
|
||||
|
||||
if (std::ranges::any_of(supported_instance_layers, [&validationLayer](auto const &lp) { return strcmp(lp.layerName, validationLayer) == 0; }))
|
||||
{
|
||||
requested_instance_layers.push_back(validationLayer);
|
||||
LOGI("Enabled Validation Layer {}", validationLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGW("Validation Layer {} is not available", validationLayer);
|
||||
}
|
||||
#endif
|
||||
|
||||
vk::ApplicationInfo app{.pApplicationName = "HPP Hello Triangle", .pEngineName = "Vulkan Samples", .apiVersion = VK_API_VERSION_1_1};
|
||||
|
||||
vk::InstanceCreateInfo instance_info{.pApplicationInfo = &app,
|
||||
.enabledLayerCount = static_cast<uint32_t>(requested_instance_layers.size()),
|
||||
.ppEnabledLayerNames = requested_instance_layers.data(),
|
||||
.enabledExtensionCount = static_cast<uint32_t>(active_instance_extensions.size()),
|
||||
.ppEnabledExtensionNames = active_instance_extensions.data()};
|
||||
|
||||
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
||||
debug_utils_create_info =
|
||||
vk::DebugUtilsMessengerCreateInfoEXT{.messageSeverity =
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning,
|
||||
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
|
||||
.pfnUserCallback = debug_utils_messenger_callback};
|
||||
|
||||
instance_info.pNext = &debug_utils_create_info;
|
||||
#endif
|
||||
|
||||
#if (defined(VKB_ENABLE_PORTABILITY))
|
||||
if (portability_enumeration_available)
|
||||
{
|
||||
instance_info.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create the Vulkan instance
|
||||
vk::Instance instance = vk::createInstance(instance_info);
|
||||
|
||||
// initialize function pointers for instance
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance);
|
||||
|
||||
#if defined(VK_USE_PLATFORM_DISPLAY_KHR) || defined(VK_USE_PLATFORM_ANDROID_KHR) || defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
// we need some additional initializing for this platform!
|
||||
if (volkInitialize())
|
||||
{
|
||||
throw std::runtime_error("Failed to initialize volk.");
|
||||
}
|
||||
volkLoadInstance(instance);
|
||||
#endif
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
vk::RenderPass HPPHelloTriangle::create_render_pass()
|
||||
{
|
||||
vk::AttachmentDescription attachment{
|
||||
.format = swapchain_data.format, // Backbuffer format.
|
||||
.samples = vk::SampleCountFlagBits::e1, // Not multisampled.
|
||||
.loadOp = vk::AttachmentLoadOp::eClear, // When starting the frame, we want tiles to be cleared.
|
||||
.storeOp = vk::AttachmentStoreOp::eStore, // When ending the frame, we want tiles to be written out.
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare, // Don't care about stencil since we're not using it.
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare, // Don't care about stencil since we're not using it.
|
||||
.initialLayout = vk::ImageLayout::eUndefined, // The image layout will be undefined when the render pass begins.
|
||||
.finalLayout = vk::ImageLayout::ePresentSrcKHR // After the render pass is complete, we will transition to PRESENT_SRC_KHR layout.
|
||||
};
|
||||
|
||||
// We have one subpass. This subpass has one color attachment.
|
||||
// While executing this subpass, the attachment will be in attachment optimal layout.
|
||||
vk::AttachmentReference color_ref{.attachment = 0, .layout = vk::ImageLayout::eColorAttachmentOptimal};
|
||||
|
||||
// We will end up with two transitions.
|
||||
// The first one happens right before we start subpass #0, where
|
||||
// eUndefined is transitioned into eColorAttachmentOptimal.
|
||||
// The final layout in the render pass attachment states ePresentSrcKHR, so we
|
||||
// will get a final transition from eColorAttachmentOptimal to ePresetSrcKHR.
|
||||
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics, .colorAttachmentCount = 1, .pColorAttachments = &color_ref};
|
||||
|
||||
// Create a dependency to external events.
|
||||
// We need to wait for the WSI semaphore to signal.
|
||||
// Only pipeline stages which depend on eColorAttachmentOutput will
|
||||
// actually wait for the semaphore, so we must also wait for that pipeline stage.
|
||||
vk::SubpassDependency dependency{.srcSubpass = vk::SubpassExternal,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
// Since we changed the image layout, we need to make the memory visible to color attachment to modify.
|
||||
.srcAccessMask = {},
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite};
|
||||
|
||||
// Finally, create the renderpass.
|
||||
vk::RenderPassCreateInfo rp_info{
|
||||
.attachmentCount = 1, .pAttachments = &attachment, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency};
|
||||
return device.createRenderPass(rp_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to load a shader module from an offline-compiled SPIR-V file.
|
||||
* @param path The path for the shader (relative to the assets directory).
|
||||
* @returns A vk::ShaderModule handle. Aborts execution if shader creation fails.
|
||||
*/
|
||||
vk::ShaderModule HPPHelloTriangle::create_shader_module(std::string const &path)
|
||||
{
|
||||
auto spirv = vkb::fs::read_shader_binary_u32(path);
|
||||
|
||||
vk::ShaderModuleCreateInfo shader_module_create_info{.codeSize = spirv.size() * sizeof(uint32_t), .pCode = spirv.data()};
|
||||
|
||||
return device.createShaderModule(shader_module_create_info);
|
||||
}
|
||||
|
||||
vk::SwapchainKHR
|
||||
HPPHelloTriangle::create_swapchain(vk::Extent2D const &swapchain_extent, vk::SurfaceFormatKHR surface_format, vk::SwapchainKHR old_swapchain)
|
||||
{
|
||||
vk::SurfaceCapabilitiesKHR surface_properties = gpu.getSurfaceCapabilitiesKHR(surface);
|
||||
|
||||
// Determine the number of vk::Image's to use in the swapchain.
|
||||
// Ideally, we desire to own 1 image at a time, the rest of the images can
|
||||
// either be rendered to and/or being queued up for display.
|
||||
uint32_t desired_swapchain_images = surface_properties.minImageCount + 1;
|
||||
if ((surface_properties.maxImageCount > 0) && (desired_swapchain_images > surface_properties.maxImageCount))
|
||||
{
|
||||
// Application must settle for fewer images than desired.
|
||||
desired_swapchain_images = surface_properties.maxImageCount;
|
||||
}
|
||||
|
||||
// Figure out a suitable surface transform.
|
||||
vk::SurfaceTransformFlagBitsKHR pre_transform =
|
||||
(surface_properties.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) ? vk::SurfaceTransformFlagBitsKHR::eIdentity : surface_properties.currentTransform;
|
||||
|
||||
// Find a supported composite type.
|
||||
vk::CompositeAlphaFlagBitsKHR composite = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
||||
if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque)
|
||||
{
|
||||
composite = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
||||
}
|
||||
else if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eInherit)
|
||||
{
|
||||
composite = vk::CompositeAlphaFlagBitsKHR::eInherit;
|
||||
}
|
||||
else if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePreMultiplied)
|
||||
{
|
||||
composite = vk::CompositeAlphaFlagBitsKHR::ePreMultiplied;
|
||||
}
|
||||
else if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePostMultiplied)
|
||||
{
|
||||
composite = vk::CompositeAlphaFlagBitsKHR::ePostMultiplied;
|
||||
}
|
||||
|
||||
// FIFO must be supported by all implementations.
|
||||
vk::PresentModeKHR swapchain_present_mode = vk::PresentModeKHR::eFifo;
|
||||
|
||||
vk::SwapchainCreateInfoKHR swapchain_create_info{
|
||||
.surface = surface,
|
||||
.minImageCount = desired_swapchain_images,
|
||||
.imageFormat = surface_format.format,
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
.imageExtent = swapchain_extent,
|
||||
.imageArrayLayers = 1,
|
||||
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
|
||||
.imageSharingMode = vk::SharingMode::eExclusive,
|
||||
.preTransform = pre_transform,
|
||||
.compositeAlpha = composite,
|
||||
.presentMode = swapchain_present_mode,
|
||||
.clipped = true,
|
||||
.oldSwapchain = old_swapchain};
|
||||
|
||||
return device.createSwapchainKHR(swapchain_create_info);
|
||||
}
|
||||
|
||||
std::pair<vk::Buffer, VmaAllocation> HPPHelloTriangle::create_vertex_buffer()
|
||||
{
|
||||
// Vertex data for a single colored triangle
|
||||
const std::vector<Vertex> vertices = {
|
||||
{{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}},
|
||||
{{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
|
||||
{{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}};
|
||||
|
||||
const vk::DeviceSize buffer_size = sizeof(vertices[0]) * vertices.size();
|
||||
|
||||
// Copy Vertex data to a buffer accessible by the device
|
||||
|
||||
vk::BufferCreateInfo buffer_create_info{.size = buffer_size, .usage = vk::BufferUsageFlagBits::eVertexBuffer};
|
||||
|
||||
// We use the Vulkan Memory Allocator to find a memory type that can be written and mapped from the host
|
||||
// On most setups this will return a memory type that resides in VRAM and is accessible from the host
|
||||
VmaAllocationCreateInfo allocation_create_info{
|
||||
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT};
|
||||
|
||||
vk::Buffer vertex_buffer;
|
||||
VmaAllocation vertex_buffer_allocation;
|
||||
VmaAllocationInfo allocation_info{};
|
||||
vmaCreateBuffer(vma_allocator, reinterpret_cast<VkBufferCreateInfo *>(&buffer_create_info), &allocation_create_info, reinterpret_cast<VkBuffer *>(&vertex_buffer), &vertex_buffer_allocation, &allocation_info);
|
||||
if (allocation_info.pMappedData)
|
||||
{
|
||||
memcpy(allocation_info.pMappedData, vertices.data(), buffer_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Could not map vertex buffer.");
|
||||
}
|
||||
|
||||
return {vertex_buffer, vertex_buffer_allocation};
|
||||
}
|
||||
|
||||
VmaAllocator HPPHelloTriangle::create_vma_allocator()
|
||||
{
|
||||
// This sample uses the Vulkan Memory Alloctor (VMA), which needs to be set up
|
||||
VmaVulkanFunctions vma_vulkan_functions{
|
||||
.vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr};
|
||||
|
||||
VmaAllocatorCreateInfo allocator_info{.physicalDevice = gpu, .device = device, .pVulkanFunctions = &vma_vulkan_functions, .instance = instance};
|
||||
|
||||
VmaAllocator allocator;
|
||||
VkResult result = vmaCreateAllocator(&allocator_info, &allocator);
|
||||
if (result != VK_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Could not create allocator for VMA allocator");
|
||||
}
|
||||
|
||||
return allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the Vulkan framebuffers.
|
||||
*/
|
||||
void HPPHelloTriangle::init_framebuffers()
|
||||
{
|
||||
assert(swapchain_data.framebuffers.empty());
|
||||
|
||||
// Create framebuffer for each swapchain image view
|
||||
for (auto &image_view : swapchain_data.image_views)
|
||||
{
|
||||
// create the framebuffer.
|
||||
swapchain_data.framebuffers.push_back(vkb::common::create_framebuffer(device, render_pass, {image_view}, swapchain_data.extent));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the Vulkan swapchain.
|
||||
*/
|
||||
void HPPHelloTriangle::init_swapchain()
|
||||
{
|
||||
vk::SurfaceCapabilitiesKHR surface_properties = gpu.getSurfaceCapabilitiesKHR(surface);
|
||||
|
||||
vk::Extent2D swapchain_extent = (surface_properties.currentExtent.width == 0xFFFFFFFF) ? swapchain_data.extent : surface_properties.currentExtent;
|
||||
|
||||
vk::SurfaceFormatKHR surface_format = vkb::common::select_surface_format(gpu, surface);
|
||||
|
||||
vk::SwapchainKHR old_swapchain = swapchain_data.swapchain;
|
||||
|
||||
swapchain_data.swapchain = create_swapchain(swapchain_extent, surface_format, old_swapchain);
|
||||
|
||||
if (old_swapchain)
|
||||
{
|
||||
for (vk::ImageView image_view : swapchain_data.image_views)
|
||||
{
|
||||
device.destroyImageView(image_view);
|
||||
}
|
||||
|
||||
size_t image_count = device.getSwapchainImagesKHR(old_swapchain).size();
|
||||
|
||||
for (size_t i = 0; i < image_count; i++)
|
||||
{
|
||||
teardown_per_frame(per_frame_data[i]);
|
||||
}
|
||||
|
||||
swapchain_data.image_views.clear();
|
||||
|
||||
device.destroySwapchainKHR(old_swapchain);
|
||||
}
|
||||
|
||||
swapchain_data.extent = swapchain_extent;
|
||||
swapchain_data.format = surface_format.format;
|
||||
|
||||
/// The swapchain images.
|
||||
std::vector<vk::Image> swapchain_images = device.getSwapchainImagesKHR(swapchain_data.swapchain);
|
||||
size_t image_count = swapchain_images.size();
|
||||
|
||||
// Initialize per-frame resources.
|
||||
// Every swapchain image has its own command pool and fence manager.
|
||||
// This makes it very easy to keep track of when we can reset command buffers and such.
|
||||
per_frame_data.clear();
|
||||
per_frame_data.resize(image_count);
|
||||
|
||||
for (size_t frame = 0; frame < image_count; frame++)
|
||||
{
|
||||
auto &pfd = per_frame_data[frame];
|
||||
pfd.queue_submit_fence = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
|
||||
pfd.primary_command_pool = device.createCommandPool({.flags = vk::CommandPoolCreateFlagBits::eTransient, .queueFamilyIndex = graphics_queue_index});
|
||||
pfd.primary_command_buffer = vkb::common::allocate_command_buffer(device, pfd.primary_command_pool);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < image_count; i++)
|
||||
{
|
||||
// Create an image view which we can render into.
|
||||
swapchain_data.image_views.push_back(create_image_view(swapchain_images[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Renders a triangle to the specified swapchain image.
|
||||
* @param swapchain_index The swapchain index for the image being rendered.
|
||||
*/
|
||||
void HPPHelloTriangle::render_triangle(uint32_t swapchain_index)
|
||||
{
|
||||
// Render to this framebuffer.
|
||||
vk::Framebuffer framebuffer = swapchain_data.framebuffers[swapchain_index];
|
||||
|
||||
// Allocate or re-use a primary command buffer.
|
||||
vk::CommandBuffer cmd = per_frame_data[swapchain_index].primary_command_buffer;
|
||||
|
||||
// We will only submit this once before it's recycled.
|
||||
vk::CommandBufferBeginInfo begin_info{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit};
|
||||
// Begin command recording
|
||||
cmd.begin(begin_info);
|
||||
|
||||
// Set clear color values.
|
||||
vk::ClearValue clear_value;
|
||||
clear_value.color = vk::ClearColorValue(std::array<float, 4>({{0.01f, 0.01f, 0.033f, 1.0f}}));
|
||||
|
||||
// Begin the render pass.
|
||||
vk::RenderPassBeginInfo rp_begin{.renderPass = render_pass,
|
||||
.framebuffer = framebuffer,
|
||||
.renderArea = {{0, 0}, {swapchain_data.extent.width, swapchain_data.extent.height}},
|
||||
.clearValueCount = 1,
|
||||
.pClearValues = &clear_value};
|
||||
// We will add draw commands in the same command buffer.
|
||||
cmd.beginRenderPass(rp_begin, vk::SubpassContents::eInline);
|
||||
|
||||
// Bind the graphics pipeline.
|
||||
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
|
||||
vk::Viewport vp{0.0f, 0.0f, static_cast<float>(swapchain_data.extent.width), static_cast<float>(swapchain_data.extent.height), 0.0f, 1.0f};
|
||||
// Set viewport dynamically
|
||||
cmd.setViewport(0, vp);
|
||||
|
||||
vk::Rect2D scissor{{0, 0}, {swapchain_data.extent.width, swapchain_data.extent.height}};
|
||||
// Set scissor dynamically
|
||||
cmd.setScissor(0, scissor);
|
||||
|
||||
// Bind the vertex buffer to source the draw calls from.
|
||||
vk::DeviceSize offset = {0};
|
||||
cmd.bindVertexBuffers(0, vertex_buffer, offset);
|
||||
|
||||
// Draw three vertices with one instance.
|
||||
cmd.draw(3, 1, 0, 0);
|
||||
|
||||
// Complete render pass.
|
||||
cmd.endRenderPass();
|
||||
|
||||
// Complete the command buffer.
|
||||
cmd.end();
|
||||
|
||||
// Submit it to the queue with a release semaphore.
|
||||
if (!per_frame_data[swapchain_index].swapchain_release_semaphore)
|
||||
{
|
||||
per_frame_data[swapchain_index].swapchain_release_semaphore = device.createSemaphore({});
|
||||
}
|
||||
|
||||
vk::PipelineStageFlags wait_stage{VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
||||
|
||||
vk::SubmitInfo info{.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &per_frame_data[swapchain_index].swapchain_acquire_semaphore,
|
||||
.pWaitDstStageMask = &wait_stage,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmd,
|
||||
.signalSemaphoreCount = 1,
|
||||
.pSignalSemaphores = &per_frame_data[swapchain_index].swapchain_release_semaphore};
|
||||
// Submit command buffer to graphics queue
|
||||
queue.submit(info, per_frame_data[swapchain_index].queue_submit_fence);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Select a physical device.
|
||||
*/
|
||||
void HPPHelloTriangle::select_physical_device_and_surface()
|
||||
{
|
||||
std::vector<vk::PhysicalDevice> gpus = instance.enumeratePhysicalDevices();
|
||||
|
||||
bool found_graphics_queue_index = false;
|
||||
for (size_t i = 0; i < gpus.size() && !found_graphics_queue_index; i++)
|
||||
{
|
||||
gpu = gpus[i];
|
||||
|
||||
std::vector<vk::QueueFamilyProperties> queue_family_properties = gpu.getQueueFamilyProperties();
|
||||
|
||||
if (queue_family_properties.empty())
|
||||
{
|
||||
throw std::runtime_error("No queue family found.");
|
||||
}
|
||||
|
||||
if (surface)
|
||||
{
|
||||
instance.destroySurfaceKHR(surface);
|
||||
}
|
||||
|
||||
surface = static_cast<vk::SurfaceKHR>(window->create_surface(static_cast<VkInstance>(instance), static_cast<VkPhysicalDevice>(gpu)));
|
||||
if (!surface)
|
||||
{
|
||||
throw std::runtime_error("Failed to create window surface.");
|
||||
}
|
||||
|
||||
for (uint32_t j = 0; j < vkb::to_u32(queue_family_properties.size()); j++)
|
||||
{
|
||||
vk::Bool32 supports_present = gpu.getSurfaceSupportKHR(j, surface);
|
||||
|
||||
// Find a queue family which supports graphics and presentation.
|
||||
if ((queue_family_properties[j].queueFlags & vk::QueueFlagBits::eGraphics) && supports_present)
|
||||
{
|
||||
graphics_queue_index = j;
|
||||
found_graphics_queue_index = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_graphics_queue_index)
|
||||
{
|
||||
LOGE("Did not find suitable queue which supports graphics and presentation.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tears down the framebuffers. If our swapchain changes, we will call this, and create a new swapchain.
|
||||
*/
|
||||
void HPPHelloTriangle::teardown_framebuffers()
|
||||
{
|
||||
// Wait until device is idle before teardown.
|
||||
queue.waitIdle();
|
||||
|
||||
for (auto &framebuffer : swapchain_data.framebuffers)
|
||||
{
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
}
|
||||
|
||||
swapchain_data.framebuffers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tears down the frame data.
|
||||
* @param per_frame_data The data of a frame.
|
||||
*/
|
||||
void HPPHelloTriangle::teardown_per_frame(FrameData &per_frame_data)
|
||||
{
|
||||
if (per_frame_data.queue_submit_fence)
|
||||
{
|
||||
device.destroyFence(per_frame_data.queue_submit_fence);
|
||||
per_frame_data.queue_submit_fence = nullptr;
|
||||
}
|
||||
|
||||
if (per_frame_data.primary_command_buffer)
|
||||
{
|
||||
device.freeCommandBuffers(per_frame_data.primary_command_pool, per_frame_data.primary_command_buffer);
|
||||
per_frame_data.primary_command_buffer = nullptr;
|
||||
}
|
||||
|
||||
if (per_frame_data.primary_command_pool)
|
||||
{
|
||||
device.destroyCommandPool(per_frame_data.primary_command_pool);
|
||||
per_frame_data.primary_command_pool = nullptr;
|
||||
}
|
||||
|
||||
if (per_frame_data.swapchain_acquire_semaphore)
|
||||
{
|
||||
device.destroySemaphore(per_frame_data.swapchain_acquire_semaphore);
|
||||
per_frame_data.swapchain_acquire_semaphore = nullptr;
|
||||
}
|
||||
|
||||
if (per_frame_data.swapchain_release_semaphore)
|
||||
{
|
||||
device.destroySemaphore(per_frame_data.swapchain_release_semaphore);
|
||||
per_frame_data.swapchain_release_semaphore = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_hello_triangle()
|
||||
{
|
||||
return std::make_unique<HPPHelloTriangle>();
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/* Copyright (c) 2021-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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include <platform/application.h>
|
||||
|
||||
/**
|
||||
* @brief A self-contained (minimal use of framework) sample that illustrates
|
||||
* the rendering of a triangle
|
||||
*/
|
||||
class HPPHelloTriangle : public vkb::Application
|
||||
{
|
||||
/**
|
||||
* @brief Swapchain data
|
||||
*/
|
||||
struct SwapchainData
|
||||
{
|
||||
vk::Extent2D extent; // The swapchain extent
|
||||
vk::Format format = vk::Format::eUndefined; // Pixel format of the swapchain.
|
||||
vk::SwapchainKHR swapchain; // The swapchain.
|
||||
std::vector<vk::ImageView> image_views; // The image view for each swapchain image.
|
||||
std::vector<vk::Framebuffer> framebuffers; // The framebuffer for each swapchain image view.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Per-frame data
|
||||
*/
|
||||
struct FrameData
|
||||
{
|
||||
vk::Fence queue_submit_fence;
|
||||
vk::CommandPool primary_command_pool;
|
||||
vk::CommandBuffer primary_command_buffer;
|
||||
vk::Semaphore swapchain_acquire_semaphore;
|
||||
vk::Semaphore swapchain_release_semaphore;
|
||||
};
|
||||
|
||||
/// Properties of the vertices used in this sample.
|
||||
struct Vertex
|
||||
{
|
||||
glm::vec3 position;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
public:
|
||||
HPPHelloTriangle();
|
||||
virtual ~HPPHelloTriangle();
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
virtual void update(float delta_time) override;
|
||||
|
||||
std::pair<vk::Result, uint32_t> acquire_next_image();
|
||||
vk::Device create_device(const std::vector<const char *> &required_device_extensions);
|
||||
vk::Pipeline create_graphics_pipeline();
|
||||
vk::ImageView create_image_view(vk::Image image);
|
||||
vk::Instance create_instance(std::vector<const char *> const &required_instance_extensions, std::vector<const char *> const &required_validation_layers);
|
||||
vk::RenderPass create_render_pass();
|
||||
vk::ShaderModule create_shader_module(std::string const &path);
|
||||
vk::SwapchainKHR create_swapchain(vk::Extent2D const &swapchain_extent, vk::SurfaceFormatKHR surface_format, vk::SwapchainKHR old_swapchain);
|
||||
std::pair<vk::Buffer, VmaAllocation> create_vertex_buffer();
|
||||
VmaAllocator create_vma_allocator();
|
||||
void init_framebuffers();
|
||||
void init_swapchain();
|
||||
void render_triangle(uint32_t swapchain_index);
|
||||
void select_physical_device_and_surface();
|
||||
void teardown_framebuffers();
|
||||
void teardown_per_frame(FrameData &per_frame_data);
|
||||
|
||||
private:
|
||||
vk::Instance instance; // The Vulkan instance.
|
||||
vk::PhysicalDevice gpu; // The Vulkan physical device.
|
||||
vk::Device device; // The Vulkan device.
|
||||
vk::Queue queue; // The Vulkan device queue.
|
||||
SwapchainData swapchain_data; // The swapchain state.
|
||||
vk::SurfaceKHR surface; // The surface we will render to.
|
||||
uint32_t graphics_queue_index; // The queue family index where graphics work will be submitted.
|
||||
vk::RenderPass render_pass; // The renderpass description.
|
||||
vk::PipelineLayout pipeline_layout; // The pipeline layout for resources.
|
||||
vk::Pipeline pipeline; // The graphics pipeline.
|
||||
vk::DebugUtilsMessengerEXT debug_utils_messenger; // The debug utils messenger.
|
||||
std::vector<vk::Semaphore> recycled_semaphores; // A set of semaphores that can be reused.
|
||||
std::vector<FrameData> per_frame_data; // A set of per-frame data.
|
||||
vk::Buffer vertex_buffer; // The Vulkan buffer object that holds the vertex data for the triangle.
|
||||
VmaAllocation vertex_buffer_allocation = VK_NULL_HANDLE; // Vulkan Memory Allocator (VMA) allocation info for the vertex buffer.
|
||||
VmaAllocator vma_allocator; // The VMA allocator for memory management.
|
||||
|
||||
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
||||
vk::DebugUtilsMessengerCreateInfoEXT debug_utils_create_info;
|
||||
#endif
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_hello_triangle();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2024-2025, Huawei Technologies Co., Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Huawei Technologies Co., Ltd."
|
||||
NAME "Vulkan 1.3 HPP Hello Triangle"
|
||||
DESCRIPTION "An introduction into Vulkan using Vulkan 1.3 using Vulkan-Hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"hello_triangle_1_3/glsl/triangle.vert"
|
||||
"hello_triangle_1_3/glsl/triangle.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hello_triangle_1_3/hlsl/triangle.vert.hlsl"
|
||||
"hello_triangle_1_3/hlsl/triangle.frag.hlsl"
|
||||
SHADER_FILES_SLANG
|
||||
"hello_triangle_1_3/slang/triangle.vert.slang"
|
||||
"hello_triangle_1_3/slang/triangle.frag.slang")
|
||||
|
||||
if(${VKB_${FOLDER_NAME}})
|
||||
target_compile_definitions(${FOLDER_NAME} PUBLIC VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
||||
endif()
|
||||
@@ -1,212 +0,0 @@
|
||||
////
|
||||
* Copyright (c) 2025, The Khronos Group
|
||||
*
|
||||
* 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.
|
||||
////
|
||||
= Hello Triangle with Vulkan 1.3 Features using Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_hello_triangle_1_3[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hello_triangle_1_3[Hello Triangle 1.3] that illustrates the usage of the C{pp} bindings of Vulkan provided by Vulkan-Hpp.
|
||||
|
||||
This sample demonstrates how to render a simple triangle using Vulkan 1.3 core features. It modernizes the traditional "Hello Triangle" Vulkan sample by incorporating:
|
||||
|
||||
- **Dynamic Rendering**
|
||||
- **Synchronization2**
|
||||
- **Extended Dynamic State**
|
||||
- **Vertex Buffers**
|
||||
|
||||
## Overview
|
||||
|
||||
The sample renders a colored triangle to the screen using Vulkan 1.3. It showcases how to:
|
||||
|
||||
- Initialize Vulkan with Vulkan 1.3 features enabled.
|
||||
- Use dynamic rendering to simplify the rendering pipeline.
|
||||
- Employ the Synchronization2 API for improved synchronization.
|
||||
- Utilize extended dynamic states to reduce pipeline complexity.
|
||||
- Manage vertex data using vertex buffers instead of hard-coded vertices.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Dynamic Rendering
|
||||
|
||||
**What is Dynamic Rendering?**
|
||||
|
||||
Dynamic Rendering is a feature introduced in Vulkan 1.3 that allows rendering without pre-defined render passes and framebuffers. It simplifies the rendering process by enabling you to specify rendering states directly during command buffer recording.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **No Render Passes or Framebuffers:** The sample does not create `vk::RenderPass` or `vk::Framebuffer` objects.
|
||||
- **`vk::CommandBuffer::beginRendering()` and `vk::CommandBuffer::endRendering()`:** These functions are used to begin and end rendering operations dynamically.
|
||||
- **Pipeline Creation:** Uses `vk::PipelineRenderingCreateInfo` during pipeline creation to specify rendering details.
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Simplifies code by reducing boilerplate associated with render passes and framebuffers.
|
||||
- Increases flexibility by allowing rendering to different attachments without recreating render passes.
|
||||
|
||||
### 2. Synchronization2
|
||||
|
||||
**What is Synchronization2?**
|
||||
|
||||
Synchronization2 is an improved synchronization API introduced in Vulkan 1.3. It provides more granular control over synchronization primitives and simplifies the synchronization process.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **`vk::CommandBuffer::pipelineBarrier2()`:** Replaces the older `vk::CommandBuffer::pipelineBarrier()` for more detailed synchronization.
|
||||
- **`vk::DependencyInfo` and `vk::ImageMemoryBarrier2`:** Used to specify precise memory dependencies and image layout transitions.
|
||||
|
||||
**Example Usage:**
|
||||
|
||||
```cpp
|
||||
vk::ImageMemoryBarrier2 image_barrier = {
|
||||
// ... members ...
|
||||
};
|
||||
|
||||
vk::DependencyInfo dependency_info = {
|
||||
.imageMemoryBarrierCount = 1,
|
||||
.pImageMemoryBarriers = &image_barrier,
|
||||
};
|
||||
|
||||
cmd.pipelineBarrier2(dependency_info);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Provides more expressive and flexible synchronization.
|
||||
- Reduces the potential for synchronization errors.
|
||||
- Simplifies the specification of pipeline stages and access masks.
|
||||
|
||||
### 3. Extended Dynamic State
|
||||
|
||||
**What is Extended Dynamic State?**
|
||||
|
||||
Extended Dynamic State allows more pipeline states to be set dynamically at command buffer recording time rather than during pipeline creation. This reduces the number of pipeline objects needed.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **Dynamic States Enabled:** The sample enables dynamic states like `vk::DynamicState::eCullMode`, `vk::DynamicState::eFrontFace`, and `vk::DynamicState::ePrimitiveTopology`.
|
||||
- **Dynamic State Commands:** Uses `vk::CommandBuffer::setCullMode()`, `vk::CommandBuffer::setFrontFace()`, and `vk::CommandBuffer::setPrimitiveTopology()` to set these states dynamically.
|
||||
|
||||
**Example Usage:**
|
||||
|
||||
```cpp
|
||||
cmd.setCullMode(vk::DynamicState::eCullMode);
|
||||
cmd.setFrontFace(vk::FrontFace::eClockwise);
|
||||
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Reduces the need to create multiple pipelines for different state configurations.
|
||||
- Enhances flexibility by allowing state changes without pipeline recreation.
|
||||
|
||||
### 4. Vertex Buffers
|
||||
|
||||
**What Changed?**
|
||||
|
||||
Unlike the original sample, which used hard-coded vertices in the shader, this sample uses a vertex buffer to store vertex data.
|
||||
|
||||
**How It's Used in the Sample:**
|
||||
|
||||
- **Vertex Structure Defined:**
|
||||
|
||||
```cpp
|
||||
struct Vertex {
|
||||
glm::vec2 position;
|
||||
glm::vec3 color;
|
||||
};
|
||||
```
|
||||
|
||||
- **Vertex Data Stored in a Buffer:**
|
||||
|
||||
```cpp
|
||||
std::vector<Vertex> vertices = {
|
||||
{{0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, // Red Vertex
|
||||
// ... other vertices ...
|
||||
};
|
||||
```
|
||||
|
||||
- **Buffer Creation and Memory Allocation:**
|
||||
|
||||
```cpp
|
||||
vk::BufferCreateInfo buffer_info = { /* ... */ };
|
||||
vertex_buffer = device.createBuffer(buffer_info);
|
||||
|
||||
vk::MemoryAllocateInfo alloc_info = { /* ... */ };
|
||||
vertex_buffer_memory = device.allocateMemory(alloc_info);
|
||||
```
|
||||
|
||||
- **Binding the Vertex Buffer:**
|
||||
|
||||
```cpp
|
||||
cmd.bindVertexBuffers(0, vertex_buffer, offset);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- **Flexibility:** Easier to modify vertex data without changing shaders.
|
||||
- **Performance:** Potentially better performance due to efficient memory usage.
|
||||
- **Scalability:** Simplifies rendering more complex geometries.
|
||||
|
||||
## How the Sample Works
|
||||
|
||||
1. **Initialization:**
|
||||
|
||||
- **Instance Creation:** Initializes a Vulkan instance with Vulkan 1.3 API version and required extensions.
|
||||
- **Device Selection:** Chooses a physical device that supports Vulkan 1.3 and required features.
|
||||
- **Logical Device Creation:** Creates a logical device with enabled Vulkan 1.3 features like dynamic rendering, synchronization2, and extended dynamic state.
|
||||
- **Surface and Swapchain Creation:** Sets up the window surface and initializes the swapchain for presenting images.
|
||||
|
||||
2. **Vertex Buffer Setup:**
|
||||
|
||||
- **Vertex Data Definition:** Defines vertices with positions and colors.
|
||||
- **Buffer Creation:** Creates a buffer to store vertex data.
|
||||
- **Memory Allocation:** Allocates memory for the buffer and maps the vertex data into it.
|
||||
|
||||
3. **Pipeline Setup:**
|
||||
|
||||
- **Shader Modules:** Loads and compiles vertex and fragment shaders.
|
||||
- **Pipeline Layout:** Creates a pipeline layout (empty in this case as no descriptors are used).
|
||||
- **Dynamic States Specification:** Specifies which states will be dynamic.
|
||||
- **Graphics Pipeline Creation:** Creates the graphics pipeline with dynamic rendering info and dynamic states enabled.
|
||||
|
||||
4. **Rendering Loop:**
|
||||
|
||||
- **Acquire Swapchain Image:** Gets the next available image from the swapchain.
|
||||
- **Command Buffer Recording:**
|
||||
|
||||
- **Begin Rendering:** Uses `vk::CommandBuffer::beginRendering()` with dynamic rendering info.
|
||||
- **Set Dynamic States:** Sets viewport, scissor, cull mode, front face, and primitive topology dynamically.
|
||||
- **Bind Pipeline and Vertex Buffer:** Binds the graphics pipeline and the vertex buffer.
|
||||
- **Draw Call:** Issues a draw call to render the triangle.
|
||||
- **End Rendering:** Uses `vk::CommandBuffer::endRendering()` to finish rendering.
|
||||
- **Image Layout Transition:** Transitions the swapchain image layout for presentation using `vk::CommandBuffer::pipelineBarrier2()`.
|
||||
|
||||
- **Queue Submission:** Submits the command buffer to the graphics queue.
|
||||
- **Present Image:** Presents the rendered image to the screen.
|
||||
|
||||
5. **Cleanup:**
|
||||
|
||||
- **Resource Destruction:** Cleans up Vulkan resources like pipelines, buffers, and swapchain images upon application exit.
|
||||
|
||||
## Dependencies and Requirements
|
||||
|
||||
- **Vulkan SDK 1.3 or Later:** Ensure you have the Vulkan SDK that supports Vulkan 1.3.
|
||||
- **Hardware Support:** A GPU that supports Vulkan 1.3 features, including dynamic rendering, synchronization2, and extended dynamic state.
|
||||
- **GLM Library:** Used for vector and matrix operations.
|
||||
- **Shader Compiler:** GLSL shaders are compiled at runtime using a GLSL compiler.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,170 +0,0 @@
|
||||
/* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/application.h"
|
||||
#include "platform/window.h"
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
/**
|
||||
* @brief A self-contained (minimal use of framework) sample that illustrates
|
||||
* the rendering of a triangle
|
||||
*/
|
||||
class HPPHelloTriangleV13 : public vkb::Application
|
||||
{
|
||||
// Define the Vertex structure
|
||||
struct Vertex
|
||||
{
|
||||
glm::vec2 position;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
// Define the vertex data
|
||||
const std::vector<Vertex> vertices = {
|
||||
{{0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, // Vertex 1: Red
|
||||
{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, // Vertex 2: Green
|
||||
{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} // Vertex 3: Blue
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Swapchain state
|
||||
*/
|
||||
struct SwapchainDimensions
|
||||
{
|
||||
/// Width of the swapchain.
|
||||
uint32_t width = 0;
|
||||
|
||||
/// Height of the swapchain.
|
||||
uint32_t height = 0;
|
||||
|
||||
/// Pixel format of the swapchain.
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Per-frame data
|
||||
*/
|
||||
struct PerFrame
|
||||
{
|
||||
vk::Fence queue_submit_fence = nullptr;
|
||||
vk::CommandPool primary_command_pool = nullptr;
|
||||
vk::CommandBuffer primary_command_buffer = nullptr;
|
||||
vk::Semaphore swapchain_acquire_semaphore = nullptr;
|
||||
vk::Semaphore swapchain_release_semaphore = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Vulkan objects and global state
|
||||
*/
|
||||
struct Context
|
||||
{
|
||||
/// The Vulkan instance.
|
||||
vk::Instance instance = nullptr;
|
||||
|
||||
/// The Vulkan physical device.
|
||||
vk::PhysicalDevice gpu = nullptr;
|
||||
|
||||
/// The Vulkan device.
|
||||
vk::Device device = nullptr;
|
||||
|
||||
/// The Vulkan device queue.
|
||||
vk::Queue queue = nullptr;
|
||||
|
||||
/// The swapchain.
|
||||
vk::SwapchainKHR swapchain = nullptr;
|
||||
|
||||
/// The swapchain dimensions.
|
||||
SwapchainDimensions swapchain_dimensions;
|
||||
|
||||
/// The surface we will render to.
|
||||
vk::SurfaceKHR surface = nullptr;
|
||||
|
||||
/// The queue family index where graphics work will be submitted.
|
||||
int32_t graphics_queue_index = -1;
|
||||
|
||||
/// The image view for each swapchain image.
|
||||
std::vector<vk::ImageView> swapchain_image_views;
|
||||
|
||||
/// The handles to the images in the swapchain.
|
||||
std::vector<vk::Image> swapchain_images;
|
||||
|
||||
/// The graphics pipeline.
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
|
||||
/**
|
||||
* The pipeline layout for resources.
|
||||
* Not used in this sample, but we still need to provide a dummy one.
|
||||
*/
|
||||
vk::PipelineLayout pipeline_layout = nullptr;
|
||||
|
||||
/// The debug utility messenger callback.
|
||||
vk::DebugUtilsMessengerEXT debug_callback = nullptr;
|
||||
|
||||
/// A set of semaphores that can be reused.
|
||||
std::vector<vk::Semaphore> recycled_semaphores;
|
||||
|
||||
/// A set of per-frame data.
|
||||
std::vector<PerFrame> per_frame;
|
||||
|
||||
/// The Vulkan buffer object that holds the vertex data for the triangle.
|
||||
vk::Buffer vertex_buffer = nullptr;
|
||||
|
||||
/// The device memory allocated for the vertex buffer.
|
||||
vk::DeviceMemory vertex_buffer_memory = nullptr;
|
||||
};
|
||||
|
||||
public:
|
||||
HPPHelloTriangleV13() = default;
|
||||
|
||||
virtual ~HPPHelloTriangleV13();
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
virtual void update(float delta_time) override;
|
||||
|
||||
vk::Result acquire_next_swapchain_image(uint32_t *image);
|
||||
uint32_t find_memory_type(vk::PhysicalDevice physical_device, uint32_t type_filter, vk::MemoryPropertyFlags properties);
|
||||
void init_device();
|
||||
void init_instance();
|
||||
void init_per_frame(PerFrame &per_frame);
|
||||
void init_pipeline();
|
||||
void init_swapchain();
|
||||
void init_vertex_buffer();
|
||||
vk::ShaderModule load_shader_module(const std::string &path, vk::ShaderStageFlagBits shader_stage);
|
||||
vk::Result present_image(uint32_t index);
|
||||
void render_triangle(uint32_t swapchain_index);
|
||||
void select_physical_device_and_surface(vkb::Window *window);
|
||||
void teardown_per_frame(PerFrame &per_frame);
|
||||
void transition_image_layout(vk::CommandBuffer cmd,
|
||||
vk::Image image,
|
||||
vk::ImageLayout oldLayout,
|
||||
vk::ImageLayout newLayout,
|
||||
vk::AccessFlags2 srcAccessMask,
|
||||
vk::AccessFlags2 dstAccessMask,
|
||||
vk::PipelineStageFlags2 srcStage,
|
||||
vk::PipelineStageFlags2 dstStage);
|
||||
bool validate_extensions(const std::vector<const char *> &required, const std::vector<vk::ExtensionProperties> &available);
|
||||
|
||||
private:
|
||||
Context context;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_hello_triangle_1_3();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2022-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Instancing"
|
||||
DESCRIPTION "Instanced mesh rendering, uses a separate vertex buffer for instanced data, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"instancing/glsl/instancing.vert"
|
||||
"instancing/glsl/instancing.frag"
|
||||
"instancing/glsl/planet.vert"
|
||||
"instancing/glsl/planet.frag"
|
||||
"instancing/glsl/starfield.vert"
|
||||
"instancing/glsl/starfield.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"instancing/hlsl/instancing.vert.hlsl"
|
||||
"instancing/hlsl/instancing.frag.hlsl"
|
||||
"instancing/hlsl/planet.vert.hlsl"
|
||||
"instancing/hlsl/planet.frag.hlsl"
|
||||
"instancing/hlsl/starfield.vert.hlsl"
|
||||
"instancing/hlsl/starfield.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= HPP Instancing
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_instancing[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/instancing[Instancing] that illustrates the usage of the C{pp} bindings of Vulkan provided by vulkan.hpp.
|
||||
@@ -1,538 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Instanced mesh rendering, uses a separate vertex buffer for instanced data, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_instancing.h"
|
||||
|
||||
#include <benchmark_mode/benchmark_mode.h>
|
||||
#include <random>
|
||||
|
||||
HPPInstancing::HPPInstancing()
|
||||
{
|
||||
title = "HPP instanced mesh rendering";
|
||||
}
|
||||
|
||||
HPPInstancing::~HPPInstancing()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
planet.destroy(device);
|
||||
rocks.destroy(device);
|
||||
device.destroyPipeline(starfield_pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPInstancing::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
initialize_camera();
|
||||
load_assets();
|
||||
prepare_instance_data();
|
||||
prepare_uniform_buffers();
|
||||
vk::Device device = get_device().get_handle();
|
||||
descriptor_set_layout = create_descriptor_set_layout();
|
||||
pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &descriptor_set_layout});
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
|
||||
// setup planet
|
||||
planet.pipeline = create_planet_pipeline();
|
||||
planet.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, descriptor_set_layout);
|
||||
update_planet_descriptor_set();
|
||||
|
||||
// setup rocks
|
||||
rocks.pipeline = create_rocks_pipeline();
|
||||
rocks.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, descriptor_set_layout);
|
||||
update_rocks_descriptor_set();
|
||||
|
||||
// setup starfield
|
||||
starfield_pipeline = create_starfield_pipeline();
|
||||
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool HPPInstancing::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
HPPApiVulkanSample::resize(width, height);
|
||||
rebuild_command_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HPPInstancing::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
|
||||
{
|
||||
auto &requested_features = gpu.get_mutable_requested_features();
|
||||
auto const &features = gpu.get_features();
|
||||
|
||||
// Enable anisotropic filtering if supported
|
||||
if (features.samplerAnisotropy)
|
||||
{
|
||||
requested_features.samplerAnisotropy = true;
|
||||
}
|
||||
// Enable texture compression
|
||||
if (features.textureCompressionBC)
|
||||
{
|
||||
requested_features.textureCompressionBC = true;
|
||||
}
|
||||
else if (features.textureCompressionASTC_LDR)
|
||||
{
|
||||
requested_features.textureCompressionASTC_LDR = true;
|
||||
}
|
||||
else if (features.textureCompressionETC2)
|
||||
{
|
||||
requested_features.textureCompressionETC2 = true;
|
||||
}
|
||||
};
|
||||
|
||||
void HPPInstancing::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
|
||||
std::array<vk::ClearValue, 2> clear_values =
|
||||
{{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.033f, 0.0f}})),
|
||||
vk::ClearDepthStencilValue{0.0f, 0}}};
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
// Set target frame buffer
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
auto command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
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);
|
||||
|
||||
vk::DeviceSize offset = 0;
|
||||
|
||||
// Star field
|
||||
// the star field uses the same descriptor_set as planet !
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, planet.descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, starfield_pipeline);
|
||||
command_buffer.draw(4, 1, 0, 0);
|
||||
|
||||
// Planet
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, planet.descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, planet.pipeline);
|
||||
command_buffer.bindVertexBuffers(0, planet.mesh->get_vertex_buffer("vertex_buffer").get_handle(), offset);
|
||||
command_buffer.bindIndexBuffer(planet.mesh->get_index_buffer().get_handle(), 0, vk::IndexType::eUint32);
|
||||
command_buffer.drawIndexed(planet.mesh->vertex_indices, 1, 0, 0, 0);
|
||||
|
||||
// Instanced rocks
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, rocks.descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, rocks.pipeline);
|
||||
// Binding point 0 : Mesh vertex buffer
|
||||
command_buffer.bindVertexBuffers(0, rocks.mesh->get_vertex_buffer("vertex_buffer").get_handle(), offset);
|
||||
// Binding point 1 : Instance data buffer
|
||||
command_buffer.bindVertexBuffers(1, instance_buffer.buffer->get_handle(), offset);
|
||||
command_buffer.bindIndexBuffer(rocks.mesh->get_index_buffer().get_handle(), 0, vk::IndexType::eUint32);
|
||||
// Render instances
|
||||
command_buffer.drawIndexed(rocks.mesh->vertex_indices, INSTANCE_COUNT, 0, 0, 0);
|
||||
|
||||
draw_ui(command_buffer);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPInstancing::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Statistics"))
|
||||
{
|
||||
drawer.text("Instances: %d", INSTANCE_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
void HPPInstancing::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
if (!paused || camera.updated)
|
||||
{
|
||||
update_uniform_buffer(delta_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPInstancing::create_descriptor_pool()
|
||||
{
|
||||
// Example uses one ubo
|
||||
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 2}, {vk::DescriptorType::eCombinedImageSampler, 2}}};
|
||||
vk::DescriptorPoolCreateInfo descriptor_pool_create_info{.maxSets = 2,
|
||||
.poolSizeCount = static_cast<uint32_t>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data()};
|
||||
|
||||
return get_device().get_handle().createDescriptorPool(descriptor_pool_create_info);
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPInstancing::create_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> set_layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex}, // Binding 0 : Vertex shader uniform buffer
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}}; // Binding 1 : Fragment shader combined sampler
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout(
|
||||
{.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()), .pBindings = set_layout_bindings.data()});
|
||||
}
|
||||
|
||||
vk::Pipeline HPPInstancing::create_planet_pipeline()
|
||||
{
|
||||
// Planet rendering pipeline
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("instancing", "planet.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("instancing", "planet.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex input bindings
|
||||
vk::VertexInputBindingDescription binding_description{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
|
||||
// Vertex attribute bindings
|
||||
std::array<vk::VertexInputAttributeDescription, 3> attribute_descriptions = {
|
||||
{ // Per-vertex attributes
|
||||
// These are advanced for each vertex fetched by the vertex shader
|
||||
{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Location 0: Position
|
||||
{1, 0, vk::Format::eR32G32B32Sfloat, 3 * sizeof(float)}, // Location 1: Normal
|
||||
{2, 0, vk::Format::eR32G32Sfloat, 6 * sizeof(float)}}}; // Location 2: Texture coordinates
|
||||
|
||||
// Use all input bindings and attribute descriptions
|
||||
vk::PipelineVertexInputStateCreateInfo input_state{.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &binding_description,
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(attribute_descriptions.size()),
|
||||
.pVertexAttributeDescriptions = attribute_descriptions.data()};
|
||||
|
||||
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.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = true;
|
||||
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,
|
||||
input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eBack,
|
||||
vk::FrontFace::eClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPInstancing::create_rocks_pipeline()
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{load_shader("instancing", "instancing.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("instancing", "instancing.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex input bindings
|
||||
// The instancing pipeline uses a vertex input state with two bindings
|
||||
std::array<vk::VertexInputBindingDescription, 2> binding_descriptions = {
|
||||
{{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex}, // Binding point 0: Mesh vertex layout description at per-vertex rate
|
||||
{1, sizeof(InstanceData), vk::VertexInputRate::eInstance}}}; // Binding point 1: Instanced data at per-instance rate
|
||||
|
||||
// Vertex attribute bindings
|
||||
// Note that the shader declaration for per-vertex and per-instance attributes is the same, the different input rates are only stored in the bindings:
|
||||
// instanced.vert:
|
||||
// layout (location = 0) in vec3 inPos; Per-Vertex
|
||||
// ...
|
||||
// layout (location = 4) in vec3 instancePos; Per-Instance
|
||||
std::array<vk::VertexInputAttributeDescription, 7> attribute_descriptions = {
|
||||
{ // Per-vertex attributes
|
||||
// These are advanced for each vertex fetched by the vertex shader
|
||||
{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Location 0: Position
|
||||
{1, 0, vk::Format::eR32G32B32Sfloat, 3 * sizeof(float)}, // Location 1: Normal
|
||||
{2, 0, vk::Format::eR32G32Sfloat, 6 * sizeof(float)}, // Location 2: Texture coordinates
|
||||
// Per-Instance attributes
|
||||
// These are fetched for each instance rendered
|
||||
{3, 1, vk::Format::eR32G32B32Sfloat, 0}, // Location 3: Position
|
||||
{4, 1, vk::Format::eR32G32B32Sfloat, 3 * sizeof(float)}, // Location 4: Rotation
|
||||
{5, 1, vk::Format::eR32Sfloat, 6 * sizeof(float)}, // Location 5: Scale
|
||||
{6, 1, vk::Format::eR32Sint, 7 * sizeof(float)}}}; // Location 6: Texture array layer index
|
||||
|
||||
// Use all input bindings and attribute descriptions
|
||||
vk::PipelineVertexInputStateCreateInfo input_state{.vertexBindingDescriptionCount = static_cast<uint32_t>(binding_descriptions.size()),
|
||||
.pVertexBindingDescriptions = binding_descriptions.data(),
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(attribute_descriptions.size()),
|
||||
.pVertexAttributeDescriptions = attribute_descriptions.data()};
|
||||
|
||||
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.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = true;
|
||||
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,
|
||||
input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eBack,
|
||||
vk::FrontFace::eClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPInstancing::create_starfield_pipeline()
|
||||
{
|
||||
// Starfield rendering pipeline
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("instancing", "starfield.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("instancing", "starfield.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex input bindings
|
||||
vk::VertexInputBindingDescription binding_description{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
|
||||
// Vertex attribute bindings
|
||||
std::array<vk::VertexInputAttributeDescription, 3> attribute_descriptions = {
|
||||
{ // Per-vertex attributes
|
||||
// These are advanced for each vertex fetched by the vertex shader
|
||||
{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Location 0: Position
|
||||
{1, 0, vk::Format::eR32G32B32Sfloat, 3 * sizeof(float)}, // Location 1: Normal
|
||||
{2, 0, vk::Format::eR32G32Sfloat, 6 * sizeof(float)}}}; // Location 2: Texture coordinates
|
||||
|
||||
// Use all input bindings and attribute descriptions
|
||||
vk::PipelineVertexInputStateCreateInfo input_state{.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &binding_description,
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(attribute_descriptions.size()),
|
||||
.pVertexAttributeDescriptions = attribute_descriptions.data()};
|
||||
|
||||
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.depthTestEnable = false;
|
||||
depth_stencil_state.depthWriteEnable = false;
|
||||
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,
|
||||
{}, // Vertices are generated in the vertex shader
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPInstancing::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
|
||||
// Submit to queue
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void HPPInstancing::load_assets()
|
||||
{
|
||||
rocks.mesh = load_model("scenes/rock.gltf");
|
||||
planet.mesh = load_model("scenes/planet.gltf");
|
||||
|
||||
rocks.texture = load_texture_array("textures/texturearray_rocks_color_rgba.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
planet.texture = load_texture("textures/lavaplanet_color_rgba.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
}
|
||||
|
||||
void HPPInstancing::initialize_camera()
|
||||
{
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_rotation(glm::vec3(-17.2f, -4.7f, 0.0f));
|
||||
camera.set_translation(glm::vec3(5.5f, -1.85f, -18.5f));
|
||||
|
||||
// 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 HPPInstancing::prepare_instance_data()
|
||||
{
|
||||
std::vector<InstanceData> instance_data;
|
||||
instance_data.resize(INSTANCE_COUNT);
|
||||
|
||||
std::default_random_engine rnd_generator(lock_simulation_speed ? 0 : static_cast<unsigned>(time(nullptr)));
|
||||
std::uniform_real_distribution<float> uniform_dist(0.0, 1.0);
|
||||
std::uniform_int_distribution<uint32_t> rnd_texture_index(0, rocks.texture.image->get_vk_image().get_array_layer_count());
|
||||
|
||||
// Distribute rocks randomly on two different rings
|
||||
glm::vec2 ring0{7.0f, 11.0f};
|
||||
glm::vec2 ring1{14.0f, 18.0f};
|
||||
for (auto i = 0, j = INSTANCE_COUNT / 2; i < INSTANCE_COUNT / 2; i++, j++)
|
||||
{
|
||||
float rho, theta;
|
||||
|
||||
// Inner ring
|
||||
rho = sqrt((pow(ring0[1], 2.0f) - pow(ring0[0], 2.0f)) * uniform_dist(rnd_generator) + pow(ring0[0], 2.0f));
|
||||
theta = 2.0f * glm::pi<float>() * uniform_dist(rnd_generator);
|
||||
instance_data[i].pos = glm::vec3(rho * cos(theta), uniform_dist(rnd_generator) * 0.5f - 0.25f, rho * sin(theta));
|
||||
instance_data[i].rot = glm::vec3(glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator));
|
||||
instance_data[i].scale = 1.5f + uniform_dist(rnd_generator) - uniform_dist(rnd_generator);
|
||||
instance_data[i].texIndex = rnd_texture_index(rnd_generator);
|
||||
instance_data[i].scale *= 0.75f;
|
||||
|
||||
// Outer ring
|
||||
rho = sqrt((pow(ring1[1], 2.0f) - pow(ring1[0], 2.0f)) * uniform_dist(rnd_generator) + pow(ring1[0], 2.0f));
|
||||
theta = 2.0f * glm::pi<float>() * uniform_dist(rnd_generator);
|
||||
instance_data[j].pos = glm::vec3(rho * cos(theta), uniform_dist(rnd_generator) * 0.5f - 0.25f, rho * sin(theta));
|
||||
instance_data[j].rot = glm::vec3(glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator));
|
||||
instance_data[j].scale = 1.5f + uniform_dist(rnd_generator) - uniform_dist(rnd_generator);
|
||||
instance_data[j].texIndex = rnd_texture_index(rnd_generator);
|
||||
instance_data[j].scale *= 0.75f;
|
||||
}
|
||||
|
||||
instance_buffer.size = instance_data.size() * sizeof(InstanceData);
|
||||
|
||||
// Staging
|
||||
// Instanced data is static, copy to device local memory
|
||||
// On devices with separate memory types for host visible and device local memory this will result in better performance
|
||||
// On devices with unified memory types (DEVICE_LOCAL_BIT and HOST_VISIBLE_BIT supported at once) this isn't necessary and you could skip the staging
|
||||
|
||||
auto const &device = get_device();
|
||||
|
||||
vkb::core::BufferCpp staging_buffer(get_device(), instance_buffer.size, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
staging_buffer.update(instance_data.data(), instance_buffer.size);
|
||||
|
||||
instance_buffer.buffer = std::make_unique<vkb::core::BufferCpp>(
|
||||
get_device(), instance_buffer.size, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
// Copy to staging buffer
|
||||
vk::CommandBuffer copy_command = get_device().create_command_buffer(vk::CommandBufferLevel::ePrimary, true);
|
||||
|
||||
vk::BufferCopy copy_region{.size = instance_buffer.size};
|
||||
copy_command.copyBuffer(staging_buffer.get_handle(), instance_buffer.buffer->get_handle(), copy_region);
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
|
||||
instance_buffer.descriptor.range = instance_buffer.size;
|
||||
instance_buffer.descriptor.buffer = instance_buffer.buffer->get_handle();
|
||||
instance_buffer.descriptor.offset = 0;
|
||||
}
|
||||
|
||||
void HPPInstancing::prepare_uniform_buffers()
|
||||
{
|
||||
uniform_buffers.scene =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(ubo_vs), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffer(0.0f);
|
||||
}
|
||||
|
||||
void HPPInstancing::update_uniform_buffer(float delta_time)
|
||||
{
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.view = camera.matrices.view;
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
ubo_vs.loc_speed += delta_time * 0.35f;
|
||||
ubo_vs.glob_speed += delta_time * 0.01f;
|
||||
}
|
||||
|
||||
uniform_buffers.scene->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
void HPPInstancing::update_planet_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo buffer_descriptor{uniform_buffers.scene->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorImageInfo image_descriptor{planet.texture.sampler,
|
||||
planet.texture.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
planet.texture.image->get_vk_image_view().get_format())};
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {{{.dstSet = planet.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &buffer_descriptor}, // Binding 0 : Vertex shader uniform buffer
|
||||
{.dstSet = planet.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &image_descriptor}}}; // Binding 1 : Color map
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPInstancing::update_rocks_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo buffer_descriptor{uniform_buffers.scene->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorImageInfo image_descriptor{rocks.texture.sampler,
|
||||
rocks.texture.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
rocks.texture.image->get_vk_image_view().get_format())};
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {{{.dstSet = rocks.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &buffer_descriptor}, // Binding 0 : Vertex shader uniform buffer
|
||||
{.dstSet = rocks.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &image_descriptor}}}; // Binding 1 : Color map
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_instancing()
|
||||
{
|
||||
return std::make_unique<HPPInstancing>();
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/* Copyright (c) 2022-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Instanced mesh rendering, uses a separate vertex buffer for instanced data, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
# define INSTANCE_COUNT 4096
|
||||
#else
|
||||
# define INSTANCE_COUNT 8192
|
||||
#endif
|
||||
|
||||
class HPPInstancing : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPInstancing();
|
||||
~HPPInstancing();
|
||||
|
||||
private:
|
||||
// Contains the instanced data
|
||||
struct InstanceBuffer
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferCpp> buffer;
|
||||
vk::DescriptorBufferInfo descriptor;
|
||||
size_t size = 0;
|
||||
};
|
||||
|
||||
// Per-instance data block
|
||||
struct InstanceData
|
||||
{
|
||||
glm::vec3 pos;
|
||||
glm::vec3 rot;
|
||||
float scale;
|
||||
uint32_t texIndex;
|
||||
};
|
||||
|
||||
struct Model
|
||||
{
|
||||
vk::DescriptorSet descriptor_set;
|
||||
std::unique_ptr<vkb::scene_graph::components::HPPSubMesh> mesh;
|
||||
vk::Pipeline pipeline;
|
||||
HPPTexture texture;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
mesh.reset();
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroySampler(texture.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
struct UBOVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
glm::vec4 light_pos = glm::vec4(0.0f, -5.0f, 0.0f, 1.0f);
|
||||
float loc_speed = 0.0f;
|
||||
float glob_speed = 0.0f;
|
||||
};
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferCpp> scene;
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void render(float delta_time) override;
|
||||
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::DescriptorSetLayout create_descriptor_set_layout();
|
||||
vk::Pipeline create_planet_pipeline();
|
||||
vk::Pipeline create_rocks_pipeline();
|
||||
vk::Pipeline create_starfield_pipeline();
|
||||
void draw();
|
||||
void load_assets();
|
||||
void initialize_camera();
|
||||
void prepare_instance_data();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffer(float delta_time);
|
||||
void update_planet_descriptor_set();
|
||||
void update_rocks_descriptor_set();
|
||||
|
||||
private:
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
InstanceBuffer instance_buffer;
|
||||
Model planet;
|
||||
Model rocks;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline starfield_pipeline;
|
||||
UBOVS ubo_vs;
|
||||
UniformBuffers uniform_buffers;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_instancing();
|
||||
@@ -1,35 +0,0 @@
|
||||
# Copyright (c) 2024, Google
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Google"
|
||||
NAME "HPP Order-independent transparency (depth peeling)"
|
||||
DESCRIPTION "Order-independent transparency using depth peeling using Vulkan-Hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"oit_depth_peeling/background.frag"
|
||||
"oit_depth_peeling/combine.frag"
|
||||
"oit_depth_peeling/fullscreen.vert"
|
||||
"oit_depth_peeling/gather.frag"
|
||||
"oit_depth_peeling/gather.vert"
|
||||
"oit_depth_peeling/gather_first.frag")
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2024, Google
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Order-independent transparency with depth peeling using Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_oit_depth_peeling[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: This is a transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/oit_depth_peeling[OIT depth peeling] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp. Please see there for the documentation on this sample.
|
||||
|
||||
:pp: {plus}{plus}
|
||||
|
||||
image::samples/api/oit_depth_peeling/images/sample.png[Sample]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample implements an order-independent transparency (OIT) algorithm using depth peeling.
|
||||
It renders a single torus whose opacity can be controlled via the UI.
|
||||
It produces pixel-perfect results.
|
||||
It is based on the https://developer.download.nvidia.com/assets/gamedev/docs/OrderIndependentTransparency.pdf[original paper] from Cass Everitt.
|
||||
|
||||
== Algorithm
|
||||
|
||||
The OIT algorithm consists of several _gather_ passes followed by one _combine_ pass.
|
||||
|
||||
Each _gather_ pass renders one layer of transparent geometry.
|
||||
The first pass renders the first layer, the second pass the second layer, etc.
|
||||
The N^th^ layer consists of all the N^th^ fragments of each pixel when the fragments are ordered from front to back.
|
||||
|
||||
The _combine_ pass is a screen-space operation.
|
||||
It merges the layer images from back to front to produce the final result.
|
||||
|
||||
The algorithm can produce pixel-perfect results, even with intersecting geometry.
|
||||
When there are more geometry layers than gather passes, the backmost layers get skipped, but the visual results stay stable (i.e. no flickering pixels).
|
||||
|
||||
== Options
|
||||
|
||||
[cols="2,4,4"]
|
||||
|===
|
||||
| Option | Description | Comments
|
||||
|
||||
| Camera auto-rotation
|
||||
| Enable the automatic rotation of the camera
|
||||
|
|
||||
|
||||
| Background grayscale
|
||||
| Specify the grayscale value by which the background color is multiplied (0.0 to 1.0)
|
||||
|
|
||||
|
||||
| Object alpha
|
||||
| Specify the opacity of the transparent object (0.0 to 1.0)
|
||||
|
|
||||
|
||||
| Front layer index
|
||||
| The first layer to be rendered (0 to 7).
|
||||
|
|
||||
|
||||
| Back layer index
|
||||
| The last layer to be rendered (0 to 7).
|
||||
| This cannot be less that the front layer index.
|
||||
|
||||
|===
|
||||
|
||||
@@ -1,595 +0,0 @@
|
||||
/* Copyright (c) 2024-2025, Google
|
||||
* Copyright (c) 2024-2025, NVIDIA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "hpp_oit_depth_peeling.h"
|
||||
|
||||
HPPOITDepthPeeling::HPPOITDepthPeeling()
|
||||
{}
|
||||
|
||||
HPPOITDepthPeeling::~HPPOITDepthPeeling()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
background.destroy(device);
|
||||
combinePass.destroy(device);
|
||||
for (auto &d : depths)
|
||||
{
|
||||
d.destroy();
|
||||
}
|
||||
device.destroyDescriptorPool(descriptor_pool);
|
||||
gatherPass.destroy(device);
|
||||
for (auto &l : layers)
|
||||
{
|
||||
l.destroy(device);
|
||||
}
|
||||
model.reset();
|
||||
device.destroySampler(point_sampler);
|
||||
scene_constants.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPOITDepthPeeling::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
prepare_camera();
|
||||
load_assets();
|
||||
create_point_sampler();
|
||||
create_scene_constants_buffer();
|
||||
create_descriptor_pool();
|
||||
create_combine_pass();
|
||||
create_images(extent.width, extent.height);
|
||||
create_gather_pass();
|
||||
create_background_pipeline();
|
||||
update_scene_constants();
|
||||
update_descriptors();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool HPPOITDepthPeeling::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
create_images(width, height);
|
||||
create_gather_pass_framebuffers(width, height);
|
||||
update_descriptors();
|
||||
return HPPApiVulkanSample::resize(width, height);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
|
||||
{
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("This sample requires support for anisotropic sampling");
|
||||
}
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
|
||||
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{.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f};
|
||||
vk::Rect2D scissor{{0, 0}, extent};
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
auto const &command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
{
|
||||
// Gather passes
|
||||
// Each pass renders a single transparent layer into a layer texture.
|
||||
for (uint32_t l = 0; l <= gui.layer_index_back; ++l)
|
||||
{
|
||||
// Two depth textures are used.
|
||||
// Their roles alternates for each pass.
|
||||
// The first depth texture is used for fixed-function depth test.
|
||||
// The second one is the result of the depth test from the previous gatherPass pass.
|
||||
// It is bound as texture and read in the shader to discard fragments from the
|
||||
// previous layers.
|
||||
vk::ImageSubresourceRange depth_subresource_range = {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1};
|
||||
vkb::common::image_layout_transition(command_buffer,
|
||||
depths[l % kDepthCount].image->get_handle(),
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
vk::AccessFlagBits::eShaderRead,
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
l <= 1 ? vk::ImageLayout::eUndefined : vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
depth_subresource_range);
|
||||
if (l > 0)
|
||||
{
|
||||
vkb::common::image_layout_transition(command_buffer,
|
||||
depths[(l + 1) % kDepthCount].image->get_handle(),
|
||||
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
vk::AccessFlagBits::eShaderRead,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
depth_subresource_range);
|
||||
}
|
||||
|
||||
// Set one of the layer textures as color attachment, as the gatherPass pass will render to it.
|
||||
vk::ImageSubresourceRange layer_subresource_range = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
vkb::common::image_layout_transition(command_buffer,
|
||||
layers[l].image->get_handle(),
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits::eShaderRead,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::eColorAttachmentOptimal,
|
||||
layer_subresource_range);
|
||||
|
||||
render_pass_begin_info.framebuffer = layers[l].gather_framebuffer;
|
||||
render_pass_begin_info.renderPass = gatherPass.render_pass;
|
||||
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
{
|
||||
// Render the geometry into the layer texture.
|
||||
command_buffer.setViewport(0, viewport);
|
||||
command_buffer.setScissor(0, scissor);
|
||||
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gatherPass.pipeline_layout, 0, depths[l % kDepthCount].gather_descriptor_set, {});
|
||||
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, l == 0 ? gatherPass.first_pipeline : gatherPass.pipeline);
|
||||
draw_model(model, command_buffer);
|
||||
}
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
// Get the layer texture ready to be read by the combinePass pass.
|
||||
vkb::common::image_layout_transition(command_buffer,
|
||||
layers[l].image->get_handle(),
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
vk::AccessFlagBits::eShaderRead,
|
||||
vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
layer_subresource_range);
|
||||
}
|
||||
|
||||
// Combine pass
|
||||
// This pass blends all the layers into the final transparent color.
|
||||
// The final color is then alpha blended into the background.
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
{
|
||||
command_buffer.setViewport(0, viewport);
|
||||
command_buffer.setScissor(0, scissor);
|
||||
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, combinePass.pipeline_layout, 0, combinePass.descriptor_set, {});
|
||||
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, background.pipeline);
|
||||
command_buffer.draw(3, 1, 0, 0);
|
||||
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, combinePass.pipeline);
|
||||
command_buffer.draw(3, 1, 0, 0);
|
||||
|
||||
draw_ui(command_buffer);
|
||||
}
|
||||
command_buffer.endRenderPass();
|
||||
}
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
drawer.checkbox("Camera auto-rotation", &gui.camera_auto_rotation);
|
||||
drawer.slider_float("Background grayscale", &gui.background_grayscale, kBackgroundGrayscaleMin, kBackgroundGrayscaleMax);
|
||||
drawer.slider_float("Object opacity", &gui.object_opacity, kObjectAlphaMin, kObjectAlphaMax);
|
||||
|
||||
drawer.slider_int("Front layer index", &gui.layer_index_front, 0, gui.layer_index_back);
|
||||
drawer.slider_int("Back layer index", &gui.layer_index_back, gui.layer_index_front, kLayerMaxCount - 1);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
queue.submit(submit_info);
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
|
||||
if (gui.camera_auto_rotation)
|
||||
{
|
||||
camera.rotate({delta_time * 5.0f, delta_time * 5.0f, 0.0f});
|
||||
}
|
||||
update_scene_constants();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void HPPOITDepthPeeling::create_background_pipeline()
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("oit_depth_peeling/fullscreen.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("oit_depth_peeling/background.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPVertex, pos)},
|
||||
{1, 0, vk::Format::eR32G32Sfloat, offsetof(HPPVertex, uv)}}};
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags};
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state{.depthCompareOp = vk::CompareOp::eGreater, .back = {.compareOp = vk::CompareOp::eAlways}};
|
||||
|
||||
background.pipeline = vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
{},
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
{},
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
combinePass.pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_combine_pass()
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> set_layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eCombinedImageSampler, kLayerMaxCount, vk::ShaderStageFlagBits::eFragment}}};
|
||||
combinePass.descriptor_set_layout = device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()), .pBindings = set_layout_bindings.data()});
|
||||
|
||||
combinePass.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, combinePass.descriptor_set_layout);
|
||||
|
||||
combinePass.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &combinePass.descriptor_set_layout});
|
||||
|
||||
create_combine_pass_pipeline();
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_combine_pass_pipeline()
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("oit_depth_peeling/fullscreen.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("oit_depth_peeling/combine.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPVertex, pos)},
|
||||
{1, 0, vk::Format::eR32G32Sfloat, offsetof(HPPVertex, uv)}}};
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment_state{true,
|
||||
vk::BlendFactor::eSrcAlpha,
|
||||
vk::BlendFactor::eOneMinusSrcColor,
|
||||
vk::BlendOp::eAdd,
|
||||
vk::BlendFactor::eOne,
|
||||
vk::BlendFactor::eZero,
|
||||
vk::BlendOp::eAdd,
|
||||
vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags};
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state{.depthCompareOp = vk::CompareOp::eGreater, .back = {.compareOp = vk::CompareOp::eAlways}};
|
||||
|
||||
combinePass.pipeline = vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
{},
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
{},
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
combinePass.pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_descriptor_pool()
|
||||
{
|
||||
const uint32_t num_gather_pass_combined_image_sampler = kDepthCount;
|
||||
const uint32_t num_gather_pass_uniform_buffer = kDepthCount;
|
||||
|
||||
const uint32_t num_combine_pass_combined_image_sampler = kLayerMaxCount + 1;
|
||||
const uint32_t num_combine_pass_uniform_buffer = 1;
|
||||
|
||||
const uint32_t num_uniform_buffer_descriptors = num_gather_pass_uniform_buffer + num_combine_pass_uniform_buffer;
|
||||
const uint32_t num_combined_image_sampler_descriptors = num_gather_pass_combined_image_sampler + num_combine_pass_combined_image_sampler;
|
||||
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, num_uniform_buffer_descriptors},
|
||||
{vk::DescriptorType::eCombinedImageSampler, num_combined_image_sampler_descriptors}}};
|
||||
const uint32_t num_gather_descriptor_sets = 2;
|
||||
const uint32_t num_combine_descriptor_sets = 1;
|
||||
const uint32_t num_descriptor_sets = num_gather_descriptor_sets + num_combine_descriptor_sets;
|
||||
vk::DescriptorPoolCreateInfo descriptor_pool_create_info{.maxSets = num_descriptor_sets,
|
||||
.poolSizeCount = static_cast<uint32_t>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data()};
|
||||
|
||||
descriptor_pool = get_device().get_handle().createDescriptorPool(descriptor_pool_create_info);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_gather_pass()
|
||||
{
|
||||
create_gather_pass_descriptor_set_layout();
|
||||
create_gather_pass_render_pass();
|
||||
create_gather_pass_depth_descriptor_sets();
|
||||
create_gather_pass_framebuffers(extent.width, extent.height);
|
||||
gatherPass.pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &gatherPass.descriptor_set_layout});
|
||||
create_gather_pass_pipelines();
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_gather_pass_depth_descriptor_sets()
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
for (auto &d : depths)
|
||||
{
|
||||
d.gather_descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, gatherPass.descriptor_set_layout);
|
||||
}
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_gather_pass_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> set_layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
gatherPass.descriptor_set_layout = get_device().get_handle().createDescriptorSetLayout(
|
||||
{.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()), .pBindings = set_layout_bindings.data()});
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_gather_pass_framebuffers(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
vk::FramebufferCreateInfo framebuffer_create_info{.width = width, .height = height, .layers = 1};
|
||||
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
|
||||
{
|
||||
if (layers[i].gather_framebuffer)
|
||||
{
|
||||
device.destroyFramebuffer(layers[i].gather_framebuffer);
|
||||
}
|
||||
framebuffer_create_info.renderPass = gatherPass.render_pass;
|
||||
const std::array<vk::ImageView, 2> attachments{layers[i].image_view->get_handle(), depths[i % kDepthCount].image_view->get_handle()};
|
||||
framebuffer_create_info.setAttachments(attachments);
|
||||
layers[i].gather_framebuffer = device.createFramebuffer(framebuffer_create_info);
|
||||
}
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_gather_pass_pipelines()
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("oit_depth_peeling/gather.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("oit_depth_peeling/gather_first.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPVertex, pos)},
|
||||
{1, 0, vk::Format::eR32G32Sfloat, offsetof(HPPVertex, uv)}}};
|
||||
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()};
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags};
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state{
|
||||
.depthTestEnable = true, .depthWriteEnable = true, .depthCompareOp = vk::CompareOp::eGreater, .back = {.compareOp = vk::CompareOp::eAlways}};
|
||||
|
||||
gatherPass.first_pipeline = vkb::common::create_graphics_pipeline(device,
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
{},
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
gatherPass.pipeline_layout,
|
||||
gatherPass.render_pass);
|
||||
|
||||
shader_stages[1] = load_shader("oit_depth_peeling/gather.frag.spv", vk::ShaderStageFlagBits::eFragment);
|
||||
|
||||
gatherPass.pipeline = vkb::common::create_graphics_pipeline(device,
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
{},
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
gatherPass.pipeline_layout,
|
||||
gatherPass.render_pass);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_gather_pass_render_pass()
|
||||
{
|
||||
std::array<vk::AttachmentDescription, 2> attachment_descriptions = {{{{},
|
||||
vk::Format::eR8G8B8A8Unorm,
|
||||
vk::SampleCountFlagBits::e1,
|
||||
vk::AttachmentLoadOp::eClear,
|
||||
vk::AttachmentStoreOp::eStore,
|
||||
vk::AttachmentLoadOp::eDontCare,
|
||||
vk::AttachmentStoreOp::eDontCare,
|
||||
vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::eColorAttachmentOptimal},
|
||||
{{},
|
||||
vk::Format::eD32Sfloat,
|
||||
vk::SampleCountFlagBits::e1,
|
||||
vk::AttachmentLoadOp::eClear,
|
||||
vk::AttachmentStoreOp::eStore,
|
||||
vk::AttachmentLoadOp::eDontCare,
|
||||
vk::AttachmentStoreOp::eDontCare,
|
||||
vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal}}};
|
||||
|
||||
vk::AttachmentReference color_attachment_reference{0, vk::ImageLayout::eColorAttachmentOptimal};
|
||||
vk::AttachmentReference depth_attachment_reference{1, vk::ImageLayout::eDepthStencilAttachmentOptimal};
|
||||
|
||||
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachments = &color_attachment_reference,
|
||||
.pDepthStencilAttachment = &depth_attachment_reference};
|
||||
|
||||
vk::RenderPassCreateInfo render_pass_create_info{.attachmentCount = static_cast<uint32_t>(attachment_descriptions.size()),
|
||||
.pAttachments = attachment_descriptions.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass};
|
||||
|
||||
gatherPass.render_pass = get_device().get_handle().createRenderPass(render_pass_create_info);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_images(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
const vk::Extent3D image_extent = {width, height, 1};
|
||||
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
|
||||
{
|
||||
layers[i].image = std::make_unique<vkb::core::HPPImage>(get_device(),
|
||||
image_extent,
|
||||
vk::Format::eR8G8B8A8Unorm,
|
||||
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eColorAttachment,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY,
|
||||
vk::SampleCountFlagBits::e1);
|
||||
layers[i].image_view = std::make_unique<vkb::core::HPPImageView>(*layers[i].image, vk::ImageViewType::e2D, vk::Format::eR8G8B8A8Unorm);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < kDepthCount; ++i)
|
||||
{
|
||||
depths[i].image = std::make_unique<vkb::core::HPPImage>(get_device(),
|
||||
image_extent,
|
||||
vk::Format::eD32Sfloat,
|
||||
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eDepthStencilAttachment,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY,
|
||||
vk::SampleCountFlagBits::e1);
|
||||
depths[i].image_view = std::make_unique<vkb::core::HPPImageView>(*depths[i].image, vk::ImageViewType::e2D, vk::Format::eD32Sfloat);
|
||||
}
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_point_sampler()
|
||||
{
|
||||
point_sampler = vkb::common::create_sampler(
|
||||
get_device().get_handle(), vk::Filter::eNearest, vk::Filter::eNearest, vk::SamplerMipmapMode::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::create_scene_constants_buffer()
|
||||
{
|
||||
scene_constants =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(SceneConstants), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::load_assets()
|
||||
{
|
||||
model = load_model("scenes/torusknot.gltf");
|
||||
background.texture = load_texture("textures/vulkan_logo_full.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::prepare_camera()
|
||||
{
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.0f, 0.0f, -4.0f});
|
||||
camera.set_rotation({0.0f, 0.0f, 0.0f});
|
||||
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 16.0f, 0.1f);
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::update_descriptors()
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
vk::DescriptorBufferInfo scene_constants_descriptor{scene_constants->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
for (uint32_t i = 0; i < kDepthCount; ++i)
|
||||
{
|
||||
vk::DescriptorImageInfo depth_texture_descriptor{point_sampler,
|
||||
depths[(i + 1) % kDepthCount].image_view->get_handle(),
|
||||
vk::ImageLayout::eDepthStencilReadOnlyOptimal};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {{{.dstSet = depths[i].gather_descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &scene_constants_descriptor},
|
||||
{.dstSet = depths[i].gather_descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &depth_texture_descriptor}}};
|
||||
device.updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
vk::DescriptorImageInfo background_texture_descriptor{background.texture.sampler,
|
||||
background.texture.image->get_vk_image_view().get_handle(),
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
|
||||
std::array<vk::DescriptorImageInfo, kLayerMaxCount> layer_texture_descriptor;
|
||||
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
|
||||
{
|
||||
layer_texture_descriptor[i].sampler = point_sampler;
|
||||
layer_texture_descriptor[i].imageView = layers[i].image_view->get_handle();
|
||||
layer_texture_descriptor[i].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> write_descriptor_sets = {{{.dstSet = combinePass.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &scene_constants_descriptor},
|
||||
{.dstSet = combinePass.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &background_texture_descriptor},
|
||||
{.dstSet = combinePass.descriptor_set,
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = static_cast<uint32_t>(layer_texture_descriptor.size()),
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = layer_texture_descriptor.data()}}};
|
||||
|
||||
device.updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPOITDepthPeeling::update_scene_constants()
|
||||
{
|
||||
SceneConstants constants = {};
|
||||
constants.model_view_projection = camera.matrices.perspective * camera.matrices.view * glm::scale(glm::mat4(1.0f), glm::vec3(0.08));
|
||||
constants.background_grayscale = gui.background_grayscale;
|
||||
constants.object_opacity = gui.object_opacity;
|
||||
constants.front_layer_index = gui.layer_index_front;
|
||||
constants.back_layer_index = gui.layer_index_back;
|
||||
scene_constants->convert_and_update(constants);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleCpp> create_hpp_oit_depth_peeling()
|
||||
{
|
||||
return std::make_unique<HPPOITDepthPeeling>();
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/* Copyright (c) 2024, Google
|
||||
* Copyright (c) 2024, NVIDIA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hpp_api_vulkan_sample.h"
|
||||
|
||||
class HPPOITDepthPeeling : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPOITDepthPeeling();
|
||||
~HPPOITDepthPeeling();
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void render(float delta_time) override;
|
||||
|
||||
void create_background_pipeline();
|
||||
void create_combine_pass();
|
||||
void create_combine_pass_pipeline();
|
||||
void create_descriptor_pool();
|
||||
void create_gather_pass();
|
||||
void create_gather_pass_depth_descriptor_sets();
|
||||
void create_gather_pass_descriptor_set_layout();
|
||||
void create_gather_pass_framebuffers(const uint32_t width, const uint32_t height);
|
||||
void create_gather_pass_pipelines();
|
||||
void create_gather_pass_render_pass();
|
||||
void create_images(const uint32_t width, const uint32_t height);
|
||||
void create_point_sampler();
|
||||
void create_scene_constants_buffer();
|
||||
void load_assets();
|
||||
void prepare_camera();
|
||||
void update_descriptors();
|
||||
void update_scene_constants();
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kLayerMaxCount = 8;
|
||||
|
||||
static constexpr uint32_t kDepthCount = 2;
|
||||
|
||||
static constexpr float kBackgroundGrayscaleMin = 0.0f;
|
||||
static constexpr float kBackgroundGrayscaleMax = 1.0f;
|
||||
|
||||
static constexpr float kObjectAlphaMin = 0.0f;
|
||||
static constexpr float kObjectAlphaMax = 1.0f;
|
||||
|
||||
struct Background
|
||||
{
|
||||
vk::Pipeline pipeline = {};
|
||||
HPPTexture texture = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroySampler(texture.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
struct CombinePass
|
||||
{
|
||||
vk::DescriptorSet descriptor_set = {};
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
// descriptor_set is implicitly destroyed when the managing descriptor_pool is destroyed
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
|
||||
descriptor_set_layout = nullptr;
|
||||
pipeline = nullptr;
|
||||
pipeline_layout = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct Depth
|
||||
{
|
||||
vk::DescriptorSet gather_descriptor_set = {};
|
||||
std::unique_ptr<vkb::core::HPPImage> image = {};
|
||||
std::unique_ptr<vkb::core::HPPImageView> image_view = {};
|
||||
|
||||
void destroy()
|
||||
{
|
||||
image_view.reset();
|
||||
image.reset();
|
||||
}
|
||||
};
|
||||
|
||||
struct GatherPass
|
||||
{
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::Pipeline first_pipeline = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
vk::RenderPass render_pass = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
device.destroyPipeline(first_pipeline);
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyRenderPass(render_pass);
|
||||
|
||||
descriptor_set_layout = nullptr;
|
||||
first_pipeline = nullptr;
|
||||
pipeline = nullptr;
|
||||
pipeline_layout = nullptr;
|
||||
render_pass = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct GUI
|
||||
{
|
||||
float background_grayscale = 0.3f;
|
||||
int32_t camera_auto_rotation = false;
|
||||
int32_t layer_index_back = kLayerMaxCount - 1;
|
||||
int32_t layer_index_front = 0;
|
||||
float object_opacity = 0.5f;
|
||||
};
|
||||
|
||||
struct Layer
|
||||
{
|
||||
vk::Framebuffer gather_framebuffer = {};
|
||||
std::unique_ptr<vkb::core::HPPImage> image = {};
|
||||
std::unique_ptr<vkb::core::HPPImageView> image_view = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyFramebuffer(gather_framebuffer);
|
||||
image_view.reset();
|
||||
image.reset();
|
||||
|
||||
gather_framebuffer = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct SceneConstants
|
||||
{
|
||||
glm::mat4 model_view_projection = {};
|
||||
glm::f32 background_grayscale = {};
|
||||
glm::f32 object_opacity = {};
|
||||
glm::int32 front_layer_index = {};
|
||||
glm::int32 back_layer_index = {};
|
||||
};
|
||||
|
||||
private:
|
||||
Background background = {};
|
||||
CombinePass combinePass = {};
|
||||
std::array<Depth, kDepthCount> depths = {};
|
||||
vk::DescriptorPool descriptor_pool = {};
|
||||
GatherPass gatherPass = {};
|
||||
GUI gui = {};
|
||||
std::array<Layer, kLayerMaxCount> layers = {};
|
||||
std::unique_ptr<vkb::scene_graph::components::HPPSubMesh> model = {};
|
||||
vk::Sampler point_sampler = {};
|
||||
std::unique_ptr<vkb::core::BufferCpp> scene_constants = {};
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleCpp> create_hpp_oit_depth_peeling();
|
||||
@@ -1,35 +0,0 @@
|
||||
# Copyright (c) 2022-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Google"
|
||||
NAME "HPP Order-independent transparency (ordered linked lists)"
|
||||
DESCRIPTION "Order-independent transparency using per-pixel ordered linked lists, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"oit_linked_lists/background.frag"
|
||||
"oit_linked_lists/combine.frag"
|
||||
"oit_linked_lists/combine.vert"
|
||||
"oit_linked_lists/fullscreen.vert"
|
||||
"oit_linked_lists/gather.frag"
|
||||
"oit_linked_lists/gather.vert")
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, NVIDIA
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Order-independent transparency with per-pixel ordered linked lists with Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_oit_linked_lists[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: This is a transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/oit_linked_lists[OIT linked lists] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp. Please see there for the documentation on this sample.
|
||||
@@ -1,505 +0,0 @@
|
||||
/* Copyright (c) 2023-2025, NVIDIA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "hpp_oit_linked_lists.h"
|
||||
|
||||
HPPOITLinkedLists::HPPOITLinkedLists()
|
||||
{
|
||||
title = "HPP OIT linked lists";
|
||||
}
|
||||
|
||||
HPPOITLinkedLists::~HPPOITLinkedLists()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
device.destroyPipeline(combine_pipeline);
|
||||
device.destroyPipeline(background_pipeline);
|
||||
device.destroyPipeline(gather_pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
|
||||
device.destroyDescriptorPool(descriptor_pool);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
|
||||
destroy_sized_objects();
|
||||
|
||||
device.destroySampler(background_texture.sampler);
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPOITLinkedLists::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
initialize_camera();
|
||||
load_assets();
|
||||
create_constant_buffers();
|
||||
create_descriptors();
|
||||
create_sized_objects(extent);
|
||||
create_pipelines();
|
||||
update_scene_constants();
|
||||
fill_instance_data();
|
||||
update_descriptors();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool HPPOITLinkedLists::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
if ((width != extent.width) || (height != extent.height))
|
||||
{
|
||||
destroy_sized_objects();
|
||||
create_sized_objects({width, height});
|
||||
update_descriptors();
|
||||
}
|
||||
return HPPApiVulkanSample::resize(width, height);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
|
||||
{
|
||||
auto &requested_features = gpu.get_mutable_requested_features();
|
||||
auto const &features = gpu.get_features();
|
||||
|
||||
if (features.fragmentStoresAndAtomics)
|
||||
{
|
||||
requested_features.fragmentStoresAndAtomics = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("This sample requires support for buffers and images stores and atomic operations in the fragment shader stage");
|
||||
}
|
||||
|
||||
// Enable anisotropic filtering if supported
|
||||
if (features.samplerAnisotropy)
|
||||
{
|
||||
requested_features.samplerAnisotropy = true;
|
||||
}
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
|
||||
vk::RenderPassBeginInfo gather_render_pass_begin_info{.renderPass = gather_render_pass,
|
||||
.framebuffer = gather_framebuffer,
|
||||
.renderArea = {{0, 0}, extent}};
|
||||
|
||||
std::array<vk::ClearValue, 2> combine_clear_values =
|
||||
{{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
|
||||
vk::ClearDepthStencilValue{0.0f, 0}}};
|
||||
vk::RenderPassBeginInfo combine_render_pass_begin_info{.renderPass = render_pass,
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(combine_clear_values.size()),
|
||||
.pClearValues = combine_clear_values.data()};
|
||||
|
||||
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f};
|
||||
vk::Rect2D scissor{{0, 0}, extent};
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
auto const &command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
{
|
||||
// Gather pass
|
||||
command_buffer.beginRenderPass(gather_render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
{
|
||||
command_buffer.setViewport(0, viewport);
|
||||
command_buffer.setScissor(0, scissor);
|
||||
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, gather_pipeline);
|
||||
draw_model(object, command_buffer, kInstanceCount);
|
||||
}
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
vkb::common::image_layout_transition(command_buffer,
|
||||
linked_list_head_image->get_handle(),
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eShaderWrite,
|
||||
vk::AccessFlagBits::eShaderRead,
|
||||
vk::ImageLayout::eGeneral,
|
||||
vk::ImageLayout::eGeneral,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
|
||||
|
||||
// Combine pass
|
||||
combine_render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
command_buffer.beginRenderPass(combine_render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
{
|
||||
command_buffer.setViewport(0, viewport);
|
||||
command_buffer.setScissor(0, scissor);
|
||||
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, background_pipeline);
|
||||
command_buffer.draw(3, 1, 0, 0);
|
||||
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, combine_pipeline);
|
||||
command_buffer.draw(3, 1, 0, 0);
|
||||
|
||||
draw_ui(command_buffer);
|
||||
}
|
||||
command_buffer.endRenderPass();
|
||||
}
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
drawer.checkbox("Sort fragments", &sort_fragments);
|
||||
drawer.checkbox("Camera auto-rotation", &camera_auto_rotation);
|
||||
drawer.slider_int("Sorted fragments per pixel", &sorted_fragment_count, kSortedFragmentMinCount, kSortedFragmentMaxCount);
|
||||
drawer.slider_float("Background grayscale", &background_grayscale, kBackgroundGrayscaleMin, kBackgroundGrayscaleMax);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
queue.submit(submit_info);
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
|
||||
if (camera_auto_rotation)
|
||||
{
|
||||
camera.rotate({delta_time * 5.0f, delta_time * 5.0f, 0.0f});
|
||||
}
|
||||
update_scene_constants();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void HPPOITLinkedLists::clear_sized_resources()
|
||||
{
|
||||
vk::CommandBuffer command_buffer = vkb::common::allocate_command_buffer(get_device().get_handle(), cmd_pool);
|
||||
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
{
|
||||
command_buffer.fillBuffer(fragment_counter->get_handle(), 0, sizeof(glm::uint), 0);
|
||||
|
||||
vk::ImageSubresourceRange subresource_range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
vkb::common::image_layout_transition(command_buffer,
|
||||
linked_list_head_image->get_handle(),
|
||||
vk::PipelineStageFlagBits::eBottomOfPipe,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlagBits::eTransferWrite,
|
||||
vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::eGeneral,
|
||||
subresource_range);
|
||||
|
||||
vk::ClearColorValue linked_lists_clear_value(kLinkedListEndSentinel, kLinkedListEndSentinel, kLinkedListEndSentinel, kLinkedListEndSentinel);
|
||||
command_buffer.clearColorImage(linked_list_head_image->get_handle(), vk::ImageLayout::eGeneral, linked_lists_clear_value, subresource_range);
|
||||
}
|
||||
command_buffer.end();
|
||||
|
||||
{
|
||||
vk::SubmitInfo submit_info{.commandBufferCount = 1, .pCommandBuffers = &command_buffer};
|
||||
queue.submit(submit_info);
|
||||
queue.waitIdle();
|
||||
}
|
||||
|
||||
get_device().get_handle().freeCommandBuffers(cmd_pool, command_buffer);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::create_constant_buffers()
|
||||
{
|
||||
scene_constants =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(SceneConstants), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
instance_data = std::make_unique<vkb::core::BufferCpp>(
|
||||
get_device(), sizeof(Instance) * kInstanceCount, vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPOITLinkedLists::create_descriptor_pool()
|
||||
{
|
||||
std::array<vk::DescriptorPoolSize, 4> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 2},
|
||||
{vk::DescriptorType::eStorageImage, 1},
|
||||
{vk::DescriptorType::eStorageBuffer, 2},
|
||||
{vk::DescriptorType::eCombinedImageSampler, 1}}};
|
||||
|
||||
vk::DescriptorPoolCreateInfo descriptor_pool_create_info{.maxSets = 1,
|
||||
.poolSizeCount = static_cast<uint32_t>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data()};
|
||||
return get_device().get_handle().createDescriptorPool(descriptor_pool_create_info);
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPOITLinkedLists::create_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 6> set_layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
{2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{3, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{4, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{5, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
return get_device().get_handle().createDescriptorSetLayout(
|
||||
{.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()), .pBindings = set_layout_bindings.data()});
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::create_descriptors()
|
||||
{
|
||||
descriptor_set_layout = create_descriptor_set_layout();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
descriptor_set = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, descriptor_set_layout);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::create_fragment_resources(vk::Extent2D const &extent)
|
||||
{
|
||||
const vk::Extent3D image_extent{extent.width, extent.height, 1};
|
||||
const vk::Format image_format{vk::Format::eR32Uint};
|
||||
linked_list_head_image = std::make_unique<vkb::core::HPPImage>(get_device(),
|
||||
image_extent,
|
||||
image_format,
|
||||
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferDst,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY,
|
||||
vk::SampleCountFlagBits::e1);
|
||||
linked_list_head_image_view = std::make_unique<vkb::core::HPPImageView>(*linked_list_head_image, vk::ImageViewType::e2D, image_format);
|
||||
|
||||
fragment_max_count = extent.width * extent.height * kFragmentsPerPixelAverage;
|
||||
const uint32_t fragment_buffer_size = sizeof(glm::uvec3) * fragment_max_count;
|
||||
fragment_buffer =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), fragment_buffer_size, vk::BufferUsageFlagBits::eStorageBuffer, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
fragment_counter = std::make_unique<vkb::core::BufferCpp>(
|
||||
get_device(), sizeof(glm::uint), vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferDst, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::create_gather_pass_objects(vk::Extent2D const &extent)
|
||||
{
|
||||
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics};
|
||||
gather_render_pass = get_device().get_handle().createRenderPass({.subpassCount = 1, .pSubpasses = &subpass});
|
||||
|
||||
gather_framebuffer = vkb::common::create_framebuffer(get_device().get_handle(), gather_render_pass, {}, extent);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::create_pipelines()
|
||||
{
|
||||
pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &descriptor_set_layout});
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.depthTestEnable = false;
|
||||
depth_stencil_state.depthWriteEnable = false;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
||||
depth_stencil_state.front = depth_stencil_state.back;
|
||||
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> gather_shader_stages = {load_shader("oit_linked_lists/gather.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("oit_linked_lists/gather.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
vk::VertexInputBindingDescription gather_vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
vk::VertexInputAttributeDescription gather_vertex_input_attribute{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPVertex, pos)};
|
||||
vk::PipelineVertexInputStateCreateInfo gather_vertex_input_state{.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &gather_vertex_input_binding,
|
||||
.vertexAttributeDescriptionCount = 1,
|
||||
.pVertexAttributeDescriptions = &gather_vertex_input_attribute};
|
||||
|
||||
gather_pipeline = vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
gather_shader_stages,
|
||||
gather_vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
gather_render_pass);
|
||||
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> background_shader_stages = {load_shader("oit_linked_lists/fullscreen.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("oit_linked_lists/background.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input_state;
|
||||
|
||||
background_pipeline = vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
background_shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> combine_shader_stages = {load_shader("oit_linked_lists/combine.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("oit_linked_lists/combine.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
vk::PipelineColorBlendAttachmentState combine_blend_attachment_state{true,
|
||||
vk::BlendFactor::eSrcAlpha,
|
||||
vk::BlendFactor::eOneMinusSrcAlpha,
|
||||
vk::BlendOp::eAdd,
|
||||
vk::BlendFactor::eOne,
|
||||
vk::BlendFactor::eZero,
|
||||
vk::BlendOp::eAdd,
|
||||
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
||||
|
||||
combine_pipeline = vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
combine_shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{combine_blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::create_sized_objects(vk::Extent2D const &extent)
|
||||
{
|
||||
create_gather_pass_objects(extent);
|
||||
create_fragment_resources(extent);
|
||||
clear_sized_resources();
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::destroy_sized_objects()
|
||||
{
|
||||
get_device().get_handle().destroyFramebuffer(gather_framebuffer);
|
||||
get_device().get_handle().destroyRenderPass(gather_render_pass);
|
||||
|
||||
fragment_counter.reset();
|
||||
fragment_buffer.reset();
|
||||
fragment_max_count = 0;
|
||||
linked_list_head_image_view.reset();
|
||||
linked_list_head_image.reset();
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::fill_instance_data()
|
||||
{
|
||||
Instance instances[kInstanceCount] = {};
|
||||
|
||||
auto get_random_float = []() { return static_cast<float>(rand()) / (RAND_MAX); };
|
||||
|
||||
for (uint32_t l = 0, instance_index = 0; l < kInstanceLayerCount; ++l)
|
||||
{
|
||||
for (uint32_t c = 0; c < kInstanceColumnCount; ++c)
|
||||
{
|
||||
for (uint32_t r = 0; r < kInstanceRowCount; ++r, ++instance_index)
|
||||
{
|
||||
const float x = static_cast<float>(r) - ((kInstanceRowCount - 1) * 0.5f);
|
||||
const float y = static_cast<float>(c) - ((kInstanceColumnCount - 1) * 0.5f);
|
||||
const float z = static_cast<float>(l) - ((kInstanceLayerCount - 1) * 0.5f);
|
||||
const float scale = 0.02f;
|
||||
instances[instance_index].model = glm::scale(glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z)), glm::vec3(scale));
|
||||
instances[instance_index].color.r = get_random_float();
|
||||
instances[instance_index].color.g = get_random_float();
|
||||
instances[instance_index].color.b = get_random_float();
|
||||
instances[instance_index].color.a = get_random_float() * 0.8f + 0.2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance_data->convert_and_update(instances);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::initialize_camera()
|
||||
{
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.0f, 0.0f, -4.0f});
|
||||
camera.set_rotation({0.0f, 0.0f, 0.0f});
|
||||
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 256.0f, 0.1f);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::load_assets()
|
||||
{
|
||||
object = load_model("scenes/geosphere.gltf");
|
||||
background_texture = load_texture("textures/vulkan_logo_full.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::update_descriptors()
|
||||
{
|
||||
vk::DescriptorBufferInfo scene_constants_descriptor{scene_constants->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorBufferInfo instance_data_descriptor{instance_data->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorImageInfo linked_list_head_image_view_descriptor{nullptr, linked_list_head_image_view->get_handle(), vk::ImageLayout::eGeneral};
|
||||
vk::DescriptorBufferInfo fragment_buffer_descriptor{fragment_buffer->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorBufferInfo fragment_counter_descriptor{fragment_counter->get_handle(), 0, vk::WholeSize};
|
||||
vk::DescriptorImageInfo background_texture_descriptor{background_texture.sampler,
|
||||
background_texture.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
background_texture.image->get_vk_image_view().get_format())};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 6> write_descriptor_sets = {{{.dstSet = descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &scene_constants_descriptor},
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &instance_data_descriptor},
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.pImageInfo = &linked_list_head_image_view_descriptor},
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 3,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageBuffer,
|
||||
.pBufferInfo = &fragment_buffer_descriptor},
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 4,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageBuffer,
|
||||
.pBufferInfo = &fragment_counter_descriptor},
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 5,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &background_texture_descriptor}}};
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPOITLinkedLists::update_scene_constants()
|
||||
{
|
||||
SceneConstants constants = {};
|
||||
constants.projection = camera.matrices.perspective;
|
||||
constants.view = camera.matrices.view;
|
||||
constants.background_grayscale = background_grayscale;
|
||||
constants.sort_fragments = sort_fragments ? 1U : 0U;
|
||||
constants.fragment_max_count = fragment_max_count;
|
||||
constants.sorted_fragment_count = sorted_fragment_count;
|
||||
scene_constants->convert_and_update(constants);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_oit_linked_lists()
|
||||
{
|
||||
return std::make_unique<HPPOITLinkedLists>();
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, NVIDIA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
class HPPOITLinkedLists : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPOITLinkedLists();
|
||||
~HPPOITLinkedLists();
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void render(float delta_time) override;
|
||||
|
||||
void clear_sized_resources();
|
||||
void create_constant_buffers();
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::DescriptorSetLayout create_descriptor_set_layout();
|
||||
void create_descriptors();
|
||||
void create_fragment_resources(vk::Extent2D const &extent);
|
||||
void create_gather_pass_objects(vk::Extent2D const &extent);
|
||||
void create_pipelines();
|
||||
void create_sized_objects(vk::Extent2D const &extent);
|
||||
void destroy_sized_objects();
|
||||
void fill_instance_data();
|
||||
void initialize_camera();
|
||||
void load_assets();
|
||||
void update_descriptors();
|
||||
void update_scene_constants();
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kInstanceRowCount = 4;
|
||||
static constexpr uint32_t kInstanceColumnCount = 4;
|
||||
static constexpr uint32_t kInstanceLayerCount = 4;
|
||||
static constexpr uint32_t kInstanceCount = kInstanceRowCount * kInstanceColumnCount * kInstanceLayerCount;
|
||||
|
||||
static constexpr uint32_t kFragmentsPerPixelAverage = 8;
|
||||
|
||||
static constexpr uint32_t kSortedFragmentMinCount = 1;
|
||||
static constexpr uint32_t kSortedFragmentMaxCount = 16;
|
||||
|
||||
static constexpr float kBackgroundGrayscaleMin = 0.0f;
|
||||
static constexpr float kBackgroundGrayscaleMax = 1.0f;
|
||||
|
||||
static constexpr uint32_t kLinkedListEndSentinel = 0xFFFFFFFFU;
|
||||
|
||||
struct SceneConstants
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
glm::f32 background_grayscale;
|
||||
glm::uint sort_fragments;
|
||||
glm::uint fragment_max_count;
|
||||
glm::uint sorted_fragment_count;
|
||||
};
|
||||
|
||||
struct Instance
|
||||
{
|
||||
glm::mat4 model;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr<vkb::scene_graph::components::HPPSubMesh> object;
|
||||
HPPTexture background_texture;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferCpp> scene_constants;
|
||||
std::unique_ptr<vkb::core::BufferCpp> instance_data;
|
||||
|
||||
std::unique_ptr<vkb::core::HPPImage> linked_list_head_image;
|
||||
std::unique_ptr<vkb::core::HPPImageView> linked_list_head_image_view;
|
||||
std::unique_ptr<vkb::core::BufferCpp> fragment_buffer;
|
||||
std::unique_ptr<vkb::core::BufferCpp> fragment_counter;
|
||||
glm::uint fragment_max_count = 0U;
|
||||
|
||||
vk::RenderPass gather_render_pass = nullptr;
|
||||
vk::Framebuffer gather_framebuffer = nullptr;
|
||||
|
||||
vk::DescriptorSetLayout descriptor_set_layout = nullptr;
|
||||
vk::DescriptorPool descriptor_pool = nullptr;
|
||||
vk::DescriptorSet descriptor_set = nullptr;
|
||||
|
||||
vk::PipelineLayout pipeline_layout = nullptr;
|
||||
vk::Pipeline gather_pipeline = nullptr;
|
||||
vk::Pipeline background_pipeline = nullptr;
|
||||
vk::Pipeline combine_pipeline = nullptr;
|
||||
|
||||
bool sort_fragments = true;
|
||||
bool camera_auto_rotation = false;
|
||||
int32_t sorted_fragment_count = kSortedFragmentMaxCount;
|
||||
float background_grayscale = 0.3f;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_oit_linked_lists();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2022-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Separate image sampler"
|
||||
DESCRIPTION "Displays a texture with a separated image and sampler"
|
||||
SHADER_FILES_GLSL
|
||||
"separate_image_sampler/glsl/separate_image_sampler.vert"
|
||||
"separate_image_sampler/glsl/separate_image_sampler.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"separate_image_sampler/hlsl/separate_image_sampler.vert.hlsl"
|
||||
"separate_image_sampler/hlsl/separate_image_sampler.frag.hlsl")
|
||||
@@ -1,143 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2022-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:doctype: book
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= Separating samplers and images with Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_separate_image_sampler[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/separate_image_sampler[Separate image sampler] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp.
|
||||
|
||||
This tutorial, along with the accompanying example code, shows how to separate samplers and images in a Vulkan application.
|
||||
Opposite to combined image and samplers, this allows the application to freely mix an arbitrary set of samplers and images in the shader.
|
||||
|
||||
In the sample code, a single image and multiple samplers with different options will be created.
|
||||
The sampler to be used for sampling the image can then be selected at runtime.
|
||||
As image and sampler objects are separated, this only requires selecting a different descriptor at runtime.
|
||||
|
||||
== In the application
|
||||
|
||||
From the application's point of view, images and samplers are always created separately.
|
||||
Access to the image is done via the image's `vk::ImageView`.
|
||||
Samplers are created using a `vk::Sampler` object, specifying how an image will be sampled.
|
||||
|
||||
The difference between separating and combining them starts at the descriptor level, which defines how the shader accesses the samplers and images.
|
||||
|
||||
A separate setup uses a descriptor of type `vk::DescriptorType::eSampledImage` for the sampled image, and a `vk::DescriptorType::eSampler` for the sampler, separating the image and sampler object:
|
||||
// {% raw %}
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Image info only references the image
|
||||
vk::DescriptorImageInfo image_info({}, texture.image->get_vk_image_view().get_handle(), vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
|
||||
// Sampled image descriptor
|
||||
vk::WriteDescriptorSet image_write_descriptor_set(base_descriptor_set, 1, 0, vk::DescriptorType::eSampledImage, image_info);
|
||||
|
||||
// One set for the sampled image
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {{
|
||||
{base_descriptor_set, 0, 0, vk::DescriptorType::eUniformBuffer, {}, buffer_descriptor}, // Binding 0 : Vertex shader uniform buffer
|
||||
image_write_descriptor_set // Binding 1 : Fragment shader sampled image
|
||||
}};
|
||||
get_device()->get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
----
|
||||
|
||||
// {% endraw %}
|
||||
|
||||
For this sample, we then create two samplers with different filtering options:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Sets for each of the sampler
|
||||
descriptor_set_alloc_info.pSetLayouts = &sampler_descriptor_set_layout;
|
||||
for (size_t i = 0; i < sampler_descriptor_sets.size(); i++)
|
||||
{
|
||||
sampler_descriptor_sets[i] = get_device()->get_handle().allocateDescriptorSets(descriptor_set_alloc_info).front();
|
||||
|
||||
// Descriptor info only references the sampler
|
||||
vk::DescriptorImageInfo sampler_info(samplers[i]);
|
||||
|
||||
vk::WriteDescriptorSet sampler_write_descriptor_set(sampler_descriptor_sets[i], 0, 0, vk::DescriptorType::eSampler, sampler_info);
|
||||
|
||||
get_device()->get_handle().updateDescriptorSets(sampler_write_descriptor_set, {});
|
||||
}
|
||||
----
|
||||
|
||||
At draw-time, the descriptor containing the sampled image is bound to set 0 and the descriptor for the currently selected sampler is bound to set 1:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Bind the uniform buffer and sampled image to set 0
|
||||
draw_cmd_buffers[i].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, base_descriptor_set, {});
|
||||
// Bind the selected sampler to set 1
|
||||
draw_cmd_buffers[i].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 1, sampler_descriptor_sets[selected_sampler], {});
|
||||
...
|
||||
draw_cmd_buffers[i].drawIndexed(index_count, 1, 0, 0, 0);
|
||||
----
|
||||
|
||||
== In the shader
|
||||
|
||||
There are no changes in the shader code to get it working with vulkan.hpp.
|
||||
With the above setup, the shader interface for the fragment shader also separates the sampler and image as two distinct uniforms:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout (set = 0, binding = 1) uniform texture2D _texture;
|
||||
layout (set = 1, binding = 0) uniform sampler _sampler;
|
||||
----
|
||||
|
||||
To sample from the image referenced by `_texture`, with the currently set sampler in '_sampler', we create a sampled image in the fragment shader at runtime using the `sampler2D` function.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(sampler2D(_texture, _sampler), inUV);
|
||||
}
|
||||
----
|
||||
|
||||
== Comparison with combined image samplers
|
||||
|
||||
For reference, a combined image and sampler setup would differ for both the application and the shader.
|
||||
The app would use a single descriptor of type `VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`, and set both image and sampler related values in the descriptor:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Descriptor info references image and sampler
|
||||
vk::DescriptorImageInfo image_info(texture.sampler, texture.view, texture.image_layout);
|
||||
|
||||
vk::WriteDescriptorSet image_write_descriptor_set(descriptor_set, 1, {}, vk::DescriptorType::eCombinedImageSampler, image_info);
|
||||
----
|
||||
|
||||
The shader interface only uses one uniform for accessing the combined image and sampler and also doesn't construct a `sampler2D` at runtime:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout (binding = 1) uniform sampler2D _combined_image;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(_combined_image, inUV);
|
||||
}
|
||||
----
|
||||
|
||||
Compared to the separated setup, changing a sampler in this setup would either require creating multiple descriptors with each image/sampler combination or rebuilding the descriptor.
|
||||
@@ -1,393 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Separate samplers and image to draw a single image with different sampling options, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_separate_image_sampler.h"
|
||||
|
||||
HPPSeparateImageSampler::HPPSeparateImageSampler()
|
||||
{
|
||||
title = "HPP Separate sampler and image";
|
||||
|
||||
zoom = -0.5f;
|
||||
rotation = {45.0f, 0.0f, 0.0f};
|
||||
}
|
||||
|
||||
HPPSeparateImageSampler::~HPPSeparateImageSampler()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(base_descriptor_set_layout);
|
||||
device.destroyDescriptorSetLayout(sampler_descriptor_set_layout);
|
||||
for (vk::Sampler sampler : samplers)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
}
|
||||
// Delete the implicitly created sampler for the texture loaded via the framework
|
||||
device.destroySampler(texture.sampler);
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPSeparateImageSampler::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
load_assets();
|
||||
generate_quad();
|
||||
prepare_uniform_buffers();
|
||||
|
||||
// Create two samplers with different options, first one with linear filtering, the second one with nearest filtering
|
||||
samplers = {{create_sampler(vk::Filter::eLinear), create_sampler(vk::Filter::eNearest)}};
|
||||
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
|
||||
// We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former
|
||||
// Descriptors set for the uniform buffer and the image
|
||||
base_descriptor_set_layout = create_base_descriptor_set_layout();
|
||||
base_descriptor_set = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, base_descriptor_set_layout);
|
||||
update_base_descriptor_set();
|
||||
|
||||
// Sets for each of the sampler
|
||||
sampler_descriptor_set_layout = create_sampler_descriptor_set_layout();
|
||||
for (size_t i = 0; i < sampler_descriptor_sets.size(); i++)
|
||||
{
|
||||
sampler_descriptor_sets[i] = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, sampler_descriptor_set_layout);
|
||||
update_sampler_descriptor_set(i);
|
||||
}
|
||||
|
||||
// Pipeline layout
|
||||
// Set layout for the base descriptors in set 0 and set layout for the sampler descriptors in set 1
|
||||
pipeline_layout = create_pipeline_layout({base_descriptor_set_layout, sampler_descriptor_set_layout});
|
||||
pipeline = create_graphics_pipeline();
|
||||
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
// Enable physical device features required for this example
|
||||
void HPPSeparateImageSampler::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 HPPSeparateImageSampler::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
std::array<vk::ClearValue, 2> clear_values = {{default_clear_color, vk::ClearDepthStencilValue{0.0f, 0}}};
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
// Set target frame buffer
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
auto command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
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);
|
||||
|
||||
// Bind the uniform buffer and sampled image to set 0
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, base_descriptor_set, {});
|
||||
// Bind the selected sampler to set 1
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 1, sampler_descriptor_sets[selected_sampler], {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
|
||||
vk::DeviceSize offset = 0;
|
||||
command_buffer.bindVertexBuffers(0, vertex_buffer->get_handle(), offset);
|
||||
command_buffer.bindIndexBuffer(index_buffer->get_handle(), 0, vk::IndexType::eUint32);
|
||||
|
||||
command_buffer.drawIndexed(index_count, 1, 0, 0, 0);
|
||||
|
||||
draw_ui(command_buffer);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
const std::vector<std::string> sampler_names = {"Linear filtering", "Nearest filtering"};
|
||||
if (drawer.combo_box("Sampler", &selected_sampler, sampler_names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPSeparateImageSampler::create_base_descriptor_set_layout()
|
||||
{
|
||||
// Set layout for the uniform buffer and the image
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> set_layout_bindings_buffer_and_image = {{
|
||||
{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex}, // Binding 0 : Vertex shader uniform buffer
|
||||
{1, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment} // Binding 1 : Fragment shader sampled image
|
||||
}};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_layout_create_info{.bindingCount = static_cast<uint32_t>(set_layout_bindings_buffer_and_image.size()),
|
||||
.pBindings = set_layout_bindings_buffer_and_image.data()};
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout(descriptor_layout_create_info);
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPSeparateImageSampler::create_descriptor_pool()
|
||||
{
|
||||
std::array<vk::DescriptorPoolSize, 3> pool_sizes = {
|
||||
{{vk::DescriptorType::eUniformBuffer, 1}, {vk::DescriptorType::eSampledImage, 1}, {vk::DescriptorType::eSampler, 2}}};
|
||||
|
||||
vk::DescriptorPoolCreateInfo descriptor_pool_create_info{.maxSets = 3,
|
||||
.poolSizeCount = static_cast<uint32_t>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data()};
|
||||
|
||||
return get_device().get_handle().createDescriptorPool(descriptor_pool_create_info);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPSeparateImageSampler::create_graphics_pipeline()
|
||||
{
|
||||
// Load shaders
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {
|
||||
load_shader("separate_image_sampler", "separate_image_sampler.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("separate_image_sampler", "separate_image_sampler.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex bindings and attributes
|
||||
vk::VertexInputBindingDescription input_binding{0, sizeof(VertexStructure), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 3> input_attributes = {
|
||||
{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexStructure, pos)}, // Location 0 : Position
|
||||
{1, 0, vk::Format::eR32G32Sfloat, offsetof(VertexStructure, uv)}, // Location 1 : Texture Coordinates
|
||||
{2, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexStructure, normal)}}}; // Location 2 : Normal
|
||||
vk::PipelineVertexInputStateCreateInfo input_state{.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &input_binding,
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(input_attributes.size()),
|
||||
.pVertexAttributeDescriptions = input_attributes.data()};
|
||||
|
||||
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.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = true;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eGreater;
|
||||
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
vk::PipelineLayout HPPSeparateImageSampler::create_pipeline_layout(std::vector<vk::DescriptorSetLayout> const &descriptor_set_layouts)
|
||||
{
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_create_info{.setLayoutCount = static_cast<uint32_t>(descriptor_set_layouts.size()),
|
||||
.pSetLayouts = descriptor_set_layouts.data()};
|
||||
|
||||
return get_device().get_handle().createPipelineLayout(pipeline_layout_create_info);
|
||||
}
|
||||
|
||||
vk::Sampler HPPSeparateImageSampler::create_sampler(vk::Filter filter)
|
||||
{
|
||||
return vkb::common::create_sampler(
|
||||
get_device().get_gpu().get_handle(),
|
||||
get_device().get_handle(),
|
||||
texture.image->get_format(),
|
||||
filter,
|
||||
vk::SamplerAddressMode::eRepeat,
|
||||
get_device().get_gpu().get_features().samplerAnisotropy ? (get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy) : 1.0f,
|
||||
static_cast<float>(texture.image->get_mipmaps().size()));
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPSeparateImageSampler::create_sampler_descriptor_set_layout()
|
||||
{
|
||||
// Set layout for the samplers
|
||||
vk::DescriptorSetLayoutBinding set_layout_binding_sampler{0, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_layout_create_info{.bindingCount = 1, .pBindings = &set_layout_binding_sampler};
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout(descriptor_layout_create_info);
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
|
||||
// Submit to queue
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::generate_quad()
|
||||
{
|
||||
// Setup vertices for a single uv-mapped quad made from two triangles
|
||||
std::vector<VertexStructure> vertices =
|
||||
{
|
||||
{{1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}};
|
||||
|
||||
// Setup indices
|
||||
std::vector<uint32_t> indices = {0, 1, 2, 2, 3, 0};
|
||||
index_count = static_cast<uint32_t>(indices.size());
|
||||
|
||||
auto vertex_buffer_size = vkb::to_u32(vertices.size() * sizeof(VertexStructure));
|
||||
auto index_buffer_size = vkb::to_u32(indices.size() * sizeof(uint32_t));
|
||||
|
||||
// Create buffers
|
||||
// For the sake of simplicity we won't stage the vertex data to the gpu memory
|
||||
// Vertex buffer
|
||||
vertex_buffer = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
||||
vertex_buffer_size,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
vertex_buffer->update(vertices.data(), vertex_buffer_size);
|
||||
|
||||
index_buffer = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
||||
index_buffer_size,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
index_buffer->update(indices.data(), index_buffer_size);
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::load_assets()
|
||||
{
|
||||
texture = load_texture("textures/metalplate01_rgba.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void HPPSeparateImageSampler::prepare_uniform_buffers()
|
||||
{
|
||||
// Vertex shader uniform buffer block
|
||||
uniform_buffer_vs =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(ubo_vs), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::update_base_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo buffer_descriptor{uniform_buffer_vs->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
// Image info only references the image
|
||||
vk::DescriptorImageInfo image_info{{}, texture.image->get_vk_image_view().get_handle(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
|
||||
// Sampled image descriptor
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {
|
||||
{
|
||||
{.dstSet = base_descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &buffer_descriptor}, // Binding 0 : Vertex shader uniform buffer
|
||||
{.dstSet = base_descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
.pImageInfo = &image_info} // Binding 1 : Fragment shader sampled image
|
||||
}};
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::update_sampler_descriptor_set(size_t index)
|
||||
{
|
||||
assert((index < samplers.size()) && (index < sampler_descriptor_sets.size()));
|
||||
|
||||
// Descriptor info only references the sampler
|
||||
vk::DescriptorImageInfo sampler_info{samplers[index]};
|
||||
|
||||
vk::WriteDescriptorSet sampler_write_descriptor_set{
|
||||
.dstSet = sampler_descriptor_sets[index], .dstBinding = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eSampler, .pImageInfo = &sampler_info};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(sampler_write_descriptor_set, {});
|
||||
}
|
||||
|
||||
void HPPSeparateImageSampler::update_uniform_buffers()
|
||||
{
|
||||
// Vertex shader
|
||||
ubo_vs.projection = glm::perspective(glm::radians(60.0f), static_cast<float>(extent.width) / static_cast<float>(extent.height), 0.001f, 256.0f);
|
||||
glm::mat4 view_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom));
|
||||
|
||||
ubo_vs.model = view_matrix * glm::translate(glm::mat4(1.0f), camera_pos);
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
ubo_vs.view_pos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f);
|
||||
|
||||
uniform_buffer_vs->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_separate_image_sampler()
|
||||
{
|
||||
return std::make_unique<HPPSeparateImageSampler>();
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Separate samplers and image to draw a single image with different sampling options, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
class HPPSeparateImageSampler : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPSeparateImageSampler();
|
||||
~HPPSeparateImageSampler() override;
|
||||
|
||||
private:
|
||||
// Vertex layout for this example
|
||||
struct VertexStructure
|
||||
{
|
||||
float pos[3];
|
||||
float uv[2];
|
||||
float normal[3];
|
||||
};
|
||||
|
||||
struct UBO
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
glm::vec4 view_pos;
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void render(float delta_time) override;
|
||||
void view_changed() override;
|
||||
|
||||
vk::DescriptorSetLayout create_base_descriptor_set_layout();
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::Pipeline create_graphics_pipeline();
|
||||
vk::PipelineLayout create_pipeline_layout(std::vector<vk::DescriptorSetLayout> const &descriptor_set_layouts);
|
||||
vk::Sampler create_sampler(vk::Filter filter);
|
||||
vk::DescriptorSetLayout create_sampler_descriptor_set_layout();
|
||||
void draw();
|
||||
void generate_quad();
|
||||
void load_assets();
|
||||
void prepare_uniform_buffers();
|
||||
void update_base_descriptor_set();
|
||||
void update_sampler_descriptor_set(size_t index);
|
||||
void update_uniform_buffers();
|
||||
|
||||
private:
|
||||
vk::DescriptorSet base_descriptor_set;
|
||||
vk::DescriptorSetLayout base_descriptor_set_layout;
|
||||
std::unique_ptr<vkb::core::BufferCpp> index_buffer;
|
||||
uint32_t index_count = 0;
|
||||
vk::Pipeline pipeline;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::DescriptorSetLayout sampler_descriptor_set_layout;
|
||||
std::array<vk::DescriptorSet, 2> sampler_descriptor_sets;
|
||||
std::array<vk::Sampler, 2> samplers;
|
||||
int32_t selected_sampler = 0;
|
||||
HPPTexture texture;
|
||||
UBO ubo_vs;
|
||||
std::unique_ptr<vkb::core::BufferCpp> uniform_buffer_vs;
|
||||
std::unique_ptr<vkb::core::BufferCpp> vertex_buffer;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_separate_image_sampler();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2022-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Terrain tessellation"
|
||||
DESCRIPTION "Using tessellation shaders for dynamic terrain level-of-detail, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"terrain_tessellation/glsl/terrain.vert"
|
||||
"terrain_tessellation/glsl/terrain.frag"
|
||||
"terrain_tessellation/glsl/terrain.tesc"
|
||||
"terrain_tessellation/glsl/terrain.tese"
|
||||
"terrain_tessellation/glsl/skysphere.vert"
|
||||
"terrain_tessellation/glsl/skysphere.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"terrain_tessellation/hlsl/terrain.vert.hlsl"
|
||||
"terrain_tessellation/hlsl/terrain.frag.hlsl"
|
||||
"terrain_tessellation/hlsl/terrain.tesc.hlsl"
|
||||
"terrain_tessellation/hlsl/terrain.tese.hlsl"
|
||||
"terrain_tessellation/hlsl/skysphere.vert.hlsl"
|
||||
"terrain_tessellation/hlsl/skysphere.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= Terrain Tessellation with Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_terrain_tessellation[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/terrain_tessellation[Terrain Tessellation] that illustrates the usage of the C{pp} bindings of Vulkan provided by vulkan.hpp.
|
||||
@@ -1,615 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dynamic terrain tessellation, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_terrain_tessellation.h"
|
||||
#include "core/command_pool.h"
|
||||
#include "heightmap.h"
|
||||
|
||||
HPPTerrainTessellation::HPPTerrainTessellation()
|
||||
{
|
||||
title = "HPP Dynamic terrain tessellation";
|
||||
}
|
||||
|
||||
HPPTerrainTessellation::~HPPTerrainTessellation()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
sky_sphere.destroy(device);
|
||||
terrain.destroy(device);
|
||||
wireframe.destroy(device);
|
||||
statistics.destroy(device);
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPTerrainTessellation::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
prepare_camera();
|
||||
load_assets();
|
||||
generate_terrain();
|
||||
prepare_uniform_buffers();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
prepare_sky_sphere();
|
||||
prepare_terrain();
|
||||
prepare_wireframe();
|
||||
prepare_statistics();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
|
||||
{
|
||||
// Tessellation shader support is required for this example
|
||||
auto &available_features = gpu.get_features();
|
||||
if (!available_features.tessellationShader)
|
||||
{
|
||||
throw vkb::VulkanException(VK_ERROR_FEATURE_NOT_PRESENT, "Selected GPU does not support tessellation shaders!");
|
||||
}
|
||||
|
||||
auto &requested_features = gpu.get_mutable_requested_features();
|
||||
|
||||
requested_features.tessellationShader = true;
|
||||
|
||||
// Fill mode non solid is required for wireframe display
|
||||
requested_features.fillModeNonSolid = available_features.fillModeNonSolid;
|
||||
wireframe.supported = available_features.fillModeNonSolid;
|
||||
|
||||
// Pipeline statistics
|
||||
requested_features.pipelineStatisticsQuery = available_features.pipelineStatisticsQuery;
|
||||
statistics.query_supported = available_features.pipelineStatisticsQuery;
|
||||
|
||||
// Enable anisotropic filtering if supported
|
||||
requested_features.samplerAnisotropy = available_features.samplerAnisotropy;
|
||||
terrain.sampler_anisotropy_supported = available_features.samplerAnisotropy;
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
std::array<vk::ClearValue, 2> clear_values = {{default_clear_color, vk::ClearDepthStencilValue{0.0f, 0}}};
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
auto command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
if (statistics.query_supported)
|
||||
{
|
||||
command_buffer.resetQueryPool(statistics.query_pool, 0, 2);
|
||||
}
|
||||
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
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);
|
||||
|
||||
vk::DeviceSize offset = 0;
|
||||
|
||||
// Skysphere
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, sky_sphere.pipeline);
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, sky_sphere.pipeline_layout, 0, sky_sphere.descriptor_set, {});
|
||||
draw_model(sky_sphere.geometry, command_buffer);
|
||||
|
||||
// Terrain
|
||||
if (statistics.query_supported)
|
||||
{
|
||||
// Begin pipeline statistics query
|
||||
command_buffer.beginQuery(statistics.query_pool, 0, {});
|
||||
}
|
||||
|
||||
// Render
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, wireframe.enabled ? wireframe.pipeline : terrain.pipeline);
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, terrain.pipeline_layout, 0, terrain.descriptor_set, {});
|
||||
command_buffer.bindVertexBuffers(0, terrain.vertices->get_handle(), offset);
|
||||
command_buffer.bindIndexBuffer(terrain.indices->get_handle(), 0, vk::IndexType::eUint32);
|
||||
command_buffer.drawIndexed(terrain.index_count, 1, 0, 0, 0);
|
||||
|
||||
if (statistics.query_supported)
|
||||
{
|
||||
// End pipeline statistics query
|
||||
command_buffer.endQuery(statistics.query_pool, 0);
|
||||
}
|
||||
|
||||
draw_ui(command_buffer);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.checkbox("Tessellation", &terrain.tessellation_enabled))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
if (drawer.input_float("Factor", &terrain.tessellation.tessellation_factor, 0.05f, "%.2f"))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
if (wireframe.supported)
|
||||
{
|
||||
if (drawer.checkbox("Wireframe", &wireframe.enabled))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (statistics.query_supported)
|
||||
{
|
||||
if (drawer.header("Pipeline statistics"))
|
||||
{
|
||||
drawer.text("VS invocations: %d", statistics.results[0]);
|
||||
drawer.text("TE invocations: %d", statistics.results[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPTerrainTessellation::create_descriptor_pool()
|
||||
{
|
||||
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 3}, {vk::DescriptorType::eCombinedImageSampler, 3}}};
|
||||
|
||||
return get_device().get_handle().createDescriptorPool(
|
||||
{.maxSets = 2, .poolSizeCount = static_cast<uint32_t>(pool_sizes.size()), .pPoolSizes = pool_sizes.data()});
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPTerrainTessellation::create_sky_sphere_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
vk::DescriptorSetLayoutCreateInfo skysphere_descriptor_layout{.bindingCount = static_cast<uint32_t>(layout_bindings.size()),
|
||||
.pBindings = layout_bindings.data()};
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout(skysphere_descriptor_layout);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPTerrainTessellation::create_sky_sphere_pipeline()
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {
|
||||
load_shader("terrain_tessellation", "skysphere.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("terrain_tessellation", "skysphere.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex bindings an attributes
|
||||
// Binding description
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
|
||||
// Attribute descriptions
|
||||
std::array<vk::VertexInputAttributeDescription, 3> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Position
|
||||
{1, 0, vk::Format::eR32G32B32Sfloat, sizeof(float) * 3}, // Normal
|
||||
{2, 0, vk::Format::eR32G32Sfloat, sizeof(float) * 6}}}; // UV
|
||||
|
||||
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()};
|
||||
|
||||
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.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = false;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.front = depth_stencil_state.back;
|
||||
|
||||
// For the sky_sphere use triangle list topology
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eBack,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
sky_sphere.pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPTerrainTessellation::create_terrain_descriptor_set_layout()
|
||||
{
|
||||
// Terrain
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eTessellationControl | vk::ShaderStageFlagBits::eTessellationEvaluation},
|
||||
{1,
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eTessellationControl | vk::ShaderStageFlagBits::eTessellationEvaluation | vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo terrain_descriptor_layout{.bindingCount = static_cast<uint32_t>(layout_bindings.size()),
|
||||
.pBindings = layout_bindings.data()};
|
||||
return get_device().get_handle().createDescriptorSetLayout(terrain_descriptor_layout);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPTerrainTessellation::create_terrain_pipeline(vk::PolygonMode polygon_mode)
|
||||
{
|
||||
// Vertex bindings an attributes
|
||||
// Binding description
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPTerrainTessellation::Vertex), vk::VertexInputRate::eVertex};
|
||||
|
||||
// Attribute descriptions
|
||||
std::array<vk::VertexInputAttributeDescription, 3> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Position
|
||||
{1, 0, vk::Format::eR32G32B32Sfloat, sizeof(float) * 3}, // Normal
|
||||
{2, 0, vk::Format::eR32G32Sfloat, sizeof(float) * 6}}}; // UV
|
||||
|
||||
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()};
|
||||
|
||||
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.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = true;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.front = depth_stencil_state.back;
|
||||
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
terrain.shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::ePatchList,
|
||||
4, // we render the terrain as a grid of quad patches
|
||||
polygon_mode,
|
||||
vk::CullModeFlagBits::eBack,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
terrain.pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
|
||||
// Submit to queue
|
||||
queue.submit(submit_info);
|
||||
|
||||
if (statistics.query_supported)
|
||||
{
|
||||
// Read query results for displaying in next frame
|
||||
auto result = get_device()
|
||||
.get_handle()
|
||||
.getQueryPoolResult<std::array<uint64_t, 2>>(statistics.query_pool, 0, 1, sizeof(statistics.results), vk::QueryResultFlagBits::e64);
|
||||
if (result.result == vk::Result::eSuccess)
|
||||
{
|
||||
statistics.results = result.value;
|
||||
}
|
||||
}
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
// Generate a terrain quad patch for feeding to the tessellation control shader
|
||||
void HPPTerrainTessellation::generate_terrain()
|
||||
{
|
||||
const uint32_t patch_size = 64;
|
||||
const float uv_scale = 1.0f;
|
||||
const uint32_t vertex_count = patch_size * patch_size;
|
||||
std::vector<Vertex> vertices;
|
||||
vertices.resize(vertex_count);
|
||||
|
||||
for (auto x = 0; x < patch_size; x++)
|
||||
{
|
||||
for (auto y = 0; y < patch_size; y++)
|
||||
{
|
||||
uint32_t index = (x + y * patch_size);
|
||||
vertices[index].pos[0] = 2.0f * x + 1.0f - patch_size;
|
||||
vertices[index].pos[1] = 0.0f;
|
||||
vertices[index].pos[2] = 2.0f * y * 1.0f - patch_size;
|
||||
vertices[index].uv = glm::vec2(static_cast<float>(x) / patch_size, static_cast<float>(y) / patch_size) * uv_scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate normals from height map using a sobel filter
|
||||
vkb::HeightMap height_map("textures/terrain_heightmap_r16.ktx", patch_size);
|
||||
for (auto x = 0; x < patch_size; x++)
|
||||
{
|
||||
for (auto y = 0; y < patch_size; y++)
|
||||
{
|
||||
// Get height samples centered around current position
|
||||
float heights[3][3];
|
||||
for (auto hx = -1; hx <= 1; hx++)
|
||||
{
|
||||
for (auto hy = -1; hy <= 1; hy++)
|
||||
{
|
||||
heights[hx + 1][hy + 1] = height_map.get_height(x + hx, y + hy);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the normal
|
||||
glm::vec3 normal;
|
||||
// Gx sobel filter
|
||||
normal.x = heights[0][0] - heights[2][0] + 2.0f * heights[0][1] - 2.0f * heights[2][1] + heights[0][2] - heights[2][2];
|
||||
// Gy sobel filter
|
||||
normal.z = heights[0][0] + 2.0f * heights[1][0] + heights[2][0] - heights[0][2] - 2.0f * heights[1][2] - heights[2][2];
|
||||
// Calculate missing up component of the normal using the filtered x and y axis
|
||||
// The first value controls the bump strength
|
||||
normal.y = 0.25f * sqrt(1.0f - normal.x * normal.x - normal.z * normal.z);
|
||||
|
||||
vertices[x + y * patch_size].normal = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
// Indices
|
||||
const uint32_t w = (patch_size - 1);
|
||||
const uint32_t index_count = w * w * 4;
|
||||
std::vector<uint32_t> indices;
|
||||
indices.resize(index_count);
|
||||
for (auto x = 0; x < w; x++)
|
||||
{
|
||||
for (auto y = 0; y < w; y++)
|
||||
{
|
||||
uint32_t index = (x + y * w) * 4;
|
||||
indices[index] = (x + y * patch_size);
|
||||
indices[index + 1] = indices[index] + patch_size;
|
||||
indices[index + 2] = indices[index + 1] + 1;
|
||||
indices[index + 3] = indices[index] + 1;
|
||||
}
|
||||
}
|
||||
terrain.index_count = index_count;
|
||||
|
||||
uint32_t vertex_buffer_size = vertex_count * sizeof(Vertex);
|
||||
uint32_t index_buffer_size = index_count * sizeof(uint32_t);
|
||||
|
||||
// Create staging buffers
|
||||
|
||||
vkb::core::BufferCpp vertex_staging(get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
vertex_staging.update(vertices);
|
||||
|
||||
vkb::core::BufferCpp index_staging(get_device(), index_buffer_size, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
index_staging.update(indices);
|
||||
|
||||
terrain.vertices = std::make_unique<vkb::core::BufferCpp>(
|
||||
get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
terrain.indices = std::make_unique<vkb::core::BufferCpp>(
|
||||
get_device(), index_buffer_size, vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
// Copy from staging buffers
|
||||
vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(get_device().get_handle(), get_device().get_command_pool().get_handle());
|
||||
copy_command.begin(vk::CommandBufferBeginInfo());
|
||||
copy_command.copyBuffer(vertex_staging.get_handle(), terrain.vertices->get_handle(), {{0, 0, vertex_buffer_size}});
|
||||
copy_command.copyBuffer(index_staging.get_handle(), terrain.indices->get_handle(), {{0, 0, index_buffer_size}});
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::load_assets()
|
||||
{
|
||||
sky_sphere.geometry = load_model("scenes/geosphere.gltf");
|
||||
sky_sphere.texture = load_texture("textures/skysphere_rgba.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
|
||||
// Terrain textures are stored in a texture array with layers corresponding to terrain height; create a repeating sampler
|
||||
terrain.terrain_array = load_texture_array("textures/terrain_texturearray_rgba.ktx", vkb::scene_graph::components::HPPImage::Color, vk::SamplerAddressMode::eRepeat);
|
||||
|
||||
// Height data is stored in a one-channel texture; create a mirroring sampler
|
||||
terrain.height_map = load_texture("textures/terrain_heightmap_r16.ktx", vkb::scene_graph::components::HPPImage::Other, vk::SamplerAddressMode::eMirroredRepeat);
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::prepare_camera()
|
||||
{
|
||||
// Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped
|
||||
camera.type = vkb::CameraType::FirstPerson;
|
||||
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 512.0f, 0.1f);
|
||||
camera.set_rotation(glm::vec3(-12.0f, 159.0f, 0.0f));
|
||||
camera.set_translation(glm::vec3(18.0f, 22.5f, 57.5f));
|
||||
camera.translation_speed = 7.5f;
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::prepare_sky_sphere()
|
||||
{
|
||||
sky_sphere.descriptor_set_layout = create_sky_sphere_descriptor_set_layout();
|
||||
sky_sphere.pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &sky_sphere.descriptor_set_layout});
|
||||
sky_sphere.pipeline = create_sky_sphere_pipeline();
|
||||
sky_sphere.descriptor_set = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, {sky_sphere.descriptor_set_layout});
|
||||
update_sky_sphere_descriptor_set();
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::prepare_statistics()
|
||||
{
|
||||
if (statistics.query_supported)
|
||||
{
|
||||
// Create query pool
|
||||
statistics.query_pool = vkb::common::create_query_pool(get_device().get_handle(),
|
||||
vk::QueryType::ePipelineStatistics,
|
||||
2,
|
||||
vk::QueryPipelineStatisticFlagBits::eVertexShaderInvocations |
|
||||
vk::QueryPipelineStatisticFlagBits::eTessellationEvaluationShaderInvocations);
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::prepare_terrain()
|
||||
{
|
||||
terrain.shader_stages = {load_shader("terrain_tessellation", "terrain.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("terrain_tessellation", "terrain.frag.spv", vk::ShaderStageFlagBits::eFragment),
|
||||
load_shader("terrain_tessellation", "terrain.tesc.spv", vk::ShaderStageFlagBits::eTessellationControl),
|
||||
load_shader("terrain_tessellation", "terrain.tese.spv", vk::ShaderStageFlagBits::eTessellationEvaluation)};
|
||||
|
||||
terrain.descriptor_set_layout = create_terrain_descriptor_set_layout();
|
||||
terrain.pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &terrain.descriptor_set_layout});
|
||||
terrain.pipeline = create_terrain_pipeline(vk::PolygonMode::eFill);
|
||||
terrain.descriptor_set = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, {terrain.descriptor_set_layout});
|
||||
update_terrain_descriptor_set();
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void HPPTerrainTessellation::prepare_uniform_buffers()
|
||||
{
|
||||
// Shared tessellation shader stages uniform buffer
|
||||
terrain.tessellation_buffer =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(terrain.tessellation), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Skysphere vertex shader uniform buffer
|
||||
sky_sphere.transform_buffer =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(sky_sphere.transform), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::prepare_wireframe()
|
||||
{
|
||||
if (wireframe.supported)
|
||||
{
|
||||
// wireframe mode uses nearly the same settings as the terrain mode... just vk::PolygonMode::eLine, instead of vk::PolygonMode::eFill
|
||||
wireframe.pipeline = create_terrain_pipeline(vk::PolygonMode::eLine);
|
||||
};
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::update_uniform_buffers()
|
||||
{
|
||||
// Tessellation
|
||||
terrain.tessellation.projection = camera.matrices.perspective;
|
||||
terrain.tessellation.modelview = camera.matrices.view * glm::mat4(1.0f);
|
||||
terrain.tessellation.light_pos.y = -0.5f - terrain.tessellation.displacement_factor; // todo: Not used yet
|
||||
terrain.tessellation.viewport_dim = glm::vec2(static_cast<float>(extent.width), static_cast<float>(extent.height));
|
||||
|
||||
frustum.update(terrain.tessellation.projection * terrain.tessellation.modelview);
|
||||
memcpy(terrain.tessellation.frustum_planes, frustum.get_planes().data(), sizeof(glm::vec4) * 6);
|
||||
|
||||
float saved_factor = terrain.tessellation.tessellation_factor;
|
||||
if (!terrain.tessellation_enabled)
|
||||
{
|
||||
// Setting this to zero sets all tessellation factors to 1.0 in the shader
|
||||
terrain.tessellation.tessellation_factor = 0.0f;
|
||||
}
|
||||
|
||||
terrain.tessellation_buffer->convert_and_update(terrain.tessellation);
|
||||
|
||||
if (!terrain.tessellation_enabled)
|
||||
{
|
||||
terrain.tessellation.tessellation_factor = saved_factor;
|
||||
}
|
||||
|
||||
// Skysphere vertex shader
|
||||
sky_sphere.transform = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view));
|
||||
sky_sphere.transform_buffer->convert_and_update(sky_sphere.transform);
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::update_sky_sphere_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo skysphere_buffer_descriptor{sky_sphere.transform_buffer->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
vk::DescriptorImageInfo skysphere_image_descriptor{sky_sphere.texture.sampler,
|
||||
sky_sphere.texture.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
sky_sphere.texture.image->get_vk_image_view().get_format())};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 2> skysphere_write_descriptor_sets = {{{.dstSet = sky_sphere.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &skysphere_buffer_descriptor},
|
||||
{.dstSet = sky_sphere.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &skysphere_image_descriptor}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(skysphere_write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPTerrainTessellation::update_terrain_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo terrain_buffer_descriptor{terrain.tessellation_buffer->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
vk::DescriptorImageInfo heightmap_image_descriptor{terrain.height_map.sampler,
|
||||
terrain.height_map.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
terrain.height_map.image->get_vk_image_view().get_format())};
|
||||
|
||||
vk::DescriptorImageInfo terrainmap_image_descriptor{terrain.terrain_array.sampler,
|
||||
terrain.terrain_array.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
terrain.terrain_array.image->get_vk_image_view().get_format())};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> terrain_write_descriptor_sets = {{{.dstSet = terrain.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &terrain_buffer_descriptor},
|
||||
{.dstSet = terrain.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &heightmap_image_descriptor},
|
||||
{.dstSet = terrain.descriptor_set,
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &terrainmap_image_descriptor}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(terrain_write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_terrain_tessellation()
|
||||
{
|
||||
return std::make_unique<HPPTerrainTessellation>();
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/* Copyright (c) 2022-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dynamic terrain tessellation, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
#include <geometry/frustum.h>
|
||||
|
||||
class HPPTerrainTessellation : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPTerrainTessellation();
|
||||
~HPPTerrainTessellation();
|
||||
|
||||
private:
|
||||
struct SkySphere
|
||||
{
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipeline;
|
||||
|
||||
std::unique_ptr<vkb::scene_graph::components::HPPSubMesh> geometry;
|
||||
|
||||
HPPTexture texture;
|
||||
|
||||
glm::mat4 transform;
|
||||
std::unique_ptr<vkb::core::BufferCpp> transform_buffer;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// the descriptor_set is implicitly freed by destroying its DescriptorPool
|
||||
device.destroySampler(texture.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
struct Statistics
|
||||
{
|
||||
bool query_supported = false;
|
||||
|
||||
vk::QueryPool query_pool;
|
||||
|
||||
std::array<uint64_t, 2> results = {{0}};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
if (query_supported)
|
||||
{
|
||||
device.destroyQueryPool(query_pool);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Shared values for tessellation control and evaluation stages
|
||||
struct Tessellation
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::vec4 light_pos = glm::vec4(-48.0f, -40.0f, 46.0f, 0.0f);
|
||||
glm::vec4 frustum_planes[6];
|
||||
float displacement_factor = 32.0f;
|
||||
float tessellation_factor = 0.75f;
|
||||
glm::vec2 viewport_dim;
|
||||
// Desired size of tessellated quad patch edge
|
||||
float tessellated_edge_size = 20.0f;
|
||||
};
|
||||
|
||||
struct Terrain
|
||||
{
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipeline;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferCpp> vertices;
|
||||
std::unique_ptr<vkb::core::BufferCpp> indices;
|
||||
uint32_t index_count;
|
||||
|
||||
HPPTexture height_map;
|
||||
HPPTexture terrain_array;
|
||||
|
||||
bool sampler_anisotropy_supported = false;
|
||||
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages;
|
||||
|
||||
Tessellation tessellation;
|
||||
std::unique_ptr<vkb::core::BufferCpp> tessellation_buffer;
|
||||
bool tessellation_enabled = true;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// the descriptor_set is implicitly freed by destroying its DescriptorPool
|
||||
device.destroySampler(height_map.sampler);
|
||||
device.destroySampler(terrain_array.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
glm::vec3 pos;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 uv;
|
||||
};
|
||||
|
||||
struct Wireframe
|
||||
{
|
||||
bool supported = false;
|
||||
bool enabled = false;
|
||||
|
||||
vk::Pipeline pipeline;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void render(float delta_time) override;
|
||||
void view_changed() override;
|
||||
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::DescriptorSetLayout create_sky_sphere_descriptor_set_layout();
|
||||
vk::Pipeline create_sky_sphere_pipeline();
|
||||
vk::DescriptorSetLayout create_terrain_descriptor_set_layout();
|
||||
vk::Pipeline create_terrain_pipeline(vk::PolygonMode polygon_mode);
|
||||
void draw();
|
||||
void generate_terrain();
|
||||
void load_assets();
|
||||
void prepare_camera();
|
||||
void prepare_sky_sphere();
|
||||
void prepare_statistics();
|
||||
void prepare_terrain();
|
||||
void prepare_uniform_buffers();
|
||||
void prepare_wireframe();
|
||||
void update_uniform_buffers();
|
||||
void update_sky_sphere_descriptor_set();
|
||||
void update_terrain_descriptor_set();
|
||||
|
||||
private:
|
||||
vkb::Frustum frustum; // View frustum passed to tessellation control shader for culling
|
||||
SkySphere sky_sphere;
|
||||
Statistics statistics;
|
||||
Terrain terrain;
|
||||
Wireframe wireframe;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_terrain_tessellation();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2021-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Texture loading and mapping"
|
||||
DESCRIPTION "Loading a texture and displaying it on a simple object, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"texture_loading/glsl/texture.vert"
|
||||
"texture_loading/glsl/texture.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"texture_loading/hlsl/texture.vert.hlsl"
|
||||
"texture_loading/hlsl/texture.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2020-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= Texture Loading with Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_texture_loading[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/texture_loading[Texture loading] that illustrates the usage of the C{pp} bindings of Vulkan provided by vulkan.hpp.
|
||||
@@ -1,591 +0,0 @@
|
||||
/* Copyright (c) 2021-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Texture loading (and display) example (including mip maps), using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_texture_loading.h"
|
||||
#include "common/ktx_common.h"
|
||||
#include "core/command_pool.h"
|
||||
|
||||
HPPTextureLoading::HPPTextureLoading()
|
||||
{
|
||||
title = "HPP Texture loading";
|
||||
|
||||
zoom = -2.5f;
|
||||
rotation = {0.0f, 15.0f, 0.0f};
|
||||
}
|
||||
|
||||
HPPTextureLoading::~HPPTextureLoading()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
texture.destroy(device);
|
||||
}
|
||||
|
||||
vertex_buffer.reset();
|
||||
index_buffer.reset();
|
||||
vertex_shader_data_buffer.reset();
|
||||
}
|
||||
|
||||
bool HPPTextureLoading::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
load_texture();
|
||||
generate_quad();
|
||||
prepare_uniform_buffers();
|
||||
descriptor_set_layout = create_descriptor_set_layout();
|
||||
pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &descriptor_set_layout});
|
||||
pipeline = create_pipeline();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
descriptor_set = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, {descriptor_set_layout});
|
||||
update_descriptor_set();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
// Enable physical device features required for this example
|
||||
void HPPTextureLoading::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 HPPTextureLoading::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
|
||||
vk::ClearValue clear_values[2];
|
||||
clear_values[0].color = default_clear_color;
|
||||
clear_values[1].depthStencil = vk::ClearDepthStencilValue{0.0f, 0};
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info;
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent = extent;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
auto command_buffer = draw_cmd_buffers[i];
|
||||
|
||||
// Set target frame buffer
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
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, pipeline_layout, 0, descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
|
||||
vk::DeviceSize offset = 0;
|
||||
command_buffer.bindVertexBuffers(0, vertex_buffer->get_handle(), offset);
|
||||
command_buffer.bindIndexBuffer(index_buffer->get_handle(), 0, vk::IndexType::eUint32);
|
||||
|
||||
command_buffer.drawIndexed(index_count, 1, 0, 0, 0);
|
||||
|
||||
draw_ui(command_buffer);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTextureLoading::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.slider_float("LOD bias", &vertex_shader_data.lod_bias, 0.0f, static_cast<float>(texture.mip_levels)))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTextureLoading::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTextureLoading::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPTextureLoading::create_descriptor_pool()
|
||||
{
|
||||
// Example uses one ubo and one image sampler
|
||||
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 1}, {vk::DescriptorType::eCombinedImageSampler, 1}}};
|
||||
|
||||
return get_device().get_handle().createDescriptorPool(
|
||||
{.maxSets = 2, .poolSizeCount = static_cast<uint32_t>(pool_sizes.size()), .pPoolSizes = pool_sizes.data()});
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPTextureLoading::create_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> set_layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout(
|
||||
{.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()), .pBindings = set_layout_bindings.data()});
|
||||
}
|
||||
|
||||
vk::Pipeline HPPTextureLoading::create_pipeline()
|
||||
{
|
||||
// Load shaders
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {{load_shader("texture_loading", "texture.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("texture_loading", "texture.frag.spv", vk::ShaderStageFlagBits::eFragment)}};
|
||||
|
||||
// Vertex bindings and attributes
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(Vertex), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 3> vertex_input_attributes = {
|
||||
{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)}, // Location 0 : Position
|
||||
{1, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, uv)}, // Location 1: Texture Coordinates
|
||||
{2, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal)}}}; // Location 2 : Normal
|
||||
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()};
|
||||
|
||||
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.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = true;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eGreater;
|
||||
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPTextureLoading::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
|
||||
// Submit to queue
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void HPPTextureLoading::generate_quad()
|
||||
{
|
||||
// Setup vertices for a single uv-mapped quad made from two triangles
|
||||
std::vector<Vertex> vertices = {{{1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}};
|
||||
|
||||
// Setup indices
|
||||
std::vector<uint32_t> indices = {0, 1, 2, 2, 3, 0};
|
||||
index_count = static_cast<uint32_t>(indices.size());
|
||||
|
||||
auto vertex_buffer_size = vkb::to_u32(vertices.size() * sizeof(Vertex));
|
||||
auto index_buffer_size = vkb::to_u32(indices.size() * sizeof(uint32_t));
|
||||
|
||||
// Create buffers
|
||||
// For the sake of simplicity we won't stage the vertex data to the gpu memory
|
||||
// Vertex buffer
|
||||
vertex_buffer = std::make_unique<vkb::core::BufferCpp>(
|
||||
get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
vertex_buffer->update(vertices.data(), vertex_buffer_size);
|
||||
|
||||
// Index buffer
|
||||
index_buffer = std::make_unique<vkb::core::BufferCpp>(
|
||||
get_device(), index_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
index_buffer->update(indices.data(), index_buffer_size);
|
||||
}
|
||||
|
||||
/*
|
||||
Upload texture image data to the GPU
|
||||
|
||||
Vulkan offers two types of image tiling (memory layout):
|
||||
|
||||
Linear tiled images:
|
||||
These are stored as is and can be copied directly to. But due to the linear nature they're not a good match for GPUs and format and feature support is very limited.
|
||||
It's not advised to use linear tiled images for anything else than copying from host to GPU if buffer copies are not an option.
|
||||
Linear tiling is thus only implemented for learning purposes, one should always prefer optimal tiled image.
|
||||
|
||||
Optimal tiled images:
|
||||
These are stored in an implementation specific layout matching the capability of the hardware. They usually support more formats and features and are much faster.
|
||||
Optimal tiled images are stored on the device and not accessible by the host. So they can't be written directly to (like liner tiled images) and always require
|
||||
some sort of data copy, either from a buffer or a linear tiled image.
|
||||
|
||||
In Short: Always use optimal tiled images for rendering.
|
||||
*/
|
||||
void HPPTextureLoading::load_texture()
|
||||
{
|
||||
// We use the Khronos texture format (https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/)
|
||||
const std::string filename = vkb::fs::path::get(vkb::fs::path::Assets, "textures/metalplate01_rgba.ktx");
|
||||
// ktx1 doesn't know whether the content is sRGB or linear, but most tools save in sRGB, so assume that.
|
||||
constexpr vk::Format format = vk::Format::eR8G8B8A8Srgb;
|
||||
|
||||
ktxTexture *ktx_texture = vkb::ktx::load_texture(filename);
|
||||
|
||||
texture.extent = vk::Extent2D{ktx_texture->baseWidth, ktx_texture->baseHeight};
|
||||
texture.mip_levels = ktx_texture->numLevels;
|
||||
|
||||
// We prefer using staging to copy the texture data to a device local optimal image
|
||||
vk::Bool32 use_staging = true;
|
||||
|
||||
// Only use linear tiling if forced
|
||||
bool force_linear_tiling = false;
|
||||
if (force_linear_tiling)
|
||||
{
|
||||
// Don't use linear if format is not supported for (linear) shader sampling
|
||||
// Get device properties for the requested texture format
|
||||
vk::FormatProperties format_properties = get_device().get_gpu().get_handle().getFormatProperties(format);
|
||||
use_staging = !(format_properties.linearTilingFeatures & vk::FormatFeatureFlagBits::eSampledImage);
|
||||
}
|
||||
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
if (use_staging)
|
||||
{
|
||||
// Copy data to an optimal tiled image
|
||||
// This loads the texture data into a host local buffer that is copied to the optimal tiled image on the device
|
||||
|
||||
// Create a host-visible staging buffer that contains the raw image data
|
||||
// This buffer will be the data source for copying texture data to the optimal tiled image on the device
|
||||
// This buffer is used as a transfer source for the buffer copy
|
||||
vk::BufferCreateInfo buffer_create_info{.size = ktx_texture->dataSize,
|
||||
.usage = vk::BufferUsageFlagBits::eTransferSrc,
|
||||
.sharingMode = vk::SharingMode::eExclusive};
|
||||
vk::Buffer staging_buffer = device.createBuffer(buffer_create_info);
|
||||
|
||||
// Get memory requirements for the staging buffer (alignment, memory type bits)
|
||||
vk::MemoryRequirements memory_requirements = device.getBufferMemoryRequirements(staging_buffer);
|
||||
|
||||
// Get memory type index for a host visible buffer
|
||||
uint32_t memory_type = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||
|
||||
vk::MemoryAllocateInfo memory_allocate_info{.allocationSize = memory_requirements.size, .memoryTypeIndex = memory_type};
|
||||
vk::DeviceMemory staging_memory = device.allocateMemory(memory_allocate_info);
|
||||
device.bindBufferMemory(staging_buffer, staging_memory, 0);
|
||||
|
||||
// Copy texture data into host local staging buffer
|
||||
uint8_t *data = reinterpret_cast<uint8_t *>(device.mapMemory(staging_memory, 0, memory_requirements.size));
|
||||
memcpy(data, ktx_texture->pData, ktx_texture->dataSize);
|
||||
device.unmapMemory(staging_memory);
|
||||
|
||||
// Setup buffer copy regions for each mip level
|
||||
std::vector<vk::BufferImageCopy> buffer_copy_regions(texture.mip_levels);
|
||||
for (uint32_t i = 0; i < texture.mip_levels; i++)
|
||||
{
|
||||
ktx_size_t offset;
|
||||
KTX_error_code result = ktxTexture_GetImageOffset(ktx_texture, i, 0, 0, &offset);
|
||||
|
||||
buffer_copy_regions[i].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
buffer_copy_regions[i].imageSubresource.mipLevel = i;
|
||||
buffer_copy_regions[i].imageSubresource.baseArrayLayer = 0;
|
||||
buffer_copy_regions[i].imageSubresource.layerCount = 1;
|
||||
buffer_copy_regions[i].imageExtent.width = ktx_texture->baseWidth >> i;
|
||||
buffer_copy_regions[i].imageExtent.height = ktx_texture->baseHeight >> i;
|
||||
buffer_copy_regions[i].imageExtent.depth = 1;
|
||||
buffer_copy_regions[i].bufferOffset = offset;
|
||||
}
|
||||
|
||||
// Create optimal tiled target image on the device
|
||||
vk::ImageCreateInfo image_create_info{
|
||||
.imageType = vk::ImageType::e2D,
|
||||
.format = format,
|
||||
.extent = {texture.extent.width, texture.extent.height, 1},
|
||||
.mipLevels = texture.mip_levels,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = vk::ImageTiling::eOptimal,
|
||||
.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
.initialLayout = vk::ImageLayout::eUndefined // Set initial layout of the image to undefined
|
||||
};
|
||||
texture.image = device.createImage(image_create_info);
|
||||
|
||||
memory_requirements = device.getImageMemoryRequirements(texture.image);
|
||||
memory_type = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||
memory_allocate_info = vk::MemoryAllocateInfo{.allocationSize = memory_requirements.size, .memoryTypeIndex = memory_type};
|
||||
texture.device_memory = device.allocateMemory(memory_allocate_info);
|
||||
device.bindImageMemory(texture.image, texture.device_memory, 0);
|
||||
|
||||
vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(device, get_device().get_command_pool().get_handle());
|
||||
copy_command.begin(vk::CommandBufferBeginInfo());
|
||||
|
||||
// Image memory barriers for the texture image
|
||||
|
||||
// The sub resource range describes the regions of the image that will be transitioned using the memory barriers below
|
||||
vk::ImageSubresourceRange subresource_range{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor, // Image contains only color data
|
||||
.baseMipLevel = 0, // Start at first mip level
|
||||
.levelCount = texture.mip_levels, // We will transition on all mip levels
|
||||
.layerCount = 1 // The 2D texture only has one layer
|
||||
};
|
||||
|
||||
// Transition the texture image layout to transfer target, so we can safely copy our buffer data to it.
|
||||
vk::ImageMemoryBarrier image_memory_barrier;
|
||||
image_memory_barrier.image = texture.image;
|
||||
image_memory_barrier.subresourceRange = subresource_range;
|
||||
image_memory_barrier.srcAccessMask = {};
|
||||
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
image_memory_barrier.oldLayout = vk::ImageLayout::eUndefined;
|
||||
image_memory_barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
// Insert a memory dependency at the proper pipeline stages that will execute the image layout transition
|
||||
// Source pipeline stage is host write/read execution (VK_PIPELINE_STAGE_HOST_BIT)
|
||||
// Destination pipeline stage is copy command execution (VK_PIPELINE_STAGE_TRANSFER_BIT)
|
||||
copy_command.pipelineBarrier(vk::PipelineStageFlagBits::eHost, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, nullptr, image_memory_barrier);
|
||||
|
||||
// Copy mip levels from staging buffer
|
||||
copy_command.copyBufferToImage(staging_buffer, texture.image, vk::ImageLayout::eTransferDstOptimal, buffer_copy_regions);
|
||||
|
||||
// Once the data has been uploaded we transfer the texture image to the shader read layout, so it can be sampled from
|
||||
image_memory_barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
image_memory_barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
image_memory_barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
|
||||
// Insert a memory dependency at the proper pipeline stages that will execute the image layout transition
|
||||
// Source pipeline stage stage is copy command execution (VK_PIPELINE_STAGE_TRANSFER_BIT)
|
||||
// Destination pipeline stage fragment shader access (VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT)
|
||||
copy_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, nullptr, image_memory_barrier);
|
||||
|
||||
// Store current layout for later reuse
|
||||
texture.image_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
|
||||
// Clean up staging resources
|
||||
device.destroyBuffer(staging_buffer);
|
||||
device.freeMemory(staging_memory);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy data to a linear tiled image
|
||||
|
||||
// Load mip map level 0 to linear tiling image
|
||||
vk::ImageCreateInfo image_create_info{.imageType = vk::ImageType::e2D,
|
||||
.format = format,
|
||||
.extent = {texture.extent.width, texture.extent.height, 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = vk::ImageTiling::eLinear,
|
||||
.usage = vk::ImageUsageFlagBits::eSampled,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
.initialLayout = vk::ImageLayout::ePreinitialized};
|
||||
vk::Image mappable_image = device.createImage(image_create_info);
|
||||
|
||||
// Get memory requirements for this image like size and alignment
|
||||
vk::MemoryRequirements memory_requirements = device.getImageMemoryRequirements(mappable_image);
|
||||
|
||||
// Get memory type that can be mapped to host memory
|
||||
uint32_t memory_type = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||
|
||||
// Set memory allocation size to required memory size
|
||||
vk::MemoryAllocateInfo memory_allocate_info{.allocationSize = memory_requirements.size, .memoryTypeIndex = memory_type};
|
||||
vk::DeviceMemory mappable_memory = device.allocateMemory(memory_allocate_info);
|
||||
device.bindImageMemory(mappable_image, mappable_memory, 0);
|
||||
|
||||
// Map image memory
|
||||
void *data = device.mapMemory(mappable_memory, 0, memory_requirements.size);
|
||||
// Copy image data of the first mip level into memory
|
||||
memcpy(data, ktx_texture->pData, ktxTexture_GetImageSize(ktx_texture, 0));
|
||||
device.unmapMemory(mappable_memory);
|
||||
|
||||
// Linear tiled images don't need to be staged and can be directly used as textures
|
||||
texture.image = mappable_image;
|
||||
texture.device_memory = mappable_memory;
|
||||
texture.image_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
|
||||
// Setup image memory barrier transfer image to shader read layout
|
||||
vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(device, get_device().get_command_pool().get_handle());
|
||||
copy_command.begin(vk::CommandBufferBeginInfo());
|
||||
|
||||
// The sub resource range describes the regions of the image we will be transition
|
||||
vk::ImageSubresourceRange subresource_range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
|
||||
// Transition the texture image layout to shader read, so it can be sampled from
|
||||
vk::ImageMemoryBarrier image_memory_barrier;
|
||||
image_memory_barrier.image = texture.image;
|
||||
image_memory_barrier.subresourceRange = subresource_range;
|
||||
image_memory_barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite;
|
||||
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
image_memory_barrier.oldLayout = vk::ImageLayout::ePreinitialized;
|
||||
image_memory_barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
// Insert a memory dependency at the proper pipeline stages that will execute the image layout transition
|
||||
// Source pipeline stage is host write/read execution (VK_PIPELINE_STAGE_HOST_BIT)
|
||||
// Destination pipeline stage fragment shader access (VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT)
|
||||
copy_command.pipelineBarrier(vk::PipelineStageFlagBits::eHost, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, nullptr, image_memory_barrier);
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
}
|
||||
|
||||
// now, the ktx_texture can be destroyed
|
||||
ktxTexture_Destroy(ktx_texture);
|
||||
|
||||
// Create a texture sampler
|
||||
// In Vulkan textures are accessed by samplers
|
||||
// This separates all the sampling information from the texture data. This means you could have multiple sampler objects for the same texture with different settings
|
||||
// Note: Similar to the samplers available with OpenGL 3.3
|
||||
|
||||
// Enable anisotropic filtering
|
||||
// This feature is optional, so we must check if it's supported on the device
|
||||
float maxAnisotropy = 1.0f;
|
||||
if (get_device().get_gpu().get_features().samplerAnisotropy)
|
||||
{
|
||||
// Use max. level of anisotropy for this example
|
||||
maxAnisotropy = get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy;
|
||||
}
|
||||
|
||||
texture.sampler = vkb::common::create_sampler(get_device().get_gpu().get_handle(), get_device().get_handle(),
|
||||
format, vk::Filter::eLinear, vk::SamplerAddressMode::eClampToEdge,
|
||||
maxAnisotropy, (use_staging) ? static_cast<float>(texture.mip_levels) : 0.0f);
|
||||
|
||||
// Create image view
|
||||
// Textures are not directly accessed by the shaders and
|
||||
// are abstracted by image views containing additional
|
||||
// information and sub resource ranges
|
||||
vk::ImageViewCreateInfo image_view_create_info;
|
||||
image_view_create_info.viewType = vk::ImageViewType::e2D;
|
||||
image_view_create_info.format = format;
|
||||
image_view_create_info.components = {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA};
|
||||
// The subresource range describes the set of mip levels (and array layers) that can be accessed through this image image_view_create_info
|
||||
// It's possible to create multiple image views for a single image referring to different (and/or overlapping) ranges of the image
|
||||
image_view_create_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
image_view_create_info.subresourceRange.baseMipLevel = 0;
|
||||
image_view_create_info.subresourceRange.baseArrayLayer = 0;
|
||||
image_view_create_info.subresourceRange.layerCount = 1;
|
||||
// Linear tiling usually won't support mip maps
|
||||
// Only set mip map count if optimal tiling is used
|
||||
image_view_create_info.subresourceRange.levelCount = (use_staging) ? texture.mip_levels : 1;
|
||||
// The image_view_create_info will be based on the texture's image
|
||||
image_view_create_info.image = texture.image;
|
||||
texture.image_view = device.createImageView(image_view_create_info);
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void HPPTextureLoading::prepare_uniform_buffers()
|
||||
{
|
||||
// Vertex shader uniform buffer block
|
||||
vertex_shader_data_buffer =
|
||||
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(vertex_shader_data), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void HPPTextureLoading::update_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo buffer_descriptor{vertex_shader_data_buffer->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
// Setup a descriptor image info for the current texture to be used as a combined image sampler
|
||||
vk::DescriptorImageInfo image_descriptor{
|
||||
texture.sampler, // The sampler (the sampler describes how to sample the image, including repeat, border, etc.)
|
||||
texture.image_view, // The image view (the image view describes the image and the subresources that can be accessed)
|
||||
texture.image_layout // The current layout of the image (Note: Should always fit the actual use, e.g. shader read)
|
||||
};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {{// Binding 0 : Vertex shader uniform buffer
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &buffer_descriptor},
|
||||
// Binding 1 : Fragment shader texture sampler
|
||||
// Fragment shader: layout (binding = 1) uniform sampler2D samplerColor;
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &image_descriptor}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPTextureLoading::update_uniform_buffers()
|
||||
{
|
||||
// Vertex shader
|
||||
vertex_shader_data.projection = glm::perspective(glm::radians(60.0f), static_cast<float>(extent.width) / static_cast<float>(extent.height), 0.001f, 256.0f);
|
||||
glm::mat4 view_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom));
|
||||
|
||||
vertex_shader_data.model = view_matrix * glm::translate(glm::mat4(1.0f), camera_pos);
|
||||
vertex_shader_data.model = glm::rotate(vertex_shader_data.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
vertex_shader_data.model = glm::rotate(vertex_shader_data.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
vertex_shader_data.model = glm::rotate(vertex_shader_data.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
vertex_shader_data.view_pos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f);
|
||||
|
||||
vertex_shader_data_buffer->convert_and_update(vertex_shader_data);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_texture_loading()
|
||||
{
|
||||
return std::make_unique<HPPTextureLoading>();
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/* Copyright (c) 2021-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Texture loading (and display) example (including mip maps), using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
class HPPTextureLoading : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPTextureLoading();
|
||||
~HPPTextureLoading();
|
||||
|
||||
private:
|
||||
// Contains all Vulkan objects that are required to store and use a texture
|
||||
// Note that this repository contains a texture class (vulkan_texture.h) that encapsulates texture loading functionality in a class that is used in subsequent demos
|
||||
struct Texture
|
||||
{
|
||||
vk::DeviceMemory device_memory;
|
||||
vk::Image image;
|
||||
vk::ImageLayout image_layout;
|
||||
vk::ImageView image_view;
|
||||
vk::Sampler sampler;
|
||||
vk::Extent2D extent;
|
||||
uint32_t mip_levels;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyImageView(image_view);
|
||||
device.destroyImage(image);
|
||||
device.destroySampler(sampler);
|
||||
device.freeMemory(device_memory);
|
||||
}
|
||||
};
|
||||
|
||||
// Vertex layout for this example
|
||||
struct Vertex
|
||||
{
|
||||
float pos[3];
|
||||
float uv[2];
|
||||
float normal[3];
|
||||
};
|
||||
|
||||
struct VertexShaderData
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
glm::vec4 view_pos;
|
||||
float lod_bias = 0.0f;
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void render(float delta_time) override;
|
||||
void view_changed() override;
|
||||
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::DescriptorSetLayout create_descriptor_set_layout();
|
||||
vk::Pipeline create_pipeline();
|
||||
void draw();
|
||||
void generate_quad();
|
||||
void load_texture();
|
||||
void prepare_uniform_buffers();
|
||||
void update_descriptor_set();
|
||||
void update_uniform_buffers();
|
||||
|
||||
private:
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
std::unique_ptr<vkb::core::BufferCpp> index_buffer;
|
||||
uint32_t index_count;
|
||||
vk::Pipeline pipeline;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
Texture texture;
|
||||
std::unique_ptr<vkb::core::BufferCpp> vertex_buffer;
|
||||
VertexShaderData vertex_shader_data;
|
||||
std::unique_ptr<vkb::core::BufferCpp> vertex_shader_data_buffer;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_texture_loading();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2022-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Texture mipmap generation"
|
||||
DESCRIPTION "Generate mipmaps for a texture"
|
||||
SHADER_FILES_GLSL
|
||||
"texture_mipmap_generation/glsl/texture.vert"
|
||||
"texture_mipmap_generation/glsl/texture.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"texture_mipmap_generation/hlsl/texture.vert.hlsl"
|
||||
"texture_mipmap_generation/hlsl/texture.frag.hlsl")
|
||||
@@ -1,233 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2022-2024, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:doctype: book
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= Run-time mip-map generation with Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_texture_mipmap_generation[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/texture_mipmap_generation[Texture mipmap generation] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp.
|
||||
|
||||
== Overview
|
||||
|
||||
Generates a complete texture mip-chain at runtime from a base image using image blits and proper image barriers.
|
||||
|
||||
This examples demonstrates how to generate a complete texture mip-chain at runtime instead of loading offline generated mip-maps from a texture file.
|
||||
|
||||
While usually not applied for textures stored on the disk (that usually have the mips generated offline and stored in the file) this technique is often used for dynamic textures like cubemaps for reflections or other render-to-texture effects.
|
||||
|
||||
Having mip-maps for runtime generated textures offers lots of benefits, both in terms of image stability and performance.
|
||||
Without mip mapping the image will become noisy, especially with high frequency textures (and texture components like specular) and using mip mapping will result in higher performance due to caching.
|
||||
|
||||
Though this example only generates one mip-chain for a single texture at the beginning this technique can also be used during normal frame rendering to generate mip-chains for dynamic textures.
|
||||
|
||||
Some GPUs also offer `asynchronous transfer queues` that may be used for doing such operations in the background.
|
||||
To detect this, check for queue families with only the `vk::QueueFlagBits::eTransfer` set.
|
||||
|
||||
== Comparison
|
||||
|
||||
Without mip mapping:
|
||||
|
||||
image::samples/api/texture_mipmap_generation/images/mip_mapping_off.jpg[Off,512px]
|
||||
|
||||
Using mip mapping with a bilinear filter:
|
||||
|
||||
image::samples/api/texture_mipmap_generation/images/mip_mapping_bilinear.jpg[Bilinear,512px]
|
||||
|
||||
Using mip mapping with an anisotropic filter:
|
||||
|
||||
image::samples/api/texture_mipmap_generation/images/mip_mapping_anisotropic.jpg[Anisotropic,512px]
|
||||
|
||||
== Requirements
|
||||
|
||||
To downsample from one mip level to the next, we will be using https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html[`vk::CommandBuffer::blitImage`].
|
||||
This requires the format used to support the `vk::FormatFeatureFlagBits::eBlitSrc` and the `vk::FormatFeatureFlagBits::eBlitDst` flags.
|
||||
If these are not supported, the image format can't be used to blit and you'd either have to choose a different format or use a custom shader to generate mip levels.
|
||||
The example uses the `vk::Format::eR8G8B8A8Srgb` that should support these flags on most implementations.
|
||||
|
||||
*_Note:_* Use https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkGetPhysicalDeviceFormatProperties.html[`vk::PhysicalDevice::getFormatProperties`] to check if the format supports the blit flags first.
|
||||
|
||||
== Points of interest
|
||||
|
||||
=== Image setup
|
||||
|
||||
Even though we'll only upload the first mip level initially, we create the image with number of desired mip levels.
|
||||
The following formula is used to calculate the number of mip levels based on the max.
|
||||
image extent:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
texture.mip_levels = static_cast<uint32_t>(floor(log2(std::max(texture.width, texture.height))) + 1);
|
||||
----
|
||||
|
||||
This is then passed to the image create info:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::ImageCreateInfo image_create_info({},
|
||||
vk::ImageType::e2D,
|
||||
format,
|
||||
vk::Extent3D(texture.extent, 1),
|
||||
texture.mip_levels,
|
||||
...
|
||||
----
|
||||
|
||||
Setting the number of desired mip levels is necessary as this is used for allocating the correct amount of memory required by the image (`vk::Device::allocateMemory`).
|
||||
|
||||
=== Upload base mip level
|
||||
|
||||
Before generating the mip-chain we need to copy the image data loaded from disk into the newly generated image.
|
||||
This image will be the base for our mip-chain:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::BufferImageCopy buffer_copy_region({}, {}, {}, {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, {}, vk::Extent3D(texture.extent, 1));
|
||||
copy_command.copyBufferToImage(staging_buffer, texture.image, vk::ImageLayout::eTransferDstOptimal, buffer_copy_region);
|
||||
----
|
||||
|
||||
=== Prepare base mip level
|
||||
|
||||
As we are going to blit *_from_* the base mip-level just uploaded we also need to insert an image memory barrier that transitions the image layout to `vk::ImageLayout::eTransferSrcOptimal` for the base mip level:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
image_memory_barrier = vk::ImageMemoryBarrier(vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eTransferRead,
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::ImageLayout::eTransferSrcOptimal,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
texture.image,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
|
||||
copy_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, image_memory_barrier);
|
||||
----
|
||||
|
||||
=== Generating the mip-chain
|
||||
|
||||
There are two different ways of generating the mip-chain.
|
||||
The first one is to blit down the whole mip-chain from level n-1 to n, the other way would be to always use the base image and blit down from that to all levels.
|
||||
This example uses the first one.
|
||||
|
||||
*_Note:_* Blitting (same for copying) images is done inside of a command buffer that has to be submitted and as such has to be synchronized before using the new image with e.g.
|
||||
a `vk::Fence`.
|
||||
|
||||
We simply loop over all remaining mip levels (level 0 was loaded from disk) and prepare a `vk::ImageBlit` structure for each blit from mip level i-1 to level i.
|
||||
|
||||
First the source for our blit.
|
||||
This is the previous mip level:
|
||||
// {% raw %}
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
for (int32_t i = 1; i < texture.mipLevels; i++)
|
||||
{
|
||||
vk::ImageBlit image_blit(// Source
|
||||
{vk::ImageAspectFlagBits::eColor, i - 1, 0, 1},
|
||||
{{{}, {int32_t(texture.extent.width >> (i - 1)), int32_t(texture.extent.height >> (i - 1)), int32_t(1)}}},
|
||||
// Destination
|
||||
{vk::ImageAspectFlagBits::eColor, i, 0, 1},
|
||||
{{{}, {int32_t(texture.extent.width >> i), int32_t(texture.extent.height >> i), int32_t(1)}}});
|
||||
}
|
||||
----
|
||||
|
||||
// {% endraw %}
|
||||
|
||||
Before we can blit to this mip level, we need to transition it's image layout to `vk::ImageLayout::eTransferDstOptimal`:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Prepare current mip level as image blit destination
|
||||
image_memory_barrier = vk::ImageMemoryBarrier({},
|
||||
vk::AccessFlagBits::eTransferWrite,
|
||||
vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
texture.image,
|
||||
{vk::ImageAspectFlagBits::eColor, i, 1, 0, 1});
|
||||
copy_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, image_memory_barrier);
|
||||
----
|
||||
|
||||
Note that we set the `baseMipLevel` of the subresource range to `i`, so the image memory barrier will only affect the one mip level we want to copy to.
|
||||
|
||||
Now that the mip level we want to copy from and the one we'll copy to are in the proper layout (transfer source and destination) we can issue the https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html[`vk::CommandBuffer::blitImage`] to copy from mip level (i-1) to mip level (i):
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
blit_command.blitImage(texture.image, vk::ImageLayout::eTransferSrcOptimal, texture.image, vk::ImageLayout::eTransferDstOptimal, image_blit, vk::Filter::eLinear);
|
||||
----
|
||||
|
||||
`vk::CommandBuffer::blitImage` does the down sampling from mip level (i-1) to mip level (i) using a linear filter, if you need better or more advanced filtering for this you need to resort to using custom shaders for generating the mip chain instead of blitting.
|
||||
|
||||
After the blit is done we can use this mip level as a base for the next level, so we transition the layout from `vk::ImageLayout::eTransferDstOptimal` to `vk::ImageLayout::eTransferSrcOptimal` so we can use this level as transfer source for the next level:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
image_memory_barrier = vk::ImageMemoryBarrier(vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eTransferRead,
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::ImageLayout::eTransferSrcOptimal,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
texture.image,
|
||||
{vk::ImageAspectFlagBits::eColor, i, 1, 0, 1});
|
||||
copy_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, image_memory_barrier);
|
||||
}
|
||||
----
|
||||
|
||||
=== Final image layout transitions
|
||||
|
||||
Once the loop is done we need to transition all mip levels of the image to their actual usage layout, which is `vk::ImageLayout::eShaderReadOnlyOptimal` for this example.
|
||||
|
||||
Note that after the loop above all levels will be in the `vk::ImageLayout::eTransferSrcOptimal` layout allowing us to transfer the whole image with a single barrier:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
image_memory_barrier = vk::ImageMemoryBarrier(vk::AccessFlagBits::eTransferRead,
|
||||
vk::AccessFlagBits::eShaderRead,
|
||||
vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
texture.image,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, texture.mip_levels, 0, 1});
|
||||
copy_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, image_memory_barrier);
|
||||
----
|
||||
|
||||
Submitting that command buffer will result in an image with a complete mip-chain and all mip levels being transitioned to the proper image layout for shader reads.
|
||||
|
||||
=== Image View creation
|
||||
|
||||
The Image View also requires information about how many Mip Levels are used.
|
||||
This is specified in the `vk::ImageViewCreateInfo.subresourceRange.levelCount` field.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::ImageViewCreateInfo image_view_create_info({},
|
||||
texture.image,
|
||||
vk::ImageViewType::e2D,
|
||||
format,
|
||||
{vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA},
|
||||
{vk::ImageAspectFlagBits::eColor, 0, texture.mip_levels, 0, 1});
|
||||
texture.view = get_device()->get_handle().createImageView(image_view_create_info);
|
||||
----
|
||||
|
||||
@@ -1,455 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Runtime mip map generation, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_texture_mipmap_generation.h"
|
||||
#include "common/hpp_vk_common.h"
|
||||
#include "common/ktx_common.h"
|
||||
#include "common/vk_initializers.h"
|
||||
#include "core/command_pool.h"
|
||||
|
||||
HPPTextureMipMapGeneration::HPPTextureMipMapGeneration()
|
||||
{
|
||||
title = "Texture MipMap generation";
|
||||
|
||||
zoom = -2.5f;
|
||||
rotation = {0.0f, 15.0f, 0.0f};
|
||||
}
|
||||
|
||||
HPPTextureMipMapGeneration::~HPPTextureMipMapGeneration()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
for (auto sampler : samplers)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
}
|
||||
device.destroyImageView(texture.view);
|
||||
device.destroyImage(texture.image);
|
||||
device.freeMemory(texture.device_memory);
|
||||
uniform_buffer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPTextureMipMapGeneration::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
prepare_camera();
|
||||
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
descriptor_set_layout = create_descriptor_set_layout();
|
||||
pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &descriptor_set_layout});
|
||||
pipeline = create_pipeline();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
descriptor_set = vkb::common::allocate_descriptor_set(get_device().get_handle(), descriptor_pool, descriptor_set_layout);
|
||||
update_descriptor_set();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
// Enable physical device features required for this example
|
||||
void HPPTextureMipMapGeneration::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 HPPTextureMipMapGeneration::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
|
||||
std::array<vk::ClearValue, 2> clear_values = {{default_clear_color, vk::ClearDepthStencilValue{1.0f, 0}}};
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
auto command_buffer = draw_cmd_buffers[i];
|
||||
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
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, pipeline_layout, 0, descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
|
||||
draw_model(scene, command_buffer);
|
||||
|
||||
draw_ui(command_buffer);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
drawer.checkbox("Rotate", &rotate_scene);
|
||||
if (drawer.slider_float("LOD bias", &ubo.lod_bias, 0.0f, static_cast<float>(texture.mip_levels)))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
if (drawer.combo_box("Sampler type", &ubo.sampler_index, sampler_names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
if (rotate_scene)
|
||||
{
|
||||
update_uniform_buffers(delta_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::check_format_features(vk::Format format) const
|
||||
{
|
||||
// Get device properties for the requested texture format
|
||||
vk::FormatProperties format_properties = get_device().get_gpu().get_handle().getFormatProperties(format);
|
||||
|
||||
// Check if the selected format supports blit source and destination, which is required for generating the mip levels
|
||||
vk::FormatFeatureFlags format_feature_flags = vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eBlitDst;
|
||||
|
||||
// If this is not supported you could implement a fallback via compute shader image writes and stores
|
||||
if ((format_properties.optimalTilingFeatures & format_feature_flags) != format_feature_flags)
|
||||
{
|
||||
throw std::runtime_error("Selected image format does not support blit source and destination");
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPTextureMipMapGeneration::create_descriptor_pool()
|
||||
{
|
||||
// Example uses one ubo and one image sampler
|
||||
std::array<vk::DescriptorPoolSize, 3> pool_sizes = {
|
||||
{{vk::DescriptorType::eUniformBuffer, 1}, {vk::DescriptorType::eSampledImage, 1}, {vk::DescriptorType::eSampler, 3}}};
|
||||
|
||||
vk::DescriptorPoolCreateInfo descriptor_pool_create_info{.maxSets = 2,
|
||||
.poolSizeCount = static_cast<uint32_t>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data()};
|
||||
|
||||
return get_device().get_handle().createDescriptorPool(descriptor_pool_create_info);
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout HPPTextureMipMapGeneration::create_descriptor_set_layout()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> set_layout_bindings = {
|
||||
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment}, // Binding 0 : Parameter uniform buffer
|
||||
{1, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment}, // Binding 1 : Fragment shader image sampler
|
||||
{2, vk::DescriptorType::eSampler, 3, vk::ShaderStageFlagBits::eFragment}}}; // Binding 2 : Sampler array (3 descriptors)
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_layout{.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()),
|
||||
.pBindings = set_layout_bindings.data()};
|
||||
|
||||
return get_device().get_handle().createDescriptorSetLayout(descriptor_layout);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPTextureMipMapGeneration::create_pipeline()
|
||||
{
|
||||
// Load shaders
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {
|
||||
load_shader("texture_mipmap_generation", "texture.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("texture_mipmap_generation", "texture.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Vertex bindings and attributes
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {{
|
||||
{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Position
|
||||
{1, 0, vk::Format::eR32G32Sfloat, sizeof(float) * 6}, // UV
|
||||
}};
|
||||
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()};
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eLessOrEqual;
|
||||
depth_stencil_state.depthTestEnable = true;
|
||||
depth_stencil_state.depthWriteEnable = true;
|
||||
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,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
|
||||
// Submit to queue
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::load_assets()
|
||||
{
|
||||
scene = load_model("scenes/tunnel_cylinder.gltf");
|
||||
|
||||
// Load the base texture containing only the first mip level and generate the whole mip-chain at runtime
|
||||
ktxTexture *ktx_texture = vkb::ktx::load_texture(vkb::fs::path::get(vkb::fs::path::Assets, "textures/checkerboard_rgba.ktx"));
|
||||
|
||||
texture.extent = vk::Extent2D{ktx_texture->baseWidth, ktx_texture->baseHeight};
|
||||
|
||||
// Calculate number of mip levels as per Vulkan specs:
|
||||
// numLevels = 1 + floor(log2(max(w, h, d)))
|
||||
texture.mip_levels = static_cast<uint32_t>(floor(log2(std::max(texture.extent.width, texture.extent.height))) + 1);
|
||||
|
||||
// ktx1 doesn't know whether the content is sRGB or linear, but most tools save in sRGB, so assume that.
|
||||
constexpr vk::Format format = vk::Format::eR8G8B8A8Srgb;
|
||||
check_format_features(format);
|
||||
|
||||
// Create a host-visible staging buffer that contains the raw image data
|
||||
vkb::core::BufferCpp staging_buffer = vkb::core::BufferCpp::create_staging_buffer(get_device(), ktx_texture->dataSize, ktx_texture->pData);
|
||||
|
||||
// now, the ktx_texture can be destroyed
|
||||
ktxTexture_Destroy(ktx_texture);
|
||||
|
||||
// Create optimal tiled target image on the device
|
||||
auto device = get_device().get_handle();
|
||||
|
||||
vk::ImageCreateInfo image_create_info{.imageType = vk::ImageType::e2D,
|
||||
.format = format,
|
||||
.extent = {.width = texture.extent.width, .height = texture.extent.height, .depth = 1},
|
||||
.mipLevels = texture.mip_levels,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = vk::ImageTiling::eOptimal,
|
||||
.usage =
|
||||
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eSampled,
|
||||
.sharingMode = vk::SharingMode::eExclusive};
|
||||
texture.image = device.createImage(image_create_info);
|
||||
|
||||
vk::MemoryRequirements memory_requirements = device.getImageMemoryRequirements(texture.image);
|
||||
|
||||
vk::MemoryAllocateInfo memory_allocation{.allocationSize = memory_requirements.size,
|
||||
.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits,
|
||||
vk::MemoryPropertyFlagBits::eDeviceLocal)};
|
||||
texture.device_memory = device.allocateMemory(memory_allocation);
|
||||
device.bindImageMemory(texture.image, texture.device_memory, 0);
|
||||
|
||||
vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(get_device().get_handle(), get_device().get_command_pool().get_handle());
|
||||
copy_command.begin(vk::CommandBufferBeginInfo());
|
||||
|
||||
// Optimal image will be used as destination for the copy, so we must transfer from our initial undefined image layout to the transfer destination layout
|
||||
vkb::common::image_layout_transition(copy_command, texture.image, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
|
||||
|
||||
// Copy the first mip of the chain, remaining mips will be generated
|
||||
vk::BufferImageCopy buffer_copy_region{.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
|
||||
.imageExtent = {texture.extent.width, texture.extent.height, 1}};
|
||||
copy_command.copyBufferToImage(staging_buffer.get_handle(), texture.image, vk::ImageLayout::eTransferDstOptimal, buffer_copy_region);
|
||||
|
||||
// Transition first mip level to transfer source so we can blit(read) from it
|
||||
vkb::common::image_layout_transition(copy_command, texture.image, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal);
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
|
||||
// Generate the mip chain
|
||||
// ---------------------------------------------------------------
|
||||
// We copy down the whole mip chain doing a blit from mip-1 to mip
|
||||
// An alternative way would be to always blit from the first mip level and sample that one down
|
||||
vk::CommandBuffer blit_command = vkb::common::allocate_command_buffer(get_device().get_handle(), get_device().get_command_pool().get_handle());
|
||||
blit_command.begin(vk::CommandBufferBeginInfo());
|
||||
|
||||
// Copy down mips from n-1 to n
|
||||
for (uint32_t i = 1; i < texture.mip_levels; i++)
|
||||
{
|
||||
vk::ImageBlit image_blit{
|
||||
{vk::ImageAspectFlagBits::eColor, i - 1, 0, 1},
|
||||
{{{{},
|
||||
{static_cast<int32_t>(texture.extent.width >> (i - 1)),
|
||||
static_cast<int32_t>(texture.extent.height >> (i - 1)),
|
||||
static_cast<int32_t>(1)}}}},
|
||||
{vk::ImageAspectFlagBits::eColor, i, 0, 1},
|
||||
{{{{}, {static_cast<int32_t>(texture.extent.width >> i), static_cast<int32_t>(texture.extent.height >> i), static_cast<int32_t>(1)}}}}};
|
||||
|
||||
// Prepare current mip level as image blit destination
|
||||
vk::ImageSubresourceRange image_subresource_range{vk::ImageAspectFlagBits::eColor, i, 1, 0, 1};
|
||||
vkb::common::image_layout_transition(
|
||||
blit_command, texture.image, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, image_subresource_range);
|
||||
|
||||
// Blit from previous level
|
||||
blit_command.blitImage(texture.image, vk::ImageLayout::eTransferSrcOptimal, texture.image, vk::ImageLayout::eTransferDstOptimal, image_blit, vk::Filter::eLinear);
|
||||
|
||||
// Prepare current mip level as image blit source for next level
|
||||
vkb::common::image_layout_transition(
|
||||
blit_command, texture.image, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, image_subresource_range);
|
||||
}
|
||||
|
||||
// After the loop, all mip layers are in TRANSFER_SRC layout, so transition all to SHADER_READ
|
||||
vkb::common::image_layout_transition(blit_command,
|
||||
texture.image,
|
||||
vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, texture.mip_levels, 0, 1});
|
||||
|
||||
get_device().flush_command_buffer(blit_command, queue, true);
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// Create samplers for different mip map demonstration cases
|
||||
|
||||
// Without mip mapping
|
||||
samplers[0] = vkb::common::create_sampler(get_device().get_gpu().get_handle(), get_device().get_handle(), format,
|
||||
vk::Filter::eLinear, vk::SamplerAddressMode::eRepeat, 1.0f, 0.0f);
|
||||
|
||||
// With mip mapping
|
||||
samplers[1] =
|
||||
vkb::common::create_sampler(get_device().get_gpu().get_handle(), get_device().get_handle(), format,
|
||||
vk::Filter::eLinear, vk::SamplerAddressMode::eRepeat, 1.0f, static_cast<float>(texture.mip_levels));
|
||||
|
||||
// With mip mapping and anisotropic filtering (when supported)
|
||||
samplers[2] = vkb::common::create_sampler(
|
||||
get_device().get_gpu().get_handle(),
|
||||
get_device().get_handle(),
|
||||
format,
|
||||
vk::Filter::eLinear,
|
||||
vk::SamplerAddressMode::eRepeat,
|
||||
get_device().get_gpu().get_features().samplerAnisotropy ? (get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy) : 1.0f,
|
||||
static_cast<float>(texture.mip_levels));
|
||||
|
||||
// Create image view
|
||||
texture.view = vkb::common::create_image_view(
|
||||
get_device().get_handle(), texture.image, vk::ImageViewType::e2D, format, vk::ImageAspectFlagBits::eColor, 0, texture.mip_levels);
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::prepare_camera()
|
||||
{
|
||||
camera.type = vkb::CameraType::FirstPerson;
|
||||
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 0.1f, 1024.0f);
|
||||
camera.set_translation(glm::vec3(0.0f, 0.0f, -12.5f));
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::prepare_uniform_buffers()
|
||||
{
|
||||
// Shared parameter uniform buffer block
|
||||
uniform_buffer = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
||||
sizeof(ubo),
|
||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::update_descriptor_set()
|
||||
{
|
||||
vk::DescriptorBufferInfo buffer_descriptor{uniform_buffer->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
vk::DescriptorImageInfo image_descriptor{nullptr, texture.view, vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
|
||||
std::array<vk::DescriptorImageInfo, 3> sampler_descriptors = {{{samplers[0], nullptr, vk::ImageLayout::eShaderReadOnlyOptimal},
|
||||
{samplers[1], nullptr, vk::ImageLayout::eShaderReadOnlyOptimal},
|
||||
{samplers[2], nullptr, vk::ImageLayout::eShaderReadOnlyOptimal}}};
|
||||
assert(samplers.size() == sampler_descriptors.size());
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> write_descriptor_sets = {{{.dstSet = descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &buffer_descriptor}, // Binding 0 : Vertex shader uniform buffer
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
.pImageInfo = &image_descriptor}, // Binding 1 : Fragment shader texture sampler
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = static_cast<uint32_t>(sampler_descriptors.size()),
|
||||
.descriptorType = vk::DescriptorType::eSampler,
|
||||
.pImageInfo = sampler_descriptors.data()}}}; // Binding 2: Sampler array
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPTextureMipMapGeneration::update_uniform_buffers(float delta_time)
|
||||
{
|
||||
ubo.projection = camera.matrices.perspective;
|
||||
ubo.model = camera.matrices.view;
|
||||
ubo.model = glm::rotate(ubo.model, glm::radians(90.0f + timer * 360.0f), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
ubo.model = glm::scale(ubo.model, glm::vec3(0.5f));
|
||||
timer += delta_time * 0.005f;
|
||||
if (timer > 1.0f)
|
||||
{
|
||||
timer -= 1.0f;
|
||||
}
|
||||
uniform_buffer->convert_and_update(ubo);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_texture_mipmap_generation()
|
||||
{
|
||||
return std::make_unique<HPPTextureMipMapGeneration>();
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/* Copyright (c) 2022-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Runtime mip map generation, using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
#include <ktx.h>
|
||||
|
||||
class HPPTextureMipMapGeneration : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPTextureMipMapGeneration();
|
||||
~HPPTextureMipMapGeneration();
|
||||
|
||||
private:
|
||||
struct Texture
|
||||
{
|
||||
vk::Image image;
|
||||
vk::DeviceMemory device_memory;
|
||||
vk::ImageView view;
|
||||
vk::Extent2D extent;
|
||||
uint32_t mip_levels;
|
||||
};
|
||||
|
||||
struct UBO
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
float lod_bias = 0.0f;
|
||||
int32_t sampler_index = 2;
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
void build_command_buffers() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void render(float delta_time) override;
|
||||
void view_changed() override;
|
||||
|
||||
void check_format_features(vk::Format) const;
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::DescriptorSetLayout create_descriptor_set_layout();
|
||||
vk::Pipeline create_pipeline();
|
||||
void draw();
|
||||
void load_assets();
|
||||
void prepare_camera();
|
||||
void prepare_uniform_buffers();
|
||||
void update_descriptor_set();
|
||||
void update_uniform_buffers(float delta_time = 0.0f);
|
||||
|
||||
private:
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::Pipeline pipeline;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
bool rotate_scene = false;
|
||||
// To demonstrate mip mapping and filtering this example uses separate samplers
|
||||
std::vector<std::string> sampler_names{"No mip maps", "Mip maps (bilinear)", "Mip maps (anisotropic)"};
|
||||
std::array<vk::Sampler, 3> samplers;
|
||||
std::unique_ptr<vkb::scene_graph::components::HPPSubMesh> scene;
|
||||
Texture texture;
|
||||
UBO ubo;
|
||||
std::unique_ptr<vkb::core::BufferCpp> uniform_buffer;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_texture_mipmap_generation();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2023-2024, 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "HPP Timestamp queries"
|
||||
DESCRIPTION "Using timestamp queries to fetch timing information from the GPU, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"hdr/glsl/composition.vert"
|
||||
"hdr/glsl/composition.frag"
|
||||
"hdr/glsl/bloom.vert"
|
||||
"hdr/glsl/bloom.frag"
|
||||
"hdr/glsl/gbuffer.vert"
|
||||
"hdr/glsl/gbuffer.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hdr/hlsl/composition.vert.hlsl"
|
||||
"hdr/hlsl/composition.frag.hlsl"
|
||||
"hdr/hlsl/bloom.vert.hlsl"
|
||||
"hdr/hlsl/bloom.frag.hlsl"
|
||||
"hdr/hlsl/gbuffer.vert.hlsl"
|
||||
"hdr/hlsl/gbuffer.frag.hlsl")
|
||||
@@ -1,282 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023-2024, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
:doctype: book
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= Timestamp queries with Vulkan-Hpp
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_timestamp_queries[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/timestamp_queries[Timestamp queries] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp.
|
||||
|
||||
This tutorial, along with the accompanying example code, shows how to use timestamp queries to measure timings on the GPU.
|
||||
|
||||
The sample, based on the HDR one, does multiple render passes and will use timestamp queries to get GPU timings for the different render passes.
|
||||
This is done by writing GPU timestamps at certain points within a command buffer.
|
||||
These can then be read on the host and used for approximate profiling and to e.g.
|
||||
improve performance where needed.
|
||||
|
||||
== Introduction
|
||||
|
||||
Vulkan offers several https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#queries[query types] that allow you to query different types of information from the GPU.
|
||||
One such query type is the https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#queries-timestamps[timestamp query].
|
||||
|
||||
This provides your application with a mechanism to time the execution of commands on the GPU.
|
||||
As with the other query types, a query pool is then used to either directly fetch or copy over the results to the host.
|
||||
|
||||
== A few important notes on timestamp queries
|
||||
|
||||
It's important to know that timestamp queries differ greatly from how timing can be done on the CPU with e.g.
|
||||
the high performance counter.
|
||||
This is mostly due to how a GPU's dispatches, overlaps and finishes work across different stages of the pipeline.
|
||||
So while technically you can specify any pipeline stage at which the timestamp should be written, a lot of stage combinations and orderings won't give meaningful result.
|
||||
This also means that you can't compare timestamps taken on different queues.
|
||||
|
||||
So while it may may sound reasonable to write timestamps for the vertex and fragment shader stage directly one after another, that will usually not return meaningful results due to how the GPU works.
|
||||
|
||||
And so for this example, we take the same approach as some popular CPU/GPU profilers by only using the top and bottom stages of the pipeline.
|
||||
This combination is known to give proper approximate timing results on most GPUs.
|
||||
|
||||
== Checking for support
|
||||
|
||||
Not all GPUs support timestamp queries, so before using them we need to make sure that they can be used.
|
||||
This differs slightly from checking other features with a simple `vk::Bool`.
|
||||
Here we need to check if the `timestampPeriod` limit of the physical device is greater than zero.
|
||||
If that's the case, timestamp queries are supported:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::PhysicalDeviceLimits const &device_limits = device->get_gpu().get_properties().limits;
|
||||
if (device_limits.timestampPeriod == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected device does not support timestamp queries!"};
|
||||
}
|
||||
----
|
||||
|
||||
Another limit we need to check is `timestampComputeAndGraphics`.
|
||||
If this is `true`, all graphics and compute pipelines support timestamp queries and the above check is sufficient.
|
||||
If not, we need to check if the queue we want to use supports timestamps:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
if (!device_limits.timestampComputeAndGraphics)
|
||||
{
|
||||
// Check if the graphics queue used in this sample supports time stamps
|
||||
vk::QueueFamilyProperties const &graphics_queue_family_properties = device->get_suitable_graphics_queue().get_properties();
|
||||
if (graphics_queue_family_properties.timestampValidBits == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected graphics queue family does not support timestamp queries!"};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Creating the query pool
|
||||
|
||||
As with all query types, we first need to create a pool for the timestamp queries.
|
||||
This is used to store and read back the results (see `prepare_time_stamp_queries`):
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::QueryPoolCreateInfo query_pool_create_info({}, vk::QueryType::eTimestamp, static_cast<uint32_t>(time_stamps.size()));
|
||||
time_stamps_query_pool = get_device()->get_handle().createQueryPool(query_pool_create_info);
|
||||
----
|
||||
|
||||
The interesting parts are the `queryType`, which we set to `vk::QueryType::eTimestamp` for using timestamp queries and the `queryCount`, which is the maximum number of the the timestamp query result this pool can store.
|
||||
|
||||
For this sample we'll be using 6 time points, one for the start and one for the end of three render passes.
|
||||
|
||||
== Resetting the query pool
|
||||
|
||||
Before we can start writing data to the query pool, we need to reset it.
|
||||
When using Vulkan 1.0 or 1.1, this requires us to enable the `VK_EXT_host_query_reset` extension:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
add_device_extension(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
|
||||
----
|
||||
|
||||
With using Vulkan 1.2 this extension has become part of the core and we won't have to manually enable it.
|
||||
|
||||
Independent of this, we also need to enable the `hostQueryReset` physical device feature:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
auto &requested_extension_features = gpu.request_extension_features<vk::PhysicalDeviceHostQueryResetFeaturesEXT>();
|
||||
requested_extension_features.hostQueryReset = true;
|
||||
----
|
||||
|
||||
With features and extensions properly enabled, we can now reset the pool at the start of the command buffer, before writing the first timestamp.
|
||||
This is done using `vk::CommandBuffer::resetQueryPool`:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
...
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
command_buffer.resetQueryPool(time_stamps_query_pool, 0, static_cast<uint32_t>(time_stamps.size()));
|
||||
----
|
||||
|
||||
'''
|
||||
|
||||
== Writing time stamps
|
||||
|
||||
Unlike getting CPU side timing information that can be queried immediately, with GPU time stamps we need to tell the implementation inside a command buffer when/where to write timestamps instead.
|
||||
The results are then fetched afterwards (see below).
|
||||
|
||||
This is done inside the command buffer with `vk::CommandBuffer::writeTimestamp`.
|
||||
This function will request a timestamp to be written from the GPU for a certain pipeline stage and write that value to memory.
|
||||
|
||||
The most interesting part of calling this function is the `pipelineStage` argument.
|
||||
As noted earlier, it's technically possible to use any pipeline stage in here, not all pipeline stages will yield proper results due to how GPUs overlap work.
|
||||
It's also important to note that not all implementations are able to latch timers at all pipeline stages (e.g.
|
||||
if they don't have hardware that maps to a given stage) and may return timers at a later pipeline stage instead.
|
||||
|
||||
Calling this function also defines an execution dependency similar to a barrier on all commands that were submitted before it.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps_query_pool, 0);
|
||||
// Do some work
|
||||
for (int i = 0; i < draw_call_count; i++) {
|
||||
command_buffer.draw(...);
|
||||
}
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps_query_pool, 1);
|
||||
----
|
||||
|
||||
To measure GPU times for the draw calls(s) we first tell the GPU to write a timestamp at the `vk::PipelineStageFlagBits::eTopOfPipe` pipeline stage.
|
||||
This is not a real pipeline stage (as in e.g.
|
||||
the vertex or fragment stages) but a special constant that tells the GPU to write the timestamp when all previous commands have been processed by the GPU's command processor.
|
||||
This ensures that we get a timestamp right before starting on the draw calls we want to measure, which will be the base for calculating our delta time.
|
||||
|
||||
The second timestamp is written at the `vk::PipelineStageFlagBits::eBottomOfPipe` pipeline stage.
|
||||
Once again this is not a real pipeline stage, but it again tells the GPU to write the timestamp after all work has been finished.
|
||||
|
||||
== Getting the results
|
||||
|
||||
Reading back the results can be done in two ways:
|
||||
|
||||
* Copy the results into a `vk::Buffer` inside the command buffer using `vk::CommandBuffer::copyQueryPoolResults`
|
||||
* Get the results after the command buffer has finished executing using `vk::Device::getQueryPoolResults`
|
||||
|
||||
For our sample we'll use option two (see `get_time_stamp_results`):
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
queue.submit(submit_info);
|
||||
...
|
||||
// The number of timestamps changes if the bloom pass is disabled
|
||||
uint32_t count = bloom ? time_stamps.size() : time_stamps.size() - 2;
|
||||
|
||||
vk::Result result = device->get_handle().getQueryPoolResults(time_stamps_query_pool,
|
||||
0,
|
||||
count,
|
||||
time_stamps.size() * sizeof(uint64_t),
|
||||
time_stamps.data(),
|
||||
sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
|
||||
----
|
||||
|
||||
Most arguments are straightforward, e.g.
|
||||
where the data will be copied to (the `time_stamps` vector).
|
||||
The important part here are the `vk::QueryResultFlags ` flags used here.
|
||||
|
||||
`vk::QueryResultFlagBits::e64` will tell the api that we want to get the results as 64 bit values.
|
||||
Without this flag, we would only get 32 bit values.
|
||||
And since timestamp queries can operate in nanoseconds, only using 32 bits could result into an overflow.
|
||||
E.g.
|
||||
if your device has a `timestampPeriod` of 1, so that one increment in the result maps to exactly one nanosecond, with 32 bit precision you'd run into such an overflow after only about 0.43 seconds.
|
||||
|
||||
The `vk::QueryResultFlagBits::eWait` bit then tells the api to wait for all results to be available.
|
||||
So when using this flag the values written to our `time_stamps` vector is guaranteed to be available after calling `vk::Device::getQueryPoolResults`.
|
||||
This is fine for our use-case where we want to immediately access the results, but may introduce unnecessary stalls in other scenarios.
|
||||
|
||||
Alternatively you can use the `vk::QueryResultFlagBits::eWithAvailability` flag, which will let you poll the availability of the results and defer writing new timestamps until the results are available.
|
||||
This should be the preferred way of fetching the results in a real-world application.
|
||||
Using this flag an additional availability value is inserted after each query value.
|
||||
If that value becomes non-zero, the result is available.
|
||||
You then check availability before writing the timestamp again.
|
||||
|
||||
Here is a basic example of how this could look like for a single timestamp value:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// time_stamp_with_availibility[current_frame * 2] contains the queried timestamp
|
||||
// time_stamp_with_availibility[current_frame * 2 + 1] contains availability of the timestamp
|
||||
std::array<uint64_t, max_frames_in_flight * 2> time_stamp_with_availibility{};
|
||||
|
||||
void drawFrame()
|
||||
{
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
// Only write new timestamp if previous result is available
|
||||
if (time_stamp_with_availibility[current_frame * 2 + 1] != 0)
|
||||
{
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps_query_pool, 0);
|
||||
}
|
||||
|
||||
// Issue draw commands
|
||||
|
||||
command_buffer.end();
|
||||
|
||||
// Get deferred time stamp query for the current frame
|
||||
vk::Result result = device.getQueryPoolResults(time_stamps_query_pool,
|
||||
0,
|
||||
1,
|
||||
2 * sizeof(uint64_t),
|
||||
&time_stamp_with_availibility[current_frame * max_frames_in_flight],
|
||||
2 * sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWithAvailability);
|
||||
assert(result == vk::Result::eSuccess);
|
||||
|
||||
// Display time stamp for the current frame if available
|
||||
if (time_stamp_with_availibility[current_frame * 2 + 1] != 0) {
|
||||
std::cout << "Timestamp = " << time_stamp_with_availibility[current_frame * 2] << "\n";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Interpreting the results
|
||||
|
||||
After we have read back the results to the host, we are ready to interpret them.
|
||||
E.g.
|
||||
for displaying them in a user interface.
|
||||
|
||||
The results we got back do not actually contain a time value, but rather a number of "ticks".
|
||||
So to get the actual time value we need to translate these values first.
|
||||
|
||||
This is done using `timestampPeriod` limit of the physical device.
|
||||
It contains the number of nanoseconds it takes for a timestamp query value to be increased by 1 ("tick").
|
||||
|
||||
In our sample, we want to display the delta between two timestamps in milliseconds, so in addition to the above rule we also multiply the value accordingly.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::PhysicalDeviceLimits const &device_limits = device->get_gpu().get_properties().limits;
|
||||
float delta_in_ms = float(time_stamps[1] - time_stamps[0]) * device_limits.timestampPeriod / 1000000.0f;
|
||||
----
|
||||
|
||||
== vk::CommandBuffer::writeTimestamp2
|
||||
|
||||
The https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_synchronization2.html[VK_KHR_synchronization2] extension introduced `vk::CommandBuffer::writeTimestamp2`.
|
||||
This is pretty much the same as the `vk::CommandBuffer::writeTimestamp` function used in this sample, but adds support for some additional pipeline stages using `vk::PipelineStageFlags2`.
|
||||
|
||||
== Verdict
|
||||
|
||||
Even though timestamp queries are limited due to how a GPU works, they can still be useful for profiling and finding performance GPU bottlenecks.
|
||||
@@ -1,821 +0,0 @@
|
||||
/* Copyright (c) 2023-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Timestamp queries (based on the HDR sample), using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_timestamp_queries.h"
|
||||
#include "core/hpp_queue.h"
|
||||
|
||||
HPPTimestampQueries::HPPTimestampQueries()
|
||||
{
|
||||
title = "Timestamp queries";
|
||||
}
|
||||
|
||||
HPPTimestampQueries::~HPPTimestampQueries()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
time_stamps.destroy(device);
|
||||
bloom.destroy(device);
|
||||
composition.destroy(device);
|
||||
filter_pass.destroy(device);
|
||||
models.destroy(device, descriptor_pool);
|
||||
offscreen.destroy(device);
|
||||
textures.destroy(device);
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPTimestampQueries::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
// Check if the selected device supports timestamps. A value of zero means no support.
|
||||
vk::PhysicalDeviceLimits const &device_limits = get_device().get_gpu().get_properties().limits;
|
||||
if (device_limits.timestampPeriod == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected device does not support timestamp queries!"};
|
||||
}
|
||||
|
||||
// Check if all queues support timestamp queries, if not we need to check on a per-queue basis
|
||||
if (!device_limits.timestampComputeAndGraphics)
|
||||
{
|
||||
// Check if the graphics queue used in this sample supports time stamps
|
||||
vk::QueueFamilyProperties const &graphics_queue_family_properties = get_device().get_queue_by_flags(vk::QueueFlagBits::eGraphics, 0).get_properties();
|
||||
if (graphics_queue_family_properties.timestampValidBits == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected graphics queue family does not support timestamp queries!"};
|
||||
}
|
||||
}
|
||||
|
||||
prepare_camera();
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
prepare_offscreen_buffer();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
prepare_bloom();
|
||||
prepare_composition();
|
||||
prepare_models();
|
||||
prepare_time_stamps();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool HPPTimestampQueries::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
HPPApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::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 HPPTimestampQueries::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);
|
||||
|
||||
// Reset the timestamp query pool, so we can start fetching new values into it
|
||||
command_buffer.resetQueryPool(time_stamps.query_pool, 0, static_cast<uint32_t>(time_stamps.values.size()));
|
||||
|
||||
{
|
||||
/*
|
||||
First pass: Render scene to offscreen framebuffer
|
||||
*/
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps.query_pool, 0);
|
||||
|
||||
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();
|
||||
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps.query_pool, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
Second render pass: First bloom pass
|
||||
*/
|
||||
if (bloom.enabled)
|
||||
{
|
||||
vk::ClearValue clear_value(vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})));
|
||||
|
||||
// Bloom filter
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps.query_pool, 2);
|
||||
|
||||
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();
|
||||
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps.query_pool, 3);
|
||||
}
|
||||
|
||||
/*
|
||||
Note: Explicit synchronization is not required between the render pass, as this is done implicitly via sub pass dependencies
|
||||
*/
|
||||
|
||||
/*
|
||||
Third render pass: Scene rendering with applied second bloom pass (when enabled)
|
||||
*/
|
||||
{
|
||||
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}}};
|
||||
|
||||
// Final composition
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps.query_pool, bloom.enabled ? 4 : 2);
|
||||
|
||||
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.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps.query_pool, bloom.enabled ? 5 : 3);
|
||||
}
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::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();
|
||||
}
|
||||
}
|
||||
if (drawer.header("timing"))
|
||||
{
|
||||
// Timestamps don't have a time unit themselves, but are read as timesteps
|
||||
// The timestampPeriod property of the device tells how many nanoseconds such a timestep translates to on the selected device
|
||||
float timestampFrequency = get_device().get_gpu().get_properties().limits.timestampPeriod;
|
||||
|
||||
drawer.text("Pass 1: Offscreen scene rendering: %.3f ms", static_cast<float>(time_stamps.values[1] - time_stamps.values[0]) * timestampFrequency / 1000000.0f);
|
||||
drawer.text("Pass 2: %s %.3f ms", (bloom.enabled ? "First bloom pass" : "Scene display"), static_cast<float>(time_stamps.values[3] - time_stamps.values[2]) * timestampFrequency / 1000000.0f);
|
||||
if (bloom.enabled)
|
||||
{
|
||||
drawer.text("Pass 3: Second bloom pass %.3f ms", static_cast<float>(time_stamps.values[5] - time_stamps.values[4]) * timestampFrequency / 1000000.0f);
|
||||
drawer.set_dirty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DeviceMemory HPPTimestampQueries::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);
|
||||
}
|
||||
|
||||
HPPTimestampQueries::FramebufferAttachment HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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{.attachment = 0, .layout = vk::ImageLayout::eColorAttachmentOptimal};
|
||||
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics, .colorAttachmentCount = 1, .pColorAttachments = &color_reference};
|
||||
|
||||
return create_render_pass({attachment_description}, subpass);
|
||||
}
|
||||
|
||||
vk::Image HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
|
||||
// Read back the time stamp query results after the frame is finished
|
||||
get_time_stamp_results();
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::get_time_stamp_results()
|
||||
{
|
||||
// The number of timestamps changes if the bloom pass is disabled
|
||||
uint32_t count = static_cast<uint32_t>(bloom.enabled ? time_stamps.values.size() : time_stamps.values.size() - 2);
|
||||
|
||||
// Fetch the time stamp results written in the command buffer submissions
|
||||
// A note on the flags used:
|
||||
// vk::QueryResultFlagBits::e64: Results will have 64 bits. As time stamp values are on nano-seconds, this flag should always be used to avoid 32 bit overflows
|
||||
// vk::QueryResultFlagBits::eWait: Since we want to immediately display the results, we use this flag to have the CPU wait until the results are available
|
||||
vk::Result result = get_device().get_handle().getQueryPoolResults(time_stamps.query_pool,
|
||||
0,
|
||||
count,
|
||||
time_stamps.values.size() * sizeof(uint64_t),
|
||||
time_stamps.values.data(),
|
||||
sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
|
||||
assert(result == vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::prepare_offscreen_buffer()
|
||||
{
|
||||
{
|
||||
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(vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eColorAttachment);
|
||||
offscreen.color[1] = create_attachment(vk::Format::eR32G32B32A32Sfloat, 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(),
|
||||
offscreen.color[0].format, vk::Filter::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
// Bloom separable filter pass
|
||||
{
|
||||
filter_pass.extent = extent;
|
||||
|
||||
// Color attachments - needs to be a blendable format, so choose from a priority ordered list
|
||||
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);
|
||||
|
||||
// One floating point color buffer
|
||||
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(),
|
||||
filter_pass.color.format, vk::Filter::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::prepare_time_stamps()
|
||||
{
|
||||
// Create the query pool object used to get the GPU time tamps
|
||||
time_stamps.query_pool =
|
||||
vkb::common::create_query_pool(get_device().get_handle(), vk::QueryType::eTimestamp, static_cast<uint32_t>(time_stamps.values.size()));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::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 HPPTimestampQueries::update_params()
|
||||
{
|
||||
uniform_buffers.params->convert_and_update(ubo_params);
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::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_timestamp_queries()
|
||||
{
|
||||
return std::make_unique<HPPTimestampQueries>();
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Timestamp queries (based on the HDR sample), using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
class HPPTimestampQueries : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPTimestampQueries();
|
||||
~HPPTimestampQueries();
|
||||
|
||||
private:
|
||||
struct Bloom
|
||||
{
|
||||
bool enabled = true;
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipelines[2];
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipelines[0]);
|
||||
device.destroyPipeline(pipelines[1]);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
}
|
||||
};
|
||||
|
||||
struct Composition
|
||||
{
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::DescriptorSet descriptor_set = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
}
|
||||
};
|
||||
|
||||
// Framebuffer for offscreen rendering
|
||||
struct FramebufferAttachment
|
||||
{
|
||||
vk::Format format = {};
|
||||
vk::Image image = {};
|
||||
vk::DeviceMemory mem = {};
|
||||
vk::ImageView view = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyImageView(view);
|
||||
device.destroyImage(image);
|
||||
device.freeMemory(mem);
|
||||
}
|
||||
};
|
||||
|
||||
struct FilterPass
|
||||
{
|
||||
vk::Extent2D extent = {};
|
||||
vk::Framebuffer framebuffer = {};
|
||||
FramebufferAttachment color = {};
|
||||
vk::RenderPass render_pass = {};
|
||||
vk::Sampler sampler = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
device.destroyRenderPass(render_pass);
|
||||
color.destroy(device);
|
||||
}
|
||||
};
|
||||
|
||||
struct Geometry
|
||||
{
|
||||
vk::DescriptorSet descriptor_set = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
std::vector<std::unique_ptr<vkb::scene_graph::components::HPPSubMesh>> meshes = {};
|
||||
|
||||
void destroy(vk::Device device, vk::DescriptorPool descriptor_pool)
|
||||
{
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
device.destroyPipeline(pipeline);
|
||||
}
|
||||
};
|
||||
|
||||
struct Models
|
||||
{
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
Geometry objects = {};
|
||||
Geometry skybox = {};
|
||||
std::vector<glm::mat4> transforms = {};
|
||||
int32_t object_index = 0;
|
||||
|
||||
void destroy(vk::Device device, vk::DescriptorPool descriptor_pool)
|
||||
{
|
||||
objects.destroy(device, descriptor_pool);
|
||||
skybox.destroy(device, descriptor_pool);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
}
|
||||
};
|
||||
|
||||
struct Offscreen
|
||||
{
|
||||
vk::Extent2D extent = {};
|
||||
vk::Framebuffer framebuffer = {};
|
||||
FramebufferAttachment color[2] = {};
|
||||
FramebufferAttachment depth = {};
|
||||
vk::RenderPass render_pass = {};
|
||||
vk::Sampler sampler = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
device.destroyRenderPass(render_pass);
|
||||
color[0].destroy(device);
|
||||
color[1].destroy(device);
|
||||
depth.destroy(device);
|
||||
}
|
||||
};
|
||||
|
||||
struct Textures
|
||||
{
|
||||
HPPTexture envmap;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(envmap.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
struct TimeStamps
|
||||
{
|
||||
std::array<uint64_t, 6> values; // GPU time stamps will be stored in an array
|
||||
vk::QueryPool query_pool; // A query pool is required to use GPU time stamps
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyQueryPool(query_pool);
|
||||
}
|
||||
};
|
||||
|
||||
struct UBOMatrices
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skybox_modelview;
|
||||
glm::mat4 inverse_modelview;
|
||||
float modelscale = 0.05f;
|
||||
};
|
||||
|
||||
struct UBOParams
|
||||
{
|
||||
float exposure = 1.0f;
|
||||
};
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferCpp> matrices;
|
||||
std::unique_ptr<vkb::core::BufferCpp> params;
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
virtual void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
virtual void build_command_buffers() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
virtual void render(float delta_time) override;
|
||||
|
||||
vk::DeviceMemory allocate_memory(vk::Image image);
|
||||
FramebufferAttachment create_attachment(vk::Format format, vk::ImageUsageFlagBits usage);
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::Pipeline create_bloom_pipeline(uint32_t direction);
|
||||
vk::Pipeline create_composition_pipeline();
|
||||
vk::RenderPass create_filter_render_pass();
|
||||
vk::Image create_image(vk::Format format, vk::ImageUsageFlagBits usage);
|
||||
vk::Pipeline create_models_pipeline(uint32_t shaderType, vk::CullModeFlagBits cullMode, bool depthTestAndWrite);
|
||||
vk::RenderPass create_offscreen_render_pass();
|
||||
vk::RenderPass create_render_pass(std::vector<vk::AttachmentDescription> const &attachment_descriptions, vk::SubpassDescription const &subpass_description);
|
||||
void draw();
|
||||
void get_time_stamp_results();
|
||||
void load_assets();
|
||||
void prepare_bloom();
|
||||
void prepare_camera();
|
||||
void prepare_composition();
|
||||
void prepare_models();
|
||||
void prepare_offscreen_buffer();
|
||||
void prepare_time_stamps();
|
||||
void prepare_uniform_buffers();
|
||||
void update_composition_descriptor_set();
|
||||
void update_bloom_descriptor_set();
|
||||
void update_model_descriptor_set(vk::DescriptorSet descriptor_set);
|
||||
void update_params();
|
||||
void update_uniform_buffers();
|
||||
|
||||
private:
|
||||
Bloom bloom;
|
||||
Composition composition;
|
||||
bool display_skybox = true;
|
||||
FilterPass filter_pass;
|
||||
Models models;
|
||||
std::vector<std::string> object_names;
|
||||
Offscreen offscreen;
|
||||
Textures textures;
|
||||
TimeStamps time_stamps;
|
||||
UBOMatrices ubo_matrices;
|
||||
UBOParams ubo_params;
|
||||
UniformBuffers uniform_buffers;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_timestamp_queries();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2019-2024, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Instancing"
|
||||
DESCRIPTION "Instanced mesh rendering, uses a separate vertex buffer for instanced data"
|
||||
SHADER_FILES_GLSL
|
||||
"instancing/glsl/instancing.vert"
|
||||
"instancing/glsl/instancing.frag"
|
||||
"instancing/glsl/planet.vert"
|
||||
"instancing/glsl/planet.frag"
|
||||
"instancing/glsl/starfield.vert"
|
||||
"instancing/glsl/starfield.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"instancing/hlsl/instancing.vert.hlsl"
|
||||
"instancing/hlsl/instancing.frag.hlsl"
|
||||
"instancing/hlsl/planet.vert.hlsl"
|
||||
"instancing/hlsl/planet.frag.hlsl"
|
||||
"instancing/hlsl/starfield.vert.hlsl"
|
||||
"instancing/hlsl/starfield.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Instancing
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/instancing[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
Uses the instancing feature for rendering many instances of the same mesh from a single vertex buffer with variable parameters and textures.
|
||||
@@ -1,522 +0,0 @@
|
||||
/* Copyright (c) 2019-2025, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Instanced mesh rendering, uses a separate vertex buffer for instanced data
|
||||
*/
|
||||
|
||||
#include "instancing.h"
|
||||
|
||||
#include "benchmark_mode/benchmark_mode.h"
|
||||
|
||||
Instancing::Instancing()
|
||||
{
|
||||
title = "Instanced mesh rendering";
|
||||
}
|
||||
|
||||
Instancing::~Instancing()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.instanced_rocks, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.planet, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.starfield, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), textures.rocks.sampler, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), textures.planet.sampler, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Instancing::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
auto &requested_features = gpu.get_mutable_requested_features();
|
||||
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
requested_features.samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
// Enable texture compression
|
||||
if (gpu.get_features().textureCompressionBC)
|
||||
{
|
||||
requested_features.textureCompressionBC = VK_TRUE;
|
||||
}
|
||||
else if (gpu.get_features().textureCompressionASTC_LDR)
|
||||
{
|
||||
requested_features.textureCompressionASTC_LDR = VK_TRUE;
|
||||
}
|
||||
else if (gpu.get_features().textureCompressionETC2)
|
||||
{
|
||||
requested_features.textureCompressionETC2 = VK_TRUE;
|
||||
}
|
||||
};
|
||||
|
||||
void Instancing::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.033f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
// Set target frame buffer
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
|
||||
// Star field
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_sets.planet, 0, NULL);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.starfield);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 4, 1, 0, 0);
|
||||
|
||||
// Planet
|
||||
auto &planet_vertex_buffer = models.planet->vertex_buffers.at("vertex_buffer");
|
||||
auto &planet_index_buffer = models.planet->index_buffer;
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_sets.planet, 0, NULL);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.planet);
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, planet_vertex_buffer.get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], planet_index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], models.planet->vertex_indices, 1, 0, 0, 0);
|
||||
|
||||
// Instanced rocks
|
||||
auto &rock_vertex_buffer = models.rock->vertex_buffers.at("vertex_buffer");
|
||||
auto &rock_index_buffer = models.rock->index_buffer;
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_sets.instanced_rocks, 0, NULL);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.instanced_rocks);
|
||||
// Binding point 0 : Mesh vertex buffer
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, rock_vertex_buffer.get(), offsets);
|
||||
// Binding point 1 : Instance data buffer
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 1, 1, &instance_buffer.buffer->get_handle(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], rock_index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
// Render instances
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], models.rock->vertex_indices, INSTANCE_COUNT, 0, 0, 0);
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void Instancing::load_assets()
|
||||
{
|
||||
models.rock = load_model("scenes/rock.gltf");
|
||||
models.planet = load_model("scenes/planet.gltf");
|
||||
|
||||
// models.rock.loadFromFile(getAssetPath() + "scenes/rock.gltf", device.get(), queue);
|
||||
// models.planet.loadFromFile(getAssetPath() + "scenes/planet.gltf", device.get(), queue);
|
||||
|
||||
textures.rocks = load_texture_array("textures/texturearray_rocks_color_rgba.ktx", vkb::sg::Image::Color);
|
||||
textures.planet = load_texture("textures/lavaplanet_color_rgba.ktx", vkb::sg::Image::Color);
|
||||
|
||||
// textures.rocks.loadFromFile(getAssetPath() + "textures/texturearray_rocks_color_rgba.ktx", device.get(), queue);
|
||||
// textures.planet.loadFromFile(getAssetPath() + "textures/lavaplanet_color_rgba.ktx", device.get(), queue);
|
||||
}
|
||||
|
||||
void Instancing::setup_descriptor_pool()
|
||||
{
|
||||
// Example uses one ubo
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes =
|
||||
{
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2),
|
||||
};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
vkb::to_u32(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
2);
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void Instancing::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings =
|
||||
{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0),
|
||||
// Binding 1 : Fragment shader combined sampler
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
1),
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
vkb::to_u32(set_layout_bindings.size()));
|
||||
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void Instancing::setup_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo descriptor_set_alloc_info;
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets;
|
||||
|
||||
descriptor_set_alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layout, 1);
|
||||
|
||||
// Instanced rocks
|
||||
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*uniform_buffers.scene);
|
||||
VkDescriptorImageInfo image_descriptor = create_descriptor(textures.rocks);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_alloc_info, &descriptor_sets.instanced_rocks));
|
||||
write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.instanced_rocks, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &buffer_descriptor), // Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.instanced_rocks, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &image_descriptor) // Binding 1 : Color map
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), vkb::to_u32(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
|
||||
// Planet
|
||||
buffer_descriptor = create_descriptor(*uniform_buffers.scene);
|
||||
image_descriptor = create_descriptor(textures.planet);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_alloc_info, &descriptor_sets.planet));
|
||||
write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.planet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &buffer_descriptor), // Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.planet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &image_descriptor) // Binding 1 : Color map
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), vkb::to_u32(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
void Instancing::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_BACK_BIT,
|
||||
VK_FRONT_FACE_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_TRUE,
|
||||
VK_TRUE,
|
||||
VK_COMPARE_OP_GREATER);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
vkb::to_u32(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
// Load shaders
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(
|
||||
pipeline_layout,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
pipeline_create_info.stageCount = vkb::to_u32(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
// This example uses two different input states, one for the instanced part and one for non-instanced rendering
|
||||
VkPipelineVertexInputStateCreateInfo input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
std::vector<VkVertexInputBindingDescription> binding_descriptions;
|
||||
std::vector<VkVertexInputAttributeDescription> attribute_descriptions;
|
||||
|
||||
// Vertex input bindings
|
||||
// The instancing pipeline uses a vertex input state with two bindings
|
||||
binding_descriptions = {
|
||||
// Binding point 0: Mesh vertex layout description at per-vertex rate
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
// Binding point 1: Instanced data at per-instance rate
|
||||
vkb::initializers::vertex_input_binding_description(1, sizeof(InstanceData), VK_VERTEX_INPUT_RATE_INSTANCE)};
|
||||
|
||||
// Vertex attribute bindings
|
||||
// Note that the shader declaration for per-vertex and per-instance attributes is the same, the different input rates are only stored in the bindings:
|
||||
// instanced.vert:
|
||||
// layout (location = 0) in vec3 inPos; Per-Vertex
|
||||
// ...
|
||||
// layout (location = 4) in vec3 instancePos; Per-Instance
|
||||
attribute_descriptions = {
|
||||
// Per-vertex attributes
|
||||
// These are advanced for each vertex fetched by the vertex shader
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 1: Normal
|
||||
vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 2: Texture coordinates
|
||||
// Per-Instance attributes
|
||||
// These are fetched for each instance rendered
|
||||
vkb::initializers::vertex_input_attribute_description(1, 3, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 3: Position
|
||||
vkb::initializers::vertex_input_attribute_description(1, 4, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 4: Rotation
|
||||
vkb::initializers::vertex_input_attribute_description(1, 5, VK_FORMAT_R32_SFLOAT, sizeof(float) * 6), // Location 5: Scale
|
||||
vkb::initializers::vertex_input_attribute_description(1, 6, VK_FORMAT_R32_SINT, sizeof(float) * 7), // Location 6: Texture array layer index
|
||||
};
|
||||
input_state.pVertexBindingDescriptions = binding_descriptions.data();
|
||||
input_state.pVertexAttributeDescriptions = attribute_descriptions.data();
|
||||
|
||||
pipeline_create_info.pVertexInputState = &input_state;
|
||||
|
||||
// Instancing pipeline
|
||||
shader_stages[0] = load_shader("instancing", "instancing.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("instancing", "instancing.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
// Use all input bindings and attribute descriptions
|
||||
input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(binding_descriptions.size());
|
||||
input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(attribute_descriptions.size());
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.instanced_rocks));
|
||||
|
||||
// Planet rendering pipeline
|
||||
shader_stages[0] = load_shader("instancing", "planet.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("instancing", "planet.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
// Only use the non-instanced input bindings and attribute descriptions
|
||||
input_state.vertexBindingDescriptionCount = 1;
|
||||
input_state.vertexAttributeDescriptionCount = 3;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.planet));
|
||||
|
||||
// Star field pipeline
|
||||
rasterization_state.cullMode = VK_CULL_MODE_NONE;
|
||||
depth_stencil_state.depthWriteEnable = VK_FALSE;
|
||||
depth_stencil_state.depthTestEnable = VK_FALSE;
|
||||
shader_stages[0] = load_shader("instancing", "starfield.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("instancing", "starfield.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
// Vertices are generated in the vertex shader
|
||||
input_state.vertexBindingDescriptionCount = 0;
|
||||
input_state.vertexAttributeDescriptionCount = 0;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.starfield));
|
||||
}
|
||||
|
||||
void Instancing::prepare_instance_data()
|
||||
{
|
||||
std::vector<InstanceData> instance_data;
|
||||
instance_data.resize(INSTANCE_COUNT);
|
||||
|
||||
std::default_random_engine rnd_generator(lock_simulation_speed ? 0 : static_cast<unsigned>(time(nullptr)));
|
||||
std::uniform_real_distribution<float> uniform_dist(0.0, 1.0);
|
||||
std::uniform_int_distribution<uint32_t> rnd_texture_index(0, textures.rocks.image->get_vk_image().get_array_layer_count());
|
||||
|
||||
// Distribute rocks randomly on two different rings
|
||||
for (auto i = 0; i < INSTANCE_COUNT / 2; i++)
|
||||
{
|
||||
glm::vec2 ring0{7.0f, 11.0f};
|
||||
glm::vec2 ring1{14.0f, 18.0f};
|
||||
|
||||
float rho, theta;
|
||||
|
||||
// Inner ring
|
||||
rho = sqrt((pow(ring0[1], 2.0f) - pow(ring0[0], 2.0f)) * uniform_dist(rnd_generator) + pow(ring0[0], 2.0f));
|
||||
theta = 2.0f * glm::pi<float>() * uniform_dist(rnd_generator);
|
||||
instance_data[i].pos = glm::vec3(rho * cos(theta), uniform_dist(rnd_generator) * 0.5f - 0.25f, rho * sin(theta));
|
||||
instance_data[i].rot = glm::vec3(glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator));
|
||||
instance_data[i].scale = 1.5f + uniform_dist(rnd_generator) - uniform_dist(rnd_generator);
|
||||
instance_data[i].texIndex = rnd_texture_index(rnd_generator);
|
||||
instance_data[i].scale *= 0.75f;
|
||||
|
||||
// Outer ring
|
||||
rho = sqrt((pow(ring1[1], 2.0f) - pow(ring1[0], 2.0f)) * uniform_dist(rnd_generator) + pow(ring1[0], 2.0f));
|
||||
theta = 2.0f * glm::pi<float>() * uniform_dist(rnd_generator);
|
||||
instance_data[static_cast<size_t>(i + INSTANCE_COUNT / 2)].pos = glm::vec3(rho * cos(theta), uniform_dist(rnd_generator) * 0.5f - 0.25f, rho * sin(theta));
|
||||
instance_data[static_cast<size_t>(i + INSTANCE_COUNT / 2)].rot = glm::vec3(glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator), glm::pi<float>() * uniform_dist(rnd_generator));
|
||||
instance_data[static_cast<size_t>(i + INSTANCE_COUNT / 2)].scale = 1.5f + uniform_dist(rnd_generator) - uniform_dist(rnd_generator);
|
||||
instance_data[static_cast<size_t>(i + INSTANCE_COUNT / 2)].texIndex = rnd_texture_index(rnd_generator);
|
||||
instance_data[static_cast<size_t>(i + INSTANCE_COUNT / 2)].scale *= 0.75f;
|
||||
}
|
||||
|
||||
instance_buffer.size = instance_data.size() * sizeof(InstanceData);
|
||||
|
||||
// Staging
|
||||
// Instanced data is static, copy to device local memory
|
||||
// On devices with separate memory types for host visible and device local memory this will result in better performance
|
||||
// On devices with unified memory types (DEVICE_LOCAL_BIT and HOST_VISIBLE_BIT supported at once) this isn't necessary and you could skip the staging
|
||||
|
||||
vkb::core::BufferC staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), instance_data);
|
||||
|
||||
instance_buffer.buffer = std::make_unique<vkb::core::BufferC>(get_device(), instance_buffer.size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
// Copy to staging buffer
|
||||
VkCommandBuffer copy_command = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
VkBufferCopy copy_region = {};
|
||||
copy_region.size = instance_buffer.size;
|
||||
vkCmdCopyBuffer(
|
||||
copy_command,
|
||||
staging_buffer.get_handle(),
|
||||
instance_buffer.buffer->get_handle(),
|
||||
1,
|
||||
©_region);
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
|
||||
instance_buffer.descriptor.range = instance_buffer.size;
|
||||
instance_buffer.descriptor.buffer = instance_buffer.buffer->get_handle();
|
||||
instance_buffer.descriptor.offset = 0;
|
||||
}
|
||||
|
||||
void Instancing::prepare_uniform_buffers()
|
||||
{
|
||||
uniform_buffers.scene = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_vs),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffer(0.0f);
|
||||
}
|
||||
|
||||
void Instancing::update_uniform_buffer(float delta_time)
|
||||
{
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.view = camera.matrices.view;
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
ubo_vs.loc_speed += delta_time * 0.35f;
|
||||
ubo_vs.glob_speed += delta_time * 0.01f;
|
||||
}
|
||||
|
||||
uniform_buffers.scene->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
void Instancing::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
// Submit to queue
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
|
||||
ApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
bool Instancing::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 256.0f, 0.1f);
|
||||
camera.set_rotation(glm::vec3(-17.2f, -4.7f, 0.0f));
|
||||
camera.set_translation(glm::vec3(5.5f, -1.85f, -18.5f));
|
||||
|
||||
load_assets();
|
||||
prepare_instance_data();
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_set();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Instancing::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (!paused || camera.updated)
|
||||
{
|
||||
update_uniform_buffer(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
void Instancing::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Statistics"))
|
||||
{
|
||||
drawer.text("Instances: %d", INSTANCE_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
bool Instancing::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
rebuild_command_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_instancing()
|
||||
{
|
||||
return std::make_unique<Instancing>();
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/* Copyright (c) 2019-2024, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Instanced mesh rendering, uses a separate vertex buffer for instanced data
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
# define INSTANCE_COUNT 4096
|
||||
#else
|
||||
# define INSTANCE_COUNT 8192
|
||||
#endif
|
||||
|
||||
class Instancing : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
struct Textures
|
||||
{
|
||||
Texture rocks;
|
||||
Texture planet;
|
||||
} textures;
|
||||
|
||||
struct Models
|
||||
{
|
||||
std::unique_ptr<vkb::sg::SubMesh> rock;
|
||||
std::unique_ptr<vkb::sg::SubMesh> planet;
|
||||
} models;
|
||||
|
||||
// Per-instance data block
|
||||
struct InstanceData
|
||||
{
|
||||
glm::vec3 pos;
|
||||
glm::vec3 rot;
|
||||
float scale;
|
||||
uint32_t texIndex;
|
||||
};
|
||||
// Contains the instanced data
|
||||
struct InstanceBuffer
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> buffer;
|
||||
size_t size = 0;
|
||||
VkDescriptorBufferInfo descriptor;
|
||||
} instance_buffer;
|
||||
|
||||
struct UBOVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
glm::vec4 light_pos = glm::vec4(0.0f, -5.0f, 0.0f, 1.0f);
|
||||
float loc_speed = 0.0f;
|
||||
float glob_speed = 0.0f;
|
||||
} ubo_vs;
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> scene;
|
||||
} uniform_buffers;
|
||||
|
||||
VkPipelineLayout pipeline_layout;
|
||||
struct Pipelines
|
||||
{
|
||||
VkPipeline instanced_rocks;
|
||||
VkPipeline planet;
|
||||
VkPipeline starfield;
|
||||
} pipelines;
|
||||
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
struct DescriptorSets
|
||||
{
|
||||
VkDescriptorSet instanced_rocks;
|
||||
VkDescriptorSet planet;
|
||||
} descriptor_sets;
|
||||
|
||||
Instancing();
|
||||
~Instancing();
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void build_command_buffers() override;
|
||||
void load_assets();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
void prepare_pipelines();
|
||||
void prepare_instance_data();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffer(float delta_time);
|
||||
void draw();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_instancing();
|
||||
@@ -1,35 +0,0 @@
|
||||
# Copyright (c) 2024, Google
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Google"
|
||||
NAME "Order-independent transparency (depth peeling)"
|
||||
DESCRIPTION "Order-independent transparency using depth peeling"
|
||||
SHADER_FILES_GLSL
|
||||
"oit_depth_peeling/background.frag"
|
||||
"oit_depth_peeling/combine.frag"
|
||||
"oit_depth_peeling/fullscreen.vert"
|
||||
"oit_depth_peeling/gather.frag"
|
||||
"oit_depth_peeling/gather.vert"
|
||||
"oit_depth_peeling/gather_first.frag")
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2024, Google
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Order-independent transparency with depth peeling
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/oit_depth_peeling[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
:pp: {plus}{plus}
|
||||
|
||||
image::./images/sample.png[Sample]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample implements an order-independent transparency (OIT) algorithm using depth peeling.
|
||||
It renders a single torus whose opacity can be controlled via the UI.
|
||||
It produces pixel-perfect results.
|
||||
It is based on the https://developer.download.nvidia.com/assets/gamedev/docs/OrderIndependentTransparency.pdf[original paper] from Cass Everitt.
|
||||
|
||||
== Algorithm
|
||||
|
||||
The OIT algorithm consists of several _gather_ passes followed by one _combine_ pass.
|
||||
|
||||
Each _gather_ pass renders one layer of transparent geometry.
|
||||
The first pass renders the first layer, the second pass the second layer, etc.
|
||||
The N^th^ layer consists of all the N^th^ fragments of each pixel when the fragments are ordered from front to back.
|
||||
|
||||
The _combine_ pass is a screen-space operation.
|
||||
It merges the layer images from back to front to produce the final result.
|
||||
|
||||
The algorithm can produce pixel-perfect results, even with intersecting geometry.
|
||||
When there are more geometry layers than gather passes, the backmost layers get skipped, but the visual results stay stable (i.e. no flickering pixels).
|
||||
|
||||
== Options
|
||||
|
||||
[cols="2,4,4"]
|
||||
|===
|
||||
| Option | Description | Comments
|
||||
|
||||
| Camera auto-rotation
|
||||
| Enable the automatic rotation of the camera
|
||||
|
|
||||
|
||||
| Background grayscale
|
||||
| Specify the grayscale value by which the background color is multiplied (0.0 to 1.0)
|
||||
|
|
||||
|
||||
| Object alpha
|
||||
| Specify the opacity of the transparent object (0.0 to 1.0)
|
||||
|
|
||||
|
||||
| Front layer index
|
||||
| The first layer to be rendered (0 to 7).
|
||||
|
|
||||
|
||||
| Back layer index
|
||||
| The last layer to be rendered (0 to 7).
|
||||
| This cannot be less that the front layer index.
|
||||
|
||||
|===
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 290 KiB |
@@ -1,578 +0,0 @@
|
||||
/* Copyright (c) 2024-2025, Google
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "oit_depth_peeling.h"
|
||||
#include <algorithm>
|
||||
|
||||
OITDepthPeeling::OITDepthPeeling()
|
||||
{
|
||||
}
|
||||
|
||||
OITDepthPeeling::~OITDepthPeeling()
|
||||
{
|
||||
if (!has_device())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), background_pipeline, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), combine_pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), combine_pipeline_layout, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), gather_pipeline, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), gather_first_pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), gather_pipeline_layout, nullptr);
|
||||
|
||||
vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool, VK_NULL_HANDLE);
|
||||
|
||||
destroy_sized_objects();
|
||||
|
||||
scene_constants.reset();
|
||||
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), gather_descriptor_set_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), combine_descriptor_set_layout, nullptr);
|
||||
|
||||
vkDestroySampler(get_device().get_handle(), point_sampler, VK_NULL_HANDLE);
|
||||
vkDestroySampler(get_device().get_handle(), background_texture.sampler, nullptr);
|
||||
object.reset();
|
||||
}
|
||||
|
||||
bool OITDepthPeeling::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.0f, 0.0f, -4.0f});
|
||||
camera.set_rotation({0.0f, 0.0f, 0.0f});
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 16.0f, 0.1f);
|
||||
|
||||
load_assets();
|
||||
create_samplers();
|
||||
create_constant_buffers();
|
||||
create_descriptors();
|
||||
create_sized_objects(width, height);
|
||||
create_pipelines();
|
||||
|
||||
update_scene_constants();
|
||||
update_descriptors();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OITDepthPeeling::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
destroy_sized_objects();
|
||||
create_sized_objects(width, height);
|
||||
update_descriptors();
|
||||
ApiVulkanSample::resize(width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OITDepthPeeling::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApiVulkanSample::prepare_frame();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
ApiVulkanSample::submit_frame();
|
||||
|
||||
if (camera_auto_rotation)
|
||||
{
|
||||
camera.rotate({delta_time * 5.0f, delta_time * 5.0f, 0.0f});
|
||||
}
|
||||
update_scene_constants();
|
||||
}
|
||||
|
||||
void OITDepthPeeling::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("This sample requires support for anisotropic sampling");
|
||||
}
|
||||
}
|
||||
|
||||
void OITDepthPeeling::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
drawer.checkbox("Camera auto-rotation", &camera_auto_rotation);
|
||||
drawer.slider_float("Background grayscale", &background_grayscale, kBackgroundGrayscaleMin, kBackgroundGrayscaleMax);
|
||||
drawer.slider_float("Object opacity", &object_alpha, kObjectAlphaMin, kObjectAlphaMax);
|
||||
|
||||
drawer.slider_int("Front layer index", &front_layer_index, 0, back_layer_index);
|
||||
drawer.slider_int("Back layer index", &back_layer_index, front_layer_index, kLayerMaxCount - 1);
|
||||
}
|
||||
|
||||
void OITDepthPeeling::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
{
|
||||
// Gather passes
|
||||
// Each pass renders a single transparent layer into a layer texture.
|
||||
for (uint32_t l = 0; l <= back_layer_index; ++l)
|
||||
{
|
||||
// Two depth textures are used.
|
||||
// Their roles alternates for each pass.
|
||||
// The first depth texture is used for fixed-function depth test.
|
||||
// The second one is the result of the depth test from the previous gather pass.
|
||||
// It is bound as texture and read in the shader to discard fragments from the
|
||||
// previous layers.
|
||||
VkImageSubresourceRange depth_subresource_range = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
||||
vkb::image_layout_transition(
|
||||
draw_cmd_buffers[i], depth_image[l % kDepthCount]->get_handle(),
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
l <= 1 ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||
depth_subresource_range);
|
||||
if (l > 0)
|
||||
{
|
||||
vkb::image_layout_transition(
|
||||
draw_cmd_buffers[i], depth_image[(l + 1) % kDepthCount]->get_handle(),
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL,
|
||||
depth_subresource_range);
|
||||
}
|
||||
|
||||
// Set one of the layer textures as color attachment, as the gather pass will render to it.
|
||||
VkImageSubresourceRange layer_subresource_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkb::image_layout_transition(
|
||||
draw_cmd_buffers[i], layer_image[l]->get_handle(),
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
layer_subresource_range);
|
||||
|
||||
render_pass_begin_info.framebuffer = gather_framebuffer[l];
|
||||
render_pass_begin_info.renderPass = gather_render_pass;
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
{
|
||||
// Render the geometry into the layer texture.
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, gather_pipeline_layout, 0, 1, &gather_descriptor_set[l % kDepthCount], 0, NULL);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, l == 0 ? gather_first_pipeline : gather_pipeline);
|
||||
draw_model(object, draw_cmd_buffers[i]);
|
||||
}
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
// Get the layer texture ready to be read by the combine pass.
|
||||
vkb::image_layout_transition(
|
||||
draw_cmd_buffers[i], layer_image[l]->get_handle(),
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
layer_subresource_range);
|
||||
}
|
||||
|
||||
// Combine pass
|
||||
// This pass blends all the layers into the final transparent color.
|
||||
// The final color is then alpha blended into the background.
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
{
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, combine_pipeline_layout, 0, 1, &combine_descriptor_set, 0, NULL);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, background_pipeline);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, combine_pipeline);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
}
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
}
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OITDepthPeeling::create_sized_objects(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
create_images(width, height);
|
||||
create_gather_pass_objects(width, height);
|
||||
}
|
||||
|
||||
void OITDepthPeeling::destroy_sized_objects()
|
||||
{
|
||||
for (uint32_t i = 0; i < kDepthCount; ++i)
|
||||
{
|
||||
depth_image_view[i].reset();
|
||||
depth_image[i].reset();
|
||||
}
|
||||
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
|
||||
{
|
||||
vkDestroyFramebuffer(get_device().get_handle(), gather_framebuffer[i], nullptr);
|
||||
layer_image_view[i].reset();
|
||||
layer_image[i].reset();
|
||||
}
|
||||
vkDestroyRenderPass(get_device().get_handle(), gather_render_pass, nullptr);
|
||||
}
|
||||
|
||||
void OITDepthPeeling::create_gather_pass_objects(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
const VkAttachmentReference color_attachment_reference = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
const VkAttachmentReference depth_attachment_reference = {1, 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;
|
||||
|
||||
VkAttachmentDescription attachment_descriptions[2] = {};
|
||||
attachment_descriptions[0].format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
attachment_descriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachment_descriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment_descriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment_descriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment_descriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachment_descriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachment_descriptions[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachment_descriptions[1].format = VK_FORMAT_D32_SFLOAT;
|
||||
attachment_descriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachment_descriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment_descriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment_descriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment_descriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachment_descriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachment_descriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkRenderPassCreateInfo render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_create_info.subpassCount = 1;
|
||||
render_pass_create_info.pSubpasses = &subpass;
|
||||
render_pass_create_info.attachmentCount = 2;
|
||||
render_pass_create_info.pAttachments = attachment_descriptions;
|
||||
VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &gather_render_pass));
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = vkb::initializers::framebuffer_create_info();
|
||||
framebuffer_create_info.width = width;
|
||||
framebuffer_create_info.height = height;
|
||||
framebuffer_create_info.layers = 1;
|
||||
framebuffer_create_info.attachmentCount = 2;
|
||||
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
|
||||
{
|
||||
framebuffer_create_info.renderPass = gather_render_pass;
|
||||
const VkImageView attachments[] = {
|
||||
layer_image_view[i]->get_handle(),
|
||||
depth_image_view[i % kDepthCount]->get_handle(),
|
||||
};
|
||||
framebuffer_create_info.pAttachments = attachments;
|
||||
VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, gather_framebuffer + i));
|
||||
}
|
||||
}
|
||||
|
||||
void OITDepthPeeling::create_images(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
const VkExtent3D image_extent = {width, height, 1};
|
||||
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
|
||||
{
|
||||
layer_image[i] = std::make_unique<vkb::core::Image>(get_device(), image_extent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VMA_MEMORY_USAGE_GPU_ONLY, VK_SAMPLE_COUNT_1_BIT);
|
||||
layer_image_view[i] = std::make_unique<vkb::core::ImageView>(*layer_image[i], VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < kDepthCount; ++i)
|
||||
{
|
||||
depth_image[i] = std::make_unique<vkb::core::Image>(get_device(), image_extent, VK_FORMAT_D32_SFLOAT, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VMA_MEMORY_USAGE_GPU_ONLY, VK_SAMPLE_COUNT_1_BIT);
|
||||
depth_image_view[i] = std::make_unique<vkb::core::ImageView>(*depth_image[i], VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_D32_SFLOAT);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OITDepthPeeling::load_assets()
|
||||
{
|
||||
object = load_model("scenes/torusknot.gltf");
|
||||
background_texture = load_texture("textures/vulkan_logo_full.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
void OITDepthPeeling::create_samplers()
|
||||
{
|
||||
VkSamplerCreateInfo sampler_info = vkb::initializers::sampler_create_info();
|
||||
sampler_info.magFilter = VK_FILTER_NEAREST;
|
||||
sampler_info.minFilter = VK_FILTER_NEAREST;
|
||||
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler_info.mipLodBias = 0.0f;
|
||||
sampler_info.maxAnisotropy = 1.0f;
|
||||
sampler_info.minLod = 0.0f;
|
||||
sampler_info.maxLod = 1.0f;
|
||||
sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_info, nullptr, &point_sampler));
|
||||
}
|
||||
|
||||
void OITDepthPeeling::create_constant_buffers()
|
||||
{
|
||||
scene_constants = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(SceneConstants), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
|
||||
void OITDepthPeeling::create_descriptors()
|
||||
{
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
|
||||
};
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &gather_descriptor_set_layout));
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2, kLayerMaxCount),
|
||||
};
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &combine_descriptor_set_layout));
|
||||
}
|
||||
|
||||
{
|
||||
const uint32_t num_gather_pass_combined_image_sampler = kDepthCount;
|
||||
const uint32_t num_gather_pass_uniform_buffer = kDepthCount;
|
||||
|
||||
const uint32_t num_combine_pass_combined_image_sampler = kLayerMaxCount + 1;
|
||||
const uint32_t num_combine_pass_uniform_buffer = 1;
|
||||
|
||||
const uint32_t num_uniform_buffer_descriptors = num_gather_pass_uniform_buffer + num_combine_pass_uniform_buffer;
|
||||
const uint32_t num_combined_image_sampler_descriptors = num_gather_pass_combined_image_sampler + num_combine_pass_combined_image_sampler;
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, num_uniform_buffer_descriptors),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, num_combined_image_sampler_descriptors),
|
||||
};
|
||||
const uint32_t num_gather_descriptor_sets = 2;
|
||||
const uint32_t num_combine_descriptor_sets = 1;
|
||||
const uint32_t num_descriptor_sets = num_gather_descriptor_sets + num_combine_descriptor_sets;
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info = vkb::initializers::descriptor_pool_create_info(static_cast<uint32_t>(pool_sizes.size()), pool_sizes.data(), num_descriptor_sets);
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
{
|
||||
const VkDescriptorSetLayout layouts[kDepthCount] = {gather_descriptor_set_layout, gather_descriptor_set_layout};
|
||||
VkDescriptorSetAllocateInfo alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, layouts, kDepthCount);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, gather_descriptor_set));
|
||||
}
|
||||
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &combine_descriptor_set_layout, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &combine_descriptor_set));
|
||||
}
|
||||
}
|
||||
|
||||
void OITDepthPeeling::update_descriptors()
|
||||
{
|
||||
VkDescriptorBufferInfo scene_constants_descriptor = create_descriptor(*scene_constants);
|
||||
|
||||
for (uint32_t i = 0; i < kDepthCount; ++i)
|
||||
{
|
||||
VkDescriptorImageInfo depth_texture_descriptor = {};
|
||||
depth_texture_descriptor.sampler = point_sampler;
|
||||
depth_texture_descriptor.imageView = depth_image_view[(i + 1) % kDepthCount]->get_handle();
|
||||
depth_texture_descriptor.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(gather_descriptor_set[i], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &scene_constants_descriptor),
|
||||
vkb::initializers::write_descriptor_set(gather_descriptor_set[i], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &depth_texture_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets(3);
|
||||
|
||||
write_descriptor_sets[0] = vkb::initializers::write_descriptor_set(combine_descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &scene_constants_descriptor);
|
||||
|
||||
VkDescriptorImageInfo background_texture_descriptor = create_descriptor(background_texture);
|
||||
write_descriptor_sets[1] = vkb::initializers::write_descriptor_set(combine_descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &background_texture_descriptor);
|
||||
|
||||
VkDescriptorImageInfo layer_texture_descriptor[kLayerMaxCount];
|
||||
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
|
||||
{
|
||||
layer_texture_descriptor[i].sampler = point_sampler;
|
||||
layer_texture_descriptor[i].imageView = layer_image_view[i]->get_handle();
|
||||
layer_texture_descriptor[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
write_descriptor_sets[2] = vkb::initializers::write_descriptor_set(combine_descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, layer_texture_descriptor, kLayerMaxCount);
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void OITDepthPeeling::create_pipelines()
|
||||
{
|
||||
{
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&gather_descriptor_set_layout, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &gather_pipeline_layout));
|
||||
}
|
||||
{
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&combine_descriptor_set_layout, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &combine_pipeline_layout));
|
||||
}
|
||||
|
||||
{
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state = vkb::initializers::pipeline_color_blend_attachment_state(0xF, VK_FALSE);
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state = vkb::initializers::pipeline_color_blend_state_create_info(1, &blend_attachment_state);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT, 0);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state = vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_TRUE, VK_TRUE, VK_COMPARE_OP_GREATER);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamicState = vkb::initializers::pipeline_dynamic_state_create_info(dynamic_state_enables.data(), static_cast<uint32_t>(dynamic_state_enables.size()), 0);
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info = vkb::initializers::pipeline_create_info(gather_pipeline_layout, gather_render_pass, 0);
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamicState;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
{
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)),
|
||||
};
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
shader_stages[0] = load_shader("oit_depth_peeling/gather.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("oit_depth_peeling/gather_first.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &gather_first_pipeline));
|
||||
|
||||
shader_stages[1] = load_shader("oit_depth_peeling/gather.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &gather_pipeline));
|
||||
}
|
||||
|
||||
pipeline_create_info.layout = combine_pipeline_layout;
|
||||
|
||||
vertex_input_state.vertexBindingDescriptionCount = 0;
|
||||
vertex_input_state.pVertexBindingDescriptions = nullptr;
|
||||
vertex_input_state.vertexAttributeDescriptionCount = 0;
|
||||
vertex_input_state.pVertexAttributeDescriptions = nullptr;
|
||||
|
||||
depth_stencil_state = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_FALSE, VK_FALSE, VK_COMPARE_OP_GREATER);
|
||||
|
||||
pipeline_create_info.renderPass = render_pass;
|
||||
|
||||
{
|
||||
shader_stages[0] = load_shader("oit_depth_peeling/fullscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("oit_depth_peeling/background.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &background_pipeline));
|
||||
}
|
||||
|
||||
{
|
||||
blend_attachment_state.blendEnable = VK_TRUE;
|
||||
blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
|
||||
shader_stages[0] = load_shader("oit_depth_peeling/fullscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("oit_depth_peeling/combine.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &combine_pipeline));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OITDepthPeeling::update_scene_constants()
|
||||
{
|
||||
SceneConstants constants = {};
|
||||
constants.model_view_projection = camera.matrices.perspective * camera.matrices.view * glm::scale(glm::mat4(1.0f), glm::vec3(0.08));
|
||||
constants.background_grayscale = background_grayscale;
|
||||
constants.object_alpha = object_alpha;
|
||||
constants.front_layer_index = front_layer_index;
|
||||
constants.back_layer_index = back_layer_index;
|
||||
scene_constants->convert_and_update(constants);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_oit_depth_peeling()
|
||||
{
|
||||
return std::make_unique<OITDepthPeeling>();
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/* Copyright (c) 2024, Google
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include "rendering/render_pipeline.h"
|
||||
#include "scene_graph/components/camera.h"
|
||||
|
||||
class OITDepthPeeling : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
OITDepthPeeling();
|
||||
~OITDepthPeeling();
|
||||
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
|
||||
private:
|
||||
void create_sized_objects(const uint32_t width, const uint32_t height);
|
||||
void destroy_sized_objects();
|
||||
void create_gather_pass_objects(const uint32_t width, const uint32_t height);
|
||||
void create_images(const uint32_t width, const uint32_t height);
|
||||
|
||||
void load_assets();
|
||||
void create_samplers();
|
||||
void create_constant_buffers();
|
||||
void create_descriptors();
|
||||
void create_pipelines();
|
||||
|
||||
void update_descriptors();
|
||||
void update_scene_constants();
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kLayerMaxCount = 8;
|
||||
|
||||
static constexpr uint32_t kDepthCount = 2;
|
||||
|
||||
static constexpr float kBackgroundGrayscaleMin = 0.0f;
|
||||
static constexpr float kBackgroundGrayscaleMax = 1.0f;
|
||||
|
||||
static constexpr float kObjectAlphaMin = 0.0f;
|
||||
static constexpr float kObjectAlphaMax = 1.0f;
|
||||
|
||||
struct SceneConstants
|
||||
{
|
||||
glm::mat4 model_view_projection;
|
||||
glm::f32 background_grayscale;
|
||||
glm::f32 object_alpha;
|
||||
glm::int32 front_layer_index;
|
||||
glm::int32 back_layer_index;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr<vkb::sg::SubMesh> object;
|
||||
Texture background_texture;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> scene_constants;
|
||||
|
||||
VkSampler point_sampler = VK_NULL_HANDLE;
|
||||
|
||||
std::unique_ptr<vkb::core::Image> layer_image[kLayerMaxCount];
|
||||
std::unique_ptr<vkb::core::ImageView> layer_image_view[kLayerMaxCount];
|
||||
|
||||
std::unique_ptr<vkb::core::Image> depth_image[kDepthCount];
|
||||
std::unique_ptr<vkb::core::ImageView> depth_image_view[kDepthCount];
|
||||
|
||||
VkRenderPass gather_render_pass = VK_NULL_HANDLE;
|
||||
VkFramebuffer gather_framebuffer[kLayerMaxCount] = {};
|
||||
|
||||
VkDescriptorSetLayout gather_descriptor_set_layout = VK_NULL_HANDLE;
|
||||
VkDescriptorSet gather_descriptor_set[kDepthCount] = {};
|
||||
VkDescriptorSetLayout combine_descriptor_set_layout = VK_NULL_HANDLE;
|
||||
VkDescriptorSet combine_descriptor_set = VK_NULL_HANDLE;
|
||||
|
||||
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
|
||||
|
||||
VkPipelineLayout gather_pipeline_layout = VK_NULL_HANDLE;
|
||||
VkPipeline gather_first_pipeline = VK_NULL_HANDLE;
|
||||
VkPipeline gather_pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout combine_pipeline_layout = VK_NULL_HANDLE;
|
||||
VkPipeline combine_pipeline = VK_NULL_HANDLE;
|
||||
VkPipeline background_pipeline = VK_NULL_HANDLE;
|
||||
|
||||
int32_t camera_auto_rotation = false;
|
||||
float background_grayscale = 0.3f;
|
||||
float object_alpha = 0.5f;
|
||||
int32_t front_layer_index = 0;
|
||||
int32_t back_layer_index = kLayerMaxCount - 1;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_oit_depth_peeling();
|
||||
@@ -1,35 +0,0 @@
|
||||
# Copyright (c) 2023-2024, Google
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Google"
|
||||
NAME "Order-independent transparency (ordered linked lists)"
|
||||
DESCRIPTION "Order-independent transparency using per-pixel ordered linked lists"
|
||||
SHADER_FILES_GLSL
|
||||
"oit_linked_lists/background.frag"
|
||||
"oit_linked_lists/combine.frag"
|
||||
"oit_linked_lists/combine.vert"
|
||||
"oit_linked_lists/fullscreen.vert"
|
||||
"oit_linked_lists/gather.frag"
|
||||
"oit_linked_lists/gather.vert")
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, Google
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Order-independent transparency with per-pixel ordered linked lists
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/oit_linked_lists[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
:pp: {plus}{plus}
|
||||
|
||||
image::./images/sample.png[Sample]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample implements an order-independent transparency (OIT) algorithm using per-pixel ordered linked lists.
|
||||
It renders 64 spheres with random color and opacity (from 0.2 to 1.0).
|
||||
It produces pixel-perfect results.
|
||||
|
||||
== Algorithm
|
||||
|
||||
The OIT algorithm consists of two passes: the _gather_ pass and the _combine_ pass.
|
||||
|
||||
During the gather pass, the transparent geometry is rendered into per-pixel order linked lists.
|
||||
Each fragment color and depth is pushed into the linked list associated with its destination pixel.
|
||||
The linked lists head are stored into a storage image that is the size of the screen.
|
||||
The fragment data (color and depth) is stored into a storage buffer shared by all linked lists.
|
||||
|
||||
The _combine_ pass is a screen-space operation.
|
||||
For each pixel, it sorts the fragments stored in the linked list of that pixel.
|
||||
It then alpha blends (in the shader code) them to produce the final transparent color and coverage.
|
||||
Finally, it alpha blends (via the fixed blend function) the transparent color into the backbuffer.
|
||||
|
||||
The algorithm can produce pixel-perfect results, even with intersecting geometry.
|
||||
However, there is a catch.
|
||||
To keep performance high, the maximum number of sorted fragments per-pixel is limited to 16.
|
||||
For more than 16 fragments, the algorithm does its best effort to blend the extra fragments, but the results might be inaccurate.
|
||||
This is well enough for the sample, due to the way the objects are placed in the scene.
|
||||
In general, there is a trade-off between performance and correctness.
|
||||
To keep occupancy high, the maximum number of sorted fragments (`SORTED_FRAGMENT_MAX_COUNT` in `combine.frag`) should be kept low.
|
||||
To get correct results in every situation, that same number should be as high as possible.
|
||||
The artifacts resulting from a low number of sorted fragments per pixel can be observed by using the `Sorted fragments per pixel` option.
|
||||
|
||||
== Options
|
||||
|
||||
[cols="2,4,4"]
|
||||
|===
|
||||
| Option | Description | Comments
|
||||
|
||||
| Sort fragments
|
||||
| Enable fragment sorting in the combine pass
|
||||
| This option, when disabled, is meant to demonstrate the visual issues that occur with non-sorted transparent geometry.
|
||||
|
||||
| Camera auto-rotation
|
||||
| Enable the automatic rotation of the camera
|
||||
|
|
||||
|
||||
| Sorted fragments per pixel
|
||||
| Specify the maximum number of fragments sorted per pixel
|
||||
| This option, when set to a low number (e.g. 4), highlights the main weakness of the algorithm.
|
||||
|
||||
| Background grayscale
|
||||
| Specify the grayscale value by which the background color is multiplied (0.0 to 1.0)
|
||||
|
|
||||
|===
|
||||
|
||||
== Tests
|
||||
|
||||
This sample was tested on Windows.
|
||||
The validation layers were enabled and all reported issues were fixed.
|
||||
The sample was also tested on Linux during development.
|
||||
Both systems featured an AMD GPU.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 194 KiB |
@@ -1,524 +0,0 @@
|
||||
/* Copyright (c) 2023-2025, Google
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "oit_linked_lists.h"
|
||||
#include <algorithm>
|
||||
|
||||
OITLinkedLists::OITLinkedLists()
|
||||
{
|
||||
}
|
||||
|
||||
OITLinkedLists::~OITLinkedLists()
|
||||
{
|
||||
if (!has_device())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), combine_pipeline, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), background_pipeline, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), gather_pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
|
||||
vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool, VK_NULL_HANDLE);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
|
||||
destroy_sized_objects();
|
||||
|
||||
instance_data.reset();
|
||||
scene_constants.reset();
|
||||
|
||||
vkDestroySampler(get_device().get_handle(), background_texture.sampler, nullptr);
|
||||
object.reset();
|
||||
}
|
||||
|
||||
bool OITLinkedLists::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.0f, 0.0f, -4.0f});
|
||||
camera.set_rotation({0.0f, 0.0f, 0.0f});
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 256.0f, 0.1f);
|
||||
|
||||
load_assets();
|
||||
create_constant_buffers();
|
||||
create_descriptors();
|
||||
create_sized_objects(width, height);
|
||||
create_pipelines();
|
||||
|
||||
update_scene_constants();
|
||||
fill_instance_data();
|
||||
update_descriptors();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OITLinkedLists::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
if ((width != this->width) || (height != this->height))
|
||||
{
|
||||
destroy_sized_objects();
|
||||
create_sized_objects(width, height);
|
||||
update_descriptors();
|
||||
}
|
||||
ApiVulkanSample::resize(width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OITLinkedLists::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApiVulkanSample::prepare_frame();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
ApiVulkanSample::submit_frame();
|
||||
|
||||
if (camera_auto_rotation)
|
||||
{
|
||||
camera.rotate({delta_time * 5.0f, delta_time * 5.0f, 0.0f});
|
||||
}
|
||||
update_scene_constants();
|
||||
}
|
||||
|
||||
void OITLinkedLists::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
if (gpu.get_features().fragmentStoresAndAtomics)
|
||||
{
|
||||
gpu.get_mutable_requested_features().fragmentStoresAndAtomics = VK_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("This sample requires support for buffers and images stores and atomic operations in the fragment shader stage");
|
||||
}
|
||||
}
|
||||
|
||||
void OITLinkedLists::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
drawer.checkbox("Sort fragments", &sort_fragments);
|
||||
drawer.checkbox("Camera auto-rotation", &camera_auto_rotation);
|
||||
drawer.slider_int("Sorted fragments per pixel", &sorted_fragment_count, kSortedFragmentMinCount, kSortedFragmentMaxCount);
|
||||
drawer.slider_float("Background grayscale", &background_grayscale, kBackgroundGrayscaleMin, kBackgroundGrayscaleMax);
|
||||
}
|
||||
|
||||
void OITLinkedLists::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
{
|
||||
// Gather pass
|
||||
{
|
||||
render_pass_begin_info.framebuffer = gather_framebuffer;
|
||||
render_pass_begin_info.renderPass = gather_render_pass;
|
||||
render_pass_begin_info.clearValueCount = 0;
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
{
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, NULL);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, gather_pipeline);
|
||||
draw_model(object, draw_cmd_buffers[i], kInstanceCount);
|
||||
}
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
VkImageSubresourceRange subresource_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkb::image_layout_transition(
|
||||
draw_cmd_buffers[i], linked_list_head_image->get_handle(),
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
|
||||
subresource_range);
|
||||
|
||||
// Combine pass
|
||||
{
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
{
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, NULL);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, background_pipeline);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, combine_pipeline);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
}
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
}
|
||||
}
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OITLinkedLists::create_sized_objects(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
create_gather_pass_objects(width, height);
|
||||
create_fragment_resources(width, height);
|
||||
clear_sized_resources();
|
||||
}
|
||||
|
||||
void OITLinkedLists::destroy_sized_objects()
|
||||
{
|
||||
vkDestroyFramebuffer(get_device().get_handle(), gather_framebuffer, nullptr);
|
||||
vkDestroyRenderPass(get_device().get_handle(), gather_render_pass, nullptr);
|
||||
|
||||
fragment_counter.reset();
|
||||
fragment_buffer.reset();
|
||||
fragment_max_count = 0;
|
||||
linked_list_head_image_view.reset();
|
||||
linked_list_head_image.reset();
|
||||
}
|
||||
|
||||
void OITLinkedLists::create_gather_pass_objects(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
VkSubpassDescription subpass = {};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
|
||||
VkRenderPassCreateInfo render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_create_info.subpassCount = 1;
|
||||
render_pass_create_info.pSubpasses = &subpass;
|
||||
|
||||
VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &gather_render_pass));
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = vkb::initializers::framebuffer_create_info();
|
||||
framebuffer_create_info.renderPass = gather_render_pass;
|
||||
framebuffer_create_info.width = width;
|
||||
framebuffer_create_info.height = height;
|
||||
framebuffer_create_info.layers = 1;
|
||||
|
||||
VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, &gather_framebuffer));
|
||||
}
|
||||
|
||||
void OITLinkedLists::create_fragment_resources(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
{
|
||||
const VkExtent3D image_extent = {width, height, 1};
|
||||
linked_list_head_image = std::make_unique<vkb::core::Image>(get_device(), image_extent, VK_FORMAT_R32_UINT, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY, VK_SAMPLE_COUNT_1_BIT);
|
||||
linked_list_head_image_view = std::make_unique<vkb::core::ImageView>(*linked_list_head_image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R32_UINT);
|
||||
}
|
||||
|
||||
{
|
||||
fragment_max_count = width * height * kFragmentsPerPixelAverage;
|
||||
const uint32_t fragment_buffer_size = sizeof(glm::uvec3) * fragment_max_count;
|
||||
fragment_buffer = std::make_unique<vkb::core::BufferC>(get_device(), fragment_buffer_size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
}
|
||||
|
||||
{
|
||||
fragment_counter = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(glm::uint), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
void OITLinkedLists::clear_sized_resources()
|
||||
{
|
||||
VkCommandBuffer command_buffer;
|
||||
VkCommandBufferAllocateInfo command_buffer_allocate_info = vkb::initializers::command_buffer_allocate_info(cmd_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
|
||||
VK_CHECK(vkAllocateCommandBuffers(get_device().get_handle(), &command_buffer_allocate_info, &command_buffer));
|
||||
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
VK_CHECK(vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info));
|
||||
{
|
||||
vkCmdFillBuffer(command_buffer, fragment_counter->get_handle(), 0, sizeof(glm::uint), 0);
|
||||
|
||||
VkImageSubresourceRange subresource_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkb::image_layout_transition(
|
||||
command_buffer, linked_list_head_image->get_handle(),
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL,
|
||||
subresource_range);
|
||||
|
||||
VkClearColorValue linked_lists_clear_value;
|
||||
linked_lists_clear_value.uint32[0] = kLinkedListEndSentinel;
|
||||
linked_lists_clear_value.uint32[1] = kLinkedListEndSentinel;
|
||||
linked_lists_clear_value.uint32[2] = kLinkedListEndSentinel;
|
||||
linked_lists_clear_value.uint32[3] = kLinkedListEndSentinel;
|
||||
vkCmdClearColorImage(command_buffer, linked_list_head_image->get_handle(), VK_IMAGE_LAYOUT_GENERAL, &linked_lists_clear_value, 1, &subresource_range);
|
||||
}
|
||||
VK_CHECK(vkEndCommandBuffer(command_buffer));
|
||||
|
||||
{
|
||||
VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &command_buffer;
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
VK_CHECK(vkQueueWaitIdle(queue));
|
||||
}
|
||||
|
||||
vkFreeCommandBuffers(get_device().get_handle(), cmd_pool, 1, &command_buffer);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OITLinkedLists::load_assets()
|
||||
{
|
||||
object = load_model("scenes/geosphere.gltf");
|
||||
background_texture = load_texture("textures/vulkan_logo_full.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
void OITLinkedLists::create_constant_buffers()
|
||||
{
|
||||
scene_constants = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(SceneConstants), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
instance_data = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(Instance) * kInstanceCount, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
|
||||
void OITLinkedLists::create_descriptors()
|
||||
{
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 1),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 5),
|
||||
};
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layout));
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1),
|
||||
};
|
||||
const uint32_t num_descriptor_sets = 1;
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info = vkb::initializers::descriptor_pool_create_info(static_cast<uint32_t>(pool_sizes.size()), pool_sizes.data(), num_descriptor_sets);
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
VkDescriptorSetAllocateInfo alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layout, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_set));
|
||||
}
|
||||
|
||||
void OITLinkedLists::update_descriptors()
|
||||
{
|
||||
VkDescriptorBufferInfo scene_constants_descriptor = create_descriptor(*scene_constants);
|
||||
VkDescriptorBufferInfo instance_data_descriptor = create_descriptor(*instance_data);
|
||||
VkDescriptorImageInfo linked_list_head_image_view_descriptor = vkb::initializers::descriptor_image_info(VK_NULL_HANDLE, linked_list_head_image_view->get_handle(), VK_IMAGE_LAYOUT_GENERAL);
|
||||
VkDescriptorBufferInfo fragment_buffer_descriptor = create_descriptor(*fragment_buffer);
|
||||
VkDescriptorBufferInfo fragment_counter_descriptor = create_descriptor(*fragment_counter);
|
||||
VkDescriptorImageInfo background_texture_descriptor = create_descriptor(background_texture);
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &scene_constants_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &instance_data_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2, &linked_list_head_image_view_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &fragment_buffer_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4, &fragment_counter_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 5, &background_texture_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
void OITLinkedLists::create_pipelines()
|
||||
{
|
||||
{
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layout, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
{
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state = vkb::initializers::pipeline_color_blend_attachment_state(0xF, VK_FALSE);
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state = vkb::initializers::pipeline_color_blend_state_create_info(1, &blend_attachment_state);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT, 0);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state = vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_FALSE, VK_FALSE, VK_COMPARE_OP_GREATER);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamicState = vkb::initializers::pipeline_dynamic_state_create_info(dynamic_state_enables.data(), static_cast<uint32_t>(dynamic_state_enables.size()), 0);
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info = vkb::initializers::pipeline_create_info(pipeline_layout, render_pass, 0);
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamicState;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
{
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),
|
||||
};
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
shader_stages[0] = load_shader("oit_linked_lists/gather.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("oit_linked_lists/gather.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
pipeline_create_info.renderPass = gather_render_pass;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &gather_pipeline));
|
||||
}
|
||||
|
||||
{
|
||||
vertex_input_state.vertexBindingDescriptionCount = 0;
|
||||
vertex_input_state.pVertexBindingDescriptions = nullptr;
|
||||
vertex_input_state.vertexAttributeDescriptionCount = 0;
|
||||
vertex_input_state.pVertexAttributeDescriptions = nullptr;
|
||||
|
||||
shader_stages[0] = load_shader("oit_linked_lists/fullscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("oit_linked_lists/background.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
pipeline_create_info.renderPass = render_pass;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &background_pipeline));
|
||||
}
|
||||
|
||||
{
|
||||
vertex_input_state.vertexBindingDescriptionCount = 0;
|
||||
vertex_input_state.pVertexBindingDescriptions = nullptr;
|
||||
vertex_input_state.vertexAttributeDescriptionCount = 0;
|
||||
vertex_input_state.pVertexAttributeDescriptions = nullptr;
|
||||
|
||||
blend_attachment_state.blendEnable = VK_TRUE;
|
||||
blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
|
||||
shader_stages[0] = load_shader("oit_linked_lists/combine.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("oit_linked_lists/combine.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
pipeline_create_info.renderPass = render_pass;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &combine_pipeline));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OITLinkedLists::update_scene_constants()
|
||||
{
|
||||
SceneConstants constants = {};
|
||||
constants.projection = camera.matrices.perspective;
|
||||
constants.view = camera.matrices.view;
|
||||
constants.background_grayscale = background_grayscale;
|
||||
constants.sort_fragments = sort_fragments ? 1U : 0U;
|
||||
constants.fragment_max_count = fragment_max_count;
|
||||
constants.sorted_fragment_count = sorted_fragment_count;
|
||||
scene_constants->convert_and_update(constants);
|
||||
}
|
||||
|
||||
void OITLinkedLists::fill_instance_data()
|
||||
{
|
||||
Instance instances[kInstanceCount] = {};
|
||||
|
||||
auto get_random_float = []() {
|
||||
return static_cast<float>(rand()) / (RAND_MAX);
|
||||
};
|
||||
|
||||
for (uint32_t l = 0, instance_index = 0; l < kInstanceLayerCount; ++l)
|
||||
{
|
||||
for (uint32_t c = 0; c < kInstanceColumnCount; ++c)
|
||||
{
|
||||
for (uint32_t r = 0; r < kInstanceRowCount; ++r, ++instance_index)
|
||||
{
|
||||
const float x = static_cast<float>(r) - ((kInstanceRowCount - 1) * 0.5f);
|
||||
const float y = static_cast<float>(c) - ((kInstanceColumnCount - 1) * 0.5f);
|
||||
const float z = static_cast<float>(l) - ((kInstanceLayerCount - 1) * 0.5f);
|
||||
const float scale = 0.02f;
|
||||
instances[instance_index].model =
|
||||
glm::scale(
|
||||
glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z)),
|
||||
glm::vec3(scale));
|
||||
|
||||
instances[instance_index].color.r = get_random_float();
|
||||
instances[instance_index].color.g = get_random_float();
|
||||
instances[instance_index].color.b = get_random_float();
|
||||
instances[instance_index].color.a = get_random_float() * 0.8f + 0.2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance_data->convert_and_update(instances);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_oit_linked_lists()
|
||||
{
|
||||
return std::make_unique<OITLinkedLists>();
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, Google
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include "rendering/render_pipeline.h"
|
||||
#include "scene_graph/components/camera.h"
|
||||
|
||||
class OITLinkedLists : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
OITLinkedLists();
|
||||
~OITLinkedLists();
|
||||
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
|
||||
private:
|
||||
void create_sized_objects(const uint32_t width, const uint32_t height);
|
||||
void destroy_sized_objects();
|
||||
void create_gather_pass_objects(const uint32_t width, const uint32_t height);
|
||||
void create_fragment_resources(const uint32_t width, const uint32_t height);
|
||||
void clear_sized_resources();
|
||||
|
||||
void load_assets();
|
||||
void create_constant_buffers();
|
||||
void create_descriptors();
|
||||
void create_pipelines();
|
||||
|
||||
void update_descriptors();
|
||||
void update_scene_constants();
|
||||
void fill_instance_data();
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kInstanceRowCount = 4;
|
||||
static constexpr uint32_t kInstanceColumnCount = 4;
|
||||
static constexpr uint32_t kInstanceLayerCount = 4;
|
||||
static constexpr uint32_t kInstanceCount = kInstanceRowCount * kInstanceColumnCount * kInstanceLayerCount;
|
||||
|
||||
static constexpr uint32_t kFragmentsPerPixelAverage = 8;
|
||||
|
||||
static constexpr int32_t kSortedFragmentMinCount = 1;
|
||||
static constexpr int32_t kSortedFragmentMaxCount = 16;
|
||||
|
||||
static constexpr float kBackgroundGrayscaleMin = 0.0f;
|
||||
static constexpr float kBackgroundGrayscaleMax = 1.0f;
|
||||
|
||||
static constexpr uint32_t kLinkedListEndSentinel = 0xFFFFFFFFU;
|
||||
|
||||
struct SceneConstants
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
glm::f32 background_grayscale;
|
||||
glm::uint sort_fragments;
|
||||
glm::uint fragment_max_count;
|
||||
glm::uint sorted_fragment_count;
|
||||
};
|
||||
|
||||
struct Instance
|
||||
{
|
||||
glm::mat4 model;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr<vkb::sg::SubMesh> object;
|
||||
Texture background_texture;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> scene_constants;
|
||||
std::unique_ptr<vkb::core::BufferC> instance_data;
|
||||
|
||||
std::unique_ptr<vkb::core::Image> linked_list_head_image;
|
||||
std::unique_ptr<vkb::core::ImageView> linked_list_head_image_view;
|
||||
std::unique_ptr<vkb::core::BufferC> fragment_buffer;
|
||||
std::unique_ptr<vkb::core::BufferC> fragment_counter;
|
||||
glm::uint fragment_max_count = 0U;
|
||||
|
||||
VkRenderPass gather_render_pass = VK_NULL_HANDLE;
|
||||
VkFramebuffer gather_framebuffer = VK_NULL_HANDLE;
|
||||
|
||||
VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
||||
|
||||
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
|
||||
VkPipeline gather_pipeline = VK_NULL_HANDLE;
|
||||
VkPipeline background_pipeline = VK_NULL_HANDLE;
|
||||
VkPipeline combine_pipeline = VK_NULL_HANDLE;
|
||||
|
||||
int32_t sort_fragments = true;
|
||||
int32_t camera_auto_rotation = false;
|
||||
int32_t sorted_fragment_count = kSortedFragmentMaxCount;
|
||||
float background_grayscale = 0.3f;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_oit_linked_lists();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2021-2024, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Separating image and sampler"
|
||||
DESCRIPTION "Displays a texture with a separated image and sampler"
|
||||
SHADER_FILES_GLSL
|
||||
"separate_image_sampler/glsl/separate_image_sampler.vert"
|
||||
"separate_image_sampler/glsl/separate_image_sampler.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"separate_image_sampler/hlsl/separate_image_sampler.vert.hlsl"
|
||||
"separate_image_sampler/hlsl/separate_image_sampler.frag.hlsl")
|
||||
@@ -1,159 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2021-2023, Sascha Willems
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
= Separating samplers and images
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/separate_image_sampler[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
This tutorial, along with the accompanying example code, shows how to separate samplers and images in a Vulkan application.
|
||||
Opposite to combined image and samplers, this allows the application to freely mix an arbitrary set of samplers and images in the shader.
|
||||
|
||||
In the sample code, a single image and multiple samplers with different options will be created.
|
||||
The sampler to be used for sampling the image can then be selected at runtime.
|
||||
As image and sampler objects are separated, this only requires selecting a different descriptor at runtime.
|
||||
|
||||
== In the application
|
||||
|
||||
From the application's point of view, images and samplers are always created separately.
|
||||
Access to the image is done via the image's `VkImageView`.
|
||||
Samplers are created using a `VkSampler` object, specifying how an image will be sampled.
|
||||
|
||||
The difference between separating and combining them starts at the descriptor level, which defines how the shader accesses the samplers and images.
|
||||
|
||||
A separate setup uses a descriptor of type `VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE` for the sampled image, and a `VK_DESCRIPTOR_TYPE_SAMPLER` for the sampler, separating the image and sampler object:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Image info only references the image
|
||||
VkDescriptorImageInfo image_info{};
|
||||
image_info.imageView = texture.image->get_vk_image_view().get_handle();
|
||||
image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
// Sampled image descriptor
|
||||
VkWriteDescriptorSet image_write_descriptor_set{};
|
||||
image_write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
image_write_descriptor_set.dstSet = base_descriptor_set;
|
||||
image_write_descriptor_set.dstBinding = 1;
|
||||
image_write_descriptor_set.descriptorCount = 1;
|
||||
image_write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
||||
image_write_descriptor_set.pImageInfo = &image_info;
|
||||
|
||||
// One set for the sampled image
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
...
|
||||
// Binding 1 : Fragment shader sampled image
|
||||
image_write_descriptor_set};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
----
|
||||
|
||||
For this sample, we then create two samplers with different filtering options:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Sets for each of the sampler
|
||||
descriptor_set_alloc_info.pSetLayouts = &sampler_descriptor_set_layout;
|
||||
for (size_t i = 0; i < sampler_descriptor_sets.size(); i++)
|
||||
{
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_alloc_info, &sampler_descriptor_sets[i]));
|
||||
|
||||
// Descriptor info only references the sampler
|
||||
VkDescriptorImageInfo sampler_info{};
|
||||
sampler_info.sampler = samplers[i];
|
||||
|
||||
VkWriteDescriptorSet sampler_write_descriptor_set{};
|
||||
sampler_write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
sampler_write_descriptor_set.dstSet = sampler_descriptor_sets[i];
|
||||
sampler_write_descriptor_set.dstBinding = 0;
|
||||
sampler_write_descriptor_set.descriptorCount = 1;
|
||||
sampler_write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
|
||||
sampler_write_descriptor_set.pImageInfo = &sampler_info;
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), 1, &sampler_write_descriptor_set, 0, nullptr);
|
||||
}
|
||||
----
|
||||
|
||||
At draw-time, the descriptor containing the sampled image is bound to set 0 and the descriptor for the currently selected sampler is bound to set 1:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Base descriptor with the image to be sampled in set 0
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &base_descriptor_set, 0, nullptr);
|
||||
// Descriptor for the selected sampler in set 1
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 1, 1, &sampler_descriptor_sets[selected_sampler], 0, nullptr);
|
||||
...
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], index_count, 1, 0, 0, 0);
|
||||
----
|
||||
|
||||
== In the shader
|
||||
|
||||
With the above setup, the shader interface for the fragment shader also separates the sampler and image as two distinct uniforms:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout (set = 0, binding = 1) uniform texture2D _texture;
|
||||
layout (set = 1, binding = 0) uniform sampler _sampler;
|
||||
----
|
||||
|
||||
To sample from the image referenced by `_texture`, with the currently set sampler in '_sampler', we create a sampled image in the fragment shader at runtime using the `sampler2D` function.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(sampler2D(_texture, _sampler), inUV);
|
||||
}
|
||||
----
|
||||
|
||||
== Comparison with combined image samplers
|
||||
|
||||
For reference, a combined image and sampler setup would differ for both the application and the shader.
|
||||
The app would use a single descriptor of type `VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`, and set both image and sampler related values in the descriptor:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Descriptor info references image and sampler
|
||||
VkDescriptorImageInfo image_info;
|
||||
image_info.imageView = texture.view;
|
||||
image_info.sampler = texture.sampler;
|
||||
image_info.imageLayout = texture.image_layout;
|
||||
|
||||
VkWriteDescriptorSet image_write_descriptor_set{};
|
||||
image_write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
image_write_descriptor_set.dstSet = descriptor_set;
|
||||
image_write_descriptor_set.dstBinding = 0;
|
||||
image_write_descriptor_set.descriptorCount = 1;
|
||||
image_write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
image_write_descriptor_set.pImageInfo = &image_info;
|
||||
----
|
||||
|
||||
The shader interface only uses one uniform for accessing the combined image and sampler and also doesn't construct a `sampler2D` at runtime:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout (binding = 1) uniform sampler2D _combined_image;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(_combined_image, inUV);
|
||||
}
|
||||
----
|
||||
|
||||
Compared to the separated setup, changing a sampler in this setup would either require creating multiple descriptors with each image/sampler combination or rebuilding the descriptor.
|
||||
@@ -1,495 +0,0 @@
|
||||
/* Copyright (c) 2021-2025, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Separate samplers and image to draw a single image with different sampling options
|
||||
*/
|
||||
|
||||
#include "separate_image_sampler.h"
|
||||
|
||||
SeparateImageSampler::SeparateImageSampler()
|
||||
{
|
||||
zoom = -0.5f;
|
||||
rotation = {45.0f, 0.0f, 0.0f};
|
||||
title = "Separate sampler and image";
|
||||
}
|
||||
|
||||
SeparateImageSampler::~SeparateImageSampler()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), base_descriptor_set_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), sampler_descriptor_set_layout, nullptr);
|
||||
for (VkSampler sampler : samplers)
|
||||
{
|
||||
vkDestroySampler(get_device().get_handle(), sampler, nullptr);
|
||||
}
|
||||
// Delete the implicitly created sampler for the texture loaded via the framework
|
||||
vkDestroySampler(get_device().get_handle(), texture.sampler, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable physical device features required for this example
|
||||
void SeparateImageSampler::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void SeparateImageSampler::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = default_clear_color;
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
// Set target frame buffer
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(static_cast<int32_t>(width), static_cast<int32_t>(height), 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
// Bind the uniform buffer and sampled image to set 0
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &base_descriptor_set, 0, nullptr);
|
||||
// Bind the selected sampler to set 1
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 1, 1, &sampler_descriptor_sets[selected_sampler], 0, nullptr);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer->get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], index_count, 1, 0, 0, 0);
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void SeparateImageSampler::setup_samplers()
|
||||
{
|
||||
// Create two samplers with different options
|
||||
VkSamplerCreateInfo samplerCI = vkb::initializers::sampler_create_info();
|
||||
samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerCI.mipLodBias = 0.0f;
|
||||
samplerCI.compareOp = VK_COMPARE_OP_NEVER;
|
||||
samplerCI.minLod = 0.0f;
|
||||
samplerCI.maxLod = static_cast<float>(texture.image->get_mipmaps().size());
|
||||
if (get_device().get_gpu().get_features().samplerAnisotropy)
|
||||
{
|
||||
// Use max. level of anisotropy for this example
|
||||
samplerCI.maxAnisotropy = get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy;
|
||||
samplerCI.anisotropyEnable = VK_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The device does not support anisotropic filtering
|
||||
samplerCI.maxAnisotropy = 1.0;
|
||||
samplerCI.anisotropyEnable = VK_FALSE;
|
||||
}
|
||||
samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
|
||||
// First sampler with linear filtering
|
||||
samplerCI.magFilter = VK_FILTER_LINEAR;
|
||||
samplerCI.minFilter = VK_FILTER_LINEAR;
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &samplerCI, nullptr, &samplers[0]));
|
||||
|
||||
// Second sampler with nearest filtering
|
||||
samplerCI.magFilter = VK_FILTER_NEAREST;
|
||||
samplerCI.minFilter = VK_FILTER_NEAREST;
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &samplerCI, nullptr, &samplers[1]));
|
||||
}
|
||||
|
||||
void SeparateImageSampler::load_assets()
|
||||
{
|
||||
texture = load_texture("textures/metalplate01_rgba.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
void SeparateImageSampler::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
// Submit to queue
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
|
||||
ApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void SeparateImageSampler::generate_quad()
|
||||
{
|
||||
// Setup vertices for a single uv-mapped quad made from two triangles
|
||||
std::vector<VertexStructure> vertices =
|
||||
{
|
||||
{{1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}};
|
||||
|
||||
// Setup indices
|
||||
std::vector<uint32_t> indices = {0, 1, 2, 2, 3, 0};
|
||||
index_count = static_cast<uint32_t>(indices.size());
|
||||
|
||||
auto vertex_buffer_size = vkb::to_u32(vertices.size() * sizeof(VertexStructure));
|
||||
auto index_buffer_size = vkb::to_u32(indices.size() * sizeof(uint32_t));
|
||||
|
||||
// Create buffers
|
||||
// For the sake of simplicity we won't stage the vertex data to the gpu memory
|
||||
// Vertex buffer
|
||||
vertex_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
vertex_buffer_size,
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
vertex_buffer->update(vertices.data(), vertex_buffer_size);
|
||||
|
||||
index_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
index_buffer_size,
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
index_buffer->update(indices.data(), index_buffer_size);
|
||||
}
|
||||
|
||||
void SeparateImageSampler::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_SAMPLER, 2)};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
3);
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void SeparateImageSampler::setup_descriptor_set_layout()
|
||||
{
|
||||
// We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info{};
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings{};
|
||||
|
||||
// Set layout for the uniform buffer and the image
|
||||
set_layout_bindings = {
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0),
|
||||
// Binding 1 : Fragment shader sampled image
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
1)};
|
||||
descriptor_layout_create_info =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &base_descriptor_set_layout));
|
||||
|
||||
// Set layout for the samplers
|
||||
set_layout_bindings = {
|
||||
// Binding 0: Fragment shader sampler
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
0)};
|
||||
descriptor_layout_create_info =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &sampler_descriptor_set_layout));
|
||||
|
||||
// Pipeline layout
|
||||
// Set layout for the base descriptors in set 0 and set layout for the sampler descriptors in set 1
|
||||
std::vector<VkDescriptorSetLayout> set_layouts = {base_descriptor_set_layout, sampler_descriptor_set_layout};
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
set_layouts.data(),
|
||||
static_cast<uint32_t>(set_layouts.size()));
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void SeparateImageSampler::setup_descriptor_set()
|
||||
{
|
||||
// We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former
|
||||
VkDescriptorSetAllocateInfo descriptor_set_alloc_info{};
|
||||
|
||||
// Descriptors set for the uniform buffer and the image
|
||||
descriptor_set_alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&base_descriptor_set_layout,
|
||||
1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_alloc_info, &base_descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*uniform_buffer_vs);
|
||||
|
||||
// Image info only references the image
|
||||
VkDescriptorImageInfo image_info{};
|
||||
image_info.imageView = texture.image->get_vk_image_view().get_handle();
|
||||
image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
// Sampled image descriptor
|
||||
VkWriteDescriptorSet image_write_descriptor_set{};
|
||||
image_write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
image_write_descriptor_set.dstSet = base_descriptor_set;
|
||||
image_write_descriptor_set.dstBinding = 1;
|
||||
image_write_descriptor_set.descriptorCount = 1;
|
||||
image_write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
||||
image_write_descriptor_set.pImageInfo = &image_info;
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::write_descriptor_set(
|
||||
base_descriptor_set,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
0,
|
||||
&buffer_descriptor),
|
||||
// Binding 1 : Fragment shader sampled image
|
||||
image_write_descriptor_set};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
|
||||
// Sets for each of the sampler
|
||||
descriptor_set_alloc_info.pSetLayouts = &sampler_descriptor_set_layout;
|
||||
for (size_t i = 0; i < sampler_descriptor_sets.size(); i++)
|
||||
{
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_alloc_info, &sampler_descriptor_sets[i]));
|
||||
|
||||
// Descriptor info only references the sampler
|
||||
VkDescriptorImageInfo sampler_info{};
|
||||
sampler_info.sampler = samplers[i];
|
||||
|
||||
VkWriteDescriptorSet sampler_write_descriptor_set{};
|
||||
sampler_write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
sampler_write_descriptor_set.dstSet = sampler_descriptor_sets[i];
|
||||
sampler_write_descriptor_set.dstBinding = 0;
|
||||
sampler_write_descriptor_set.descriptorCount = 1;
|
||||
sampler_write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
|
||||
sampler_write_descriptor_set.pImageInfo = &sampler_info;
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), 1, &sampler_write_descriptor_set, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SeparateImageSampler::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_NONE,
|
||||
VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_TRUE,
|
||||
VK_TRUE,
|
||||
VK_COMPARE_OP_GREATER);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
// Load shaders
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
|
||||
shader_stages[0] = load_shader("separate_image_sampler", "separate_image_sampler.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("separate_image_sampler", "separate_image_sampler.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex bindings and attributes
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(VertexStructure), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VertexStructure, pos)),
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(VertexStructure, uv)),
|
||||
vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VertexStructure, normal)),
|
||||
};
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(
|
||||
pipeline_layout,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void SeparateImageSampler::prepare_uniform_buffers()
|
||||
{
|
||||
// Vertex shader uniform buffer block
|
||||
uniform_buffer_vs = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_vs),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void SeparateImageSampler::update_uniform_buffers()
|
||||
{
|
||||
// Vertex shader
|
||||
ubo_vs.projection = glm::perspective(glm::radians(60.0f), static_cast<float>(width) / static_cast<float>(height), 0.001f, 256.0f);
|
||||
glm::mat4 view_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom));
|
||||
|
||||
ubo_vs.model = view_matrix * glm::translate(glm::mat4(1.0f), camera_pos);
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
ubo_vs.view_pos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f);
|
||||
|
||||
uniform_buffer_vs->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
bool SeparateImageSampler::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
load_assets();
|
||||
generate_quad();
|
||||
prepare_uniform_buffers();
|
||||
setup_samplers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_set();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SeparateImageSampler::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
void SeparateImageSampler::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void SeparateImageSampler::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
const std::vector<std::string> sampler_names = {"Linear filtering",
|
||||
"Nearest filtering"};
|
||||
if (drawer.combo_box("Sampler", &selected_sampler, sampler_names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_separate_image_sampler()
|
||||
{
|
||||
return std::make_unique<SeparateImageSampler>();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/* Copyright (c) 2021-2024, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Separate samplers and image to draw a single image with different sampling options
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ktx.h>
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class SeparateImageSampler : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
// Vertex layout for this example
|
||||
struct VertexStructure
|
||||
{
|
||||
float pos[3];
|
||||
float uv[2];
|
||||
float normal[3];
|
||||
};
|
||||
|
||||
Texture texture;
|
||||
|
||||
std::array<VkSampler, 2> samplers{};
|
||||
int32_t selected_sampler = 0;
|
||||
std::array<VkDescriptorSet, 2> sampler_descriptor_sets{};
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer;
|
||||
uint32_t index_count;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer_vs;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
glm::vec4 view_pos;
|
||||
} ubo_vs;
|
||||
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
|
||||
VkDescriptorSet base_descriptor_set = VK_NULL_HANDLE;
|
||||
|
||||
VkDescriptorSetLayout base_descriptor_set_layout;
|
||||
VkDescriptorSetLayout sampler_descriptor_set_layout;
|
||||
|
||||
SeparateImageSampler();
|
||||
~SeparateImageSampler() override;
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void build_command_buffers() override;
|
||||
void setup_samplers();
|
||||
void load_assets();
|
||||
void draw();
|
||||
void generate_quad();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual void view_changed() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_separate_image_sampler();
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (c) 2023, Google
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Google"
|
||||
NAME "Swapchain Recreation"
|
||||
DESCRIPTION "Best practices when dealing with Vulkan swapchain recreation.")
|
||||
@@ -1,92 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023-2024, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Swapchain Recreation
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/swapchain_recreation[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
A sample that implements best practices in handling present resources and swapchain recreation, for example due to window resizing or present mode changes.
|
||||
|
||||
Before VK_EXT_swapchain_maintenance1, there is no straightforward way to tell when a semaphore associated with a present operation can be recycled, or when a retired swapchain can be destroyed.
|
||||
Both these operations depend on knowing when the presentation engine has acquired a reference to these resources as part of the present job, for which there is no indicator.
|
||||
|
||||
In this sample, a workaround is implemented where a fence signaled by vkAcquireNextImageKHR is used to determine when the _previous_ present job involving the same image index has been completed.
|
||||
This is often much later than the point where the present resources can be freed.
|
||||
|
||||
Take the following shorthand notation:
|
||||
|
||||
* PE: Presentation Engine
|
||||
* ANI: vkAcquireNextImageKHR
|
||||
* QS: vkQueueSubmit
|
||||
* QP: vkQueuePresentKHR
|
||||
* W: Wait
|
||||
* S: Signal
|
||||
* R: Render
|
||||
* P: Present
|
||||
* SN: Semaphore N
|
||||
* IN: Swapchain image N
|
||||
* FN: Fence N
|
||||
|
||||
Assuming both ANI calls below return the same index:
|
||||
|
||||
CPU: ANI ... QS ... QP ANI ... QS ... QP
|
||||
S:S1 W:S1 W:S2 S:S3 W:S3 W:S4
|
||||
S:F1 S:S2 S:F2 S:S4
|
||||
GPU: <------ R ------> <------ R ------>
|
||||
PE: <-- P --> <-- P -->
|
||||
|
||||
The following holds:
|
||||
|
||||
F2 is signaled
|
||||
=> The PE has handed the image to the application
|
||||
=> The PE is no longer presenting the image (the first P operation is finished)
|
||||
=> The PE is done waiting on S2
|
||||
|
||||
At this point, we can destroy or recycle S2.
|
||||
To implement this, a history of present operations is maintained, which includes the wait semaphore used with that presentation.
|
||||
Associated with each present operation, is a fence that is used to determine when that semaphore can be destroyed.
|
||||
|
||||
Since the fence is not actually known at present time (QP), the present operation is kept in history without an associated fence.
|
||||
Once ANI returns the same index, the fence given to ANI is associated with the previous QP of that index.
|
||||
|
||||
After each present call, the present history is inspected.
|
||||
Any present operation whose fence is signaled is cleaned up.
|
||||
|
||||
== Swapchain recreation
|
||||
|
||||
When recreating the swapchain, all images are eventually freed and new ones are created, possibly with a different count and present mode.
|
||||
For the old swapchain, we can no longer rely on a future ANI to know when a previous presentation's semaphore can be destroyed, as there won't be any more acquisitions from the old swapchain.
|
||||
Similarly, we cannot know when the old swapchain itself can be destroyed.
|
||||
|
||||
This issue is resolved by deferring the destruction of the old swapchain and its remaining present semaphores to the time when the semaphore corresponding to the first present of the new swapchain can be destroyed.
|
||||
Because once the first present semaphore of the new swapchain can be destroyed, the first present operation of the new swapchain is done, which means the old swapchain is no longer being presented.
|
||||
|
||||
Note that the swapchain may be recreated without a second acquire.
|
||||
This means that the swapchain could be recreated while there are pending old swapchains to be destroyed.
|
||||
The destruction of both old swapchains must now be deferred to when the first QP of the new swapchain has been processed.
|
||||
If an application resizes the window constantly and at a high rate, we would keep accumulating old swapchains and not free them until it stops.
|
||||
|
||||
== VK_EXT_swapchain_maintenance1
|
||||
|
||||
With the VK_EXT_swapchain_maintenance1, all the above is unnecessary.
|
||||
Each QP operation can have an associated fence, which can be used to know when the semaphore associated with it can be recycled.
|
||||
The old swapchains can be destroyed at the same time as before.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,216 +0,0 @@
|
||||
/* Copyright (c) 2023-2025, Google
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "platform/application.h"
|
||||
#include "vulkan_sample.h"
|
||||
|
||||
/**
|
||||
* @brief A sample that implements best practices in handling present resources and swapchain
|
||||
* recreation, for example due to window resizing or present mode changes.
|
||||
*/
|
||||
class SwapchainRecreation : public vkb::VulkanSampleC
|
||||
{
|
||||
struct SwapchainObjects
|
||||
{
|
||||
std::vector<VkImage> images;
|
||||
std::vector<VkImageView> views;
|
||||
std::vector<VkFramebuffer> framebuffers;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Per-frame data. This is not per swapchain image!
|
||||
* A queue of this data structure is used to remember the history of submissions. To avoid
|
||||
* the CPU getting too far ahead of the GPU, the sample paces itself by waiting for the
|
||||
* submission before last to finish before recording commands for the new frame. This means
|
||||
* that frame N+1 doesn't start recording until frame N-1 finishes executing on the GPU (and
|
||||
* likely frame N starts). In a real application, this minimizes latency from input to
|
||||
* screen.
|
||||
*/
|
||||
struct PerFrame
|
||||
{
|
||||
VkFence submit_fence = VK_NULL_HANDLE;
|
||||
VkCommandPool command_pool = VK_NULL_HANDLE;
|
||||
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
|
||||
VkSemaphore acquire_semaphore = VK_NULL_HANDLE;
|
||||
VkSemaphore present_semaphore = VK_NULL_HANDLE;
|
||||
|
||||
// Garbage to clean up once the submit_fence is signaled, if any.
|
||||
std::vector<SwapchainObjects> swapchain_garbage;
|
||||
};
|
||||
|
||||
struct SwapchainCleanupData
|
||||
{
|
||||
/// The old swapchain to be destroyed.
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
|
||||
/**
|
||||
* @brief Any present semaphores that were pending recycle at the time the swapchain
|
||||
* was recreated will be scheduled for recycling at the same time as the swapchain's
|
||||
* destruction.
|
||||
*/
|
||||
std::vector<VkSemaphore> semaphores;
|
||||
};
|
||||
|
||||
struct PresentOperationInfo
|
||||
{
|
||||
/**
|
||||
* @brief Fence that tells when the present semaphore can be destroyed. Without
|
||||
* VK_EXT_swapchain_maintenance1, the fence used with the vkAcquireNextImageKHR that
|
||||
* returns the same image index in the future is used to know when the semaphore can
|
||||
* be recycled.
|
||||
*/
|
||||
VkFence cleanup_fence = VK_NULL_HANDLE;
|
||||
VkSemaphore present_semaphore = VK_NULL_HANDLE;
|
||||
|
||||
/**
|
||||
* @brief Old swapchains are scheduled to be destroyed at the same time as the last
|
||||
* wait semaphore used to present an image to the old swapchains can be recycled.
|
||||
*/
|
||||
std::vector<SwapchainCleanupData> old_swapchains;
|
||||
|
||||
/**
|
||||
* @brief Used to associate an acquire fence with the previous present operation of
|
||||
* the image. Only relevant when VK_EXT_swapchain_maintenance1 is not supported;
|
||||
* otherwise a fence is always associated with the present operation.
|
||||
*/
|
||||
uint32_t image_index = std::numeric_limits<uint32_t>::max();
|
||||
};
|
||||
|
||||
public:
|
||||
SwapchainRecreation();
|
||||
|
||||
virtual ~SwapchainRecreation() override;
|
||||
|
||||
void create_render_context() override;
|
||||
|
||||
void prepare_render_context() override;
|
||||
|
||||
void update(float delta_time) override;
|
||||
|
||||
bool resize(uint32_t width, uint32_t height) override;
|
||||
|
||||
void input_event(const vkb::InputEvent &input_event) override;
|
||||
|
||||
private:
|
||||
/// Submission and present queue.
|
||||
const vkb::Queue *queue = nullptr;
|
||||
|
||||
/// Allow enabling VK_EXT_surface_maintenance1 and VK_EXT_swapchain_maintenance1.
|
||||
///
|
||||
/// Can be set to false by setting environment variable `USE_MAINTENANCE1=no`
|
||||
bool allow_maintenance1 = true;
|
||||
/// Whether the VK_EXT_surface_maintenance1 and VK_EXT_swapchain_maintenance1 extensions are
|
||||
/// enabled.
|
||||
bool has_maintenance1 = false;
|
||||
|
||||
/// Surface data.
|
||||
VkSurfaceFormatKHR surface_format = {};
|
||||
std::vector<VkPresentModeKHR> present_modes = {};
|
||||
std::vector<VkPresentModeKHR> compatible_modes = {};
|
||||
VkExtent2D swapchain_extents = {};
|
||||
|
||||
/// The swapchain.
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
|
||||
/// Swapchain data.
|
||||
VkPresentModeKHR current_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
VkPresentModeKHR desired_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
SwapchainObjects swapchain_objects;
|
||||
|
||||
/// The render pass used for rendering.
|
||||
VkRenderPass render_pass = VK_NULL_HANDLE;
|
||||
|
||||
/// The submission history. This is a fixed-size queue, implemented as a circular buffer.
|
||||
std::array<PerFrame, 2> submit_history = {};
|
||||
size_t submit_history_index = 0;
|
||||
|
||||
/// The present operation history. This is used to clean up present semaphores and old swapchains.
|
||||
std::deque<PresentOperationInfo> present_history;
|
||||
|
||||
/**
|
||||
* @brief The previous swapchain which needs to be scheduled for destruction when
|
||||
* appropriate. This will be done when the first image of the current swapchain is
|
||||
* presented. If there were older swapchains pending destruction when the swapchain is
|
||||
* recreated, they will accumulate and be destroyed with the previous swapchain.
|
||||
*
|
||||
* Note that if the user resizes the window such that the swapchain is recreated every
|
||||
* frame, this array can go grow indefinitely.
|
||||
*/
|
||||
std::vector<SwapchainCleanupData> old_swapchains;
|
||||
|
||||
/// Resource pools.
|
||||
std::vector<VkSemaphore> semaphore_pool;
|
||||
std::vector<VkFence> fence_pool;
|
||||
|
||||
/// Time.
|
||||
uint32_t frame_number = 0;
|
||||
|
||||
// FPS log.
|
||||
float fps_timer = 0;
|
||||
uint32_t fps_last_logged_frame_number = 0;
|
||||
|
||||
// Other statistics
|
||||
uint32_t swapchain_creation_count = 0;
|
||||
|
||||
// User toggles.
|
||||
bool recreate_swapchain_on_present_mode_change = false;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
std::unique_ptr<vkb::core::DeviceC> create_device(vkb::PhysicalDevice &gpu) override;
|
||||
|
||||
void get_queue();
|
||||
void query_surface_format();
|
||||
void query_present_modes();
|
||||
void query_compatible_present_modes(VkPresentModeKHR present_mode);
|
||||
void adjust_desired_present_mode();
|
||||
void create_render_pass();
|
||||
|
||||
bool are_present_modes_compatible();
|
||||
|
||||
void init_swapchain();
|
||||
void init_swapchain_image(uint32_t index);
|
||||
void cleanup_swapchain_objects(SwapchainObjects &garbage);
|
||||
bool recreate_swapchain();
|
||||
|
||||
void setup_frame();
|
||||
void render(uint32_t index);
|
||||
|
||||
VkResult acquire_next_image(uint32_t *index);
|
||||
VkResult present_image(uint32_t index);
|
||||
void add_present_to_history(uint32_t index, VkFence present_fence);
|
||||
void cleanup_present_history();
|
||||
void cleanup_present_info(PresentOperationInfo &present_info);
|
||||
void cleanup_old_swapchain(SwapchainCleanupData &old_swapchain);
|
||||
|
||||
void associate_fence_with_present_history(uint32_t index, VkFence acquire_fence);
|
||||
void schedule_old_swapchain_for_destruction(VkSwapchainKHR old_swapchain);
|
||||
|
||||
VkSemaphore get_semaphore();
|
||||
void recycle_semaphore(VkSemaphore semaphore);
|
||||
|
||||
VkFence get_fence();
|
||||
void recycle_fence(VkFence fence);
|
||||
|
||||
VkPhysicalDevice get_gpu_handle();
|
||||
VkDevice get_device_handle();
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_swapchain_recreation();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2019-2024, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Terrain tessellation"
|
||||
DESCRIPTION "Using tessellation shaders for dynamic terrain level-of-detail"
|
||||
SHADER_FILES_GLSL
|
||||
"terrain_tessellation/glsl/terrain.vert"
|
||||
"terrain_tessellation/glsl/terrain.frag"
|
||||
"terrain_tessellation/glsl/terrain.tesc"
|
||||
"terrain_tessellation/glsl/terrain.tese"
|
||||
"terrain_tessellation/glsl/skysphere.vert"
|
||||
"terrain_tessellation/glsl/skysphere.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"terrain_tessellation/hlsl/terrain.vert.hlsl"
|
||||
"terrain_tessellation/hlsl/terrain.frag.hlsl"
|
||||
"terrain_tessellation/hlsl/terrain.tesc.hlsl"
|
||||
"terrain_tessellation/hlsl/terrain.tese.hlsl"
|
||||
"terrain_tessellation/hlsl/skysphere.vert.hlsl"
|
||||
"terrain_tessellation/hlsl/skysphere.frag.hlsl")
|
||||
@@ -1,27 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, The Khronos Group
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Terrain Tessellation
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/terrain_tessellation[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
Uses a tessellation shader for rendering a terrain with dynamic level-of-detail and frustum culling.
|
||||
@@ -1,771 +0,0 @@
|
||||
/* Copyright (c) 2019-2025, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dynamic terrain tessellation
|
||||
*/
|
||||
|
||||
#include "terrain_tessellation.h"
|
||||
|
||||
#include "heightmap.h"
|
||||
|
||||
TerrainTessellation::TerrainTessellation()
|
||||
{
|
||||
title = "Dynamic terrain tessellation";
|
||||
}
|
||||
|
||||
TerrainTessellation::~TerrainTessellation()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.terrain, nullptr);
|
||||
if (pipelines.wireframe != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.wireframe, nullptr);
|
||||
}
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.skysphere, nullptr);
|
||||
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.skysphere, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.terrain, nullptr);
|
||||
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.terrain, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.skysphere, nullptr);
|
||||
|
||||
uniform_buffers.skysphere_vertex.reset();
|
||||
uniform_buffers.terrain_tessellation.reset();
|
||||
|
||||
textures.heightmap.image.reset();
|
||||
vkDestroySampler(get_device().get_handle(), textures.heightmap.sampler, nullptr);
|
||||
textures.skysphere.image.reset();
|
||||
vkDestroySampler(get_device().get_handle(), textures.skysphere.sampler, nullptr);
|
||||
textures.terrain_array.image.reset();
|
||||
vkDestroySampler(get_device().get_handle(), textures.terrain_array.sampler, nullptr);
|
||||
|
||||
if (query_pool != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyQueryPool(get_device().get_handle(), query_pool, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainTessellation::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
auto &requested_features = gpu.get_mutable_requested_features();
|
||||
|
||||
// Tessellation shader support is required for this example
|
||||
if (gpu.get_features().tessellationShader)
|
||||
{
|
||||
requested_features.tessellationShader = VK_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw vkb::VulkanException(VK_ERROR_FEATURE_NOT_PRESENT, "Selected GPU does not support tessellation shaders!");
|
||||
}
|
||||
|
||||
// Fill mode non solid is required for wireframe display
|
||||
if (gpu.get_features().fillModeNonSolid)
|
||||
{
|
||||
requested_features.fillModeNonSolid = VK_TRUE;
|
||||
}
|
||||
|
||||
// Pipeline statistics
|
||||
if (gpu.get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
requested_features.pipelineStatisticsQuery = VK_TRUE;
|
||||
}
|
||||
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
requested_features.samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup pool and buffer for storing pipeline statistics results
|
||||
void TerrainTessellation::setup_query_result_buffer()
|
||||
{
|
||||
// Create query pool
|
||||
if (get_device().get_gpu().get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
VkQueryPoolCreateInfo query_pool_info = {};
|
||||
query_pool_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
|
||||
query_pool_info.queryType = VK_QUERY_TYPE_PIPELINE_STATISTICS;
|
||||
query_pool_info.pipelineStatistics =
|
||||
VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT |
|
||||
VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT;
|
||||
query_pool_info.queryCount = 2;
|
||||
VK_CHECK(vkCreateQueryPool(get_device().get_handle(), &query_pool_info, NULL, &query_pool));
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves the results of the pipeline statistics query submitted to the command buffer
|
||||
void TerrainTessellation::get_query_results()
|
||||
{
|
||||
// We use vkGetQueryResults to copy the results into a host visible buffer
|
||||
vkGetQueryPoolResults(
|
||||
get_device().get_handle(),
|
||||
query_pool,
|
||||
0,
|
||||
1,
|
||||
sizeof(pipeline_stats),
|
||||
pipeline_stats,
|
||||
sizeof(uint64_t),
|
||||
VK_QUERY_RESULT_64_BIT);
|
||||
}
|
||||
|
||||
void TerrainTessellation::load_assets()
|
||||
{
|
||||
skysphere = load_model("scenes/geosphere.gltf");
|
||||
|
||||
textures.skysphere = load_texture("textures/skysphere_rgba.ktx", vkb::sg::Image::Color);
|
||||
// Terrain textures are stored in a texture array with layers corresponding to terrain height
|
||||
textures.terrain_array = load_texture_array("textures/terrain_texturearray_rgba.ktx", vkb::sg::Image::Color);
|
||||
|
||||
// Height data is stored in a one-channel texture
|
||||
textures.heightmap = load_texture("textures/terrain_heightmap_r16.ktx", vkb::sg::Image::Other);
|
||||
|
||||
VkSamplerCreateInfo sampler_create_info = vkb::initializers::sampler_create_info();
|
||||
|
||||
// Calculate valid filter and mipmap modes
|
||||
VkFilter filter = VK_FILTER_LINEAR;
|
||||
VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
vkb::make_filters_valid(get_device().get_gpu().get_handle(), textures.heightmap.image->get_format(), &filter, &mipmap_mode);
|
||||
|
||||
// Setup a mirroring sampler for the height map
|
||||
vkDestroySampler(get_device().get_handle(), textures.heightmap.sampler, nullptr);
|
||||
sampler_create_info.magFilter = filter;
|
||||
sampler_create_info.minFilter = filter;
|
||||
sampler_create_info.mipmapMode = mipmap_mode;
|
||||
sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
||||
sampler_create_info.addressModeV = sampler_create_info.addressModeU;
|
||||
sampler_create_info.addressModeW = sampler_create_info.addressModeU;
|
||||
sampler_create_info.compareOp = VK_COMPARE_OP_NEVER;
|
||||
sampler_create_info.minLod = 0.0f;
|
||||
sampler_create_info.maxLod = static_cast<float>(textures.heightmap.image->get_mipmaps().size());
|
||||
sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_create_info, nullptr, &textures.heightmap.sampler));
|
||||
|
||||
filter = VK_FILTER_LINEAR;
|
||||
mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
vkb::make_filters_valid(get_device().get_gpu().get_handle(), textures.terrain_array.image->get_format(), &filter, &mipmap_mode);
|
||||
|
||||
// Setup a repeating sampler for the terrain texture layers
|
||||
vkDestroySampler(get_device().get_handle(), textures.terrain_array.sampler, nullptr);
|
||||
sampler_create_info = vkb::initializers::sampler_create_info();
|
||||
sampler_create_info.magFilter = filter;
|
||||
sampler_create_info.minFilter = filter;
|
||||
sampler_create_info.mipmapMode = mipmap_mode;
|
||||
sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
sampler_create_info.addressModeV = sampler_create_info.addressModeU;
|
||||
sampler_create_info.addressModeW = sampler_create_info.addressModeU;
|
||||
sampler_create_info.compareOp = VK_COMPARE_OP_NEVER;
|
||||
sampler_create_info.minLod = 0.0f;
|
||||
sampler_create_info.maxLod = static_cast<float>(textures.terrain_array.image->get_mipmaps().size());
|
||||
sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
if (get_device().get_gpu().get_features().samplerAnisotropy)
|
||||
{
|
||||
sampler_create_info.maxAnisotropy = 4.0f;
|
||||
sampler_create_info.anisotropyEnable = VK_TRUE;
|
||||
}
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_create_info, nullptr, &textures.terrain_array.sampler));
|
||||
}
|
||||
|
||||
void TerrainTessellation::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = default_clear_color;
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
if (get_device().get_gpu().get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
vkCmdResetQueryPool(draw_cmd_buffers[i], query_pool, 0, 2);
|
||||
}
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdSetLineWidth(draw_cmd_buffers[i], 1.0f);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
|
||||
// Skysphere
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skysphere);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.skysphere, 0, 1, &descriptor_sets.skysphere, 0, NULL);
|
||||
draw_model(skysphere, draw_cmd_buffers[i]);
|
||||
|
||||
// Terrain
|
||||
if (get_device().get_gpu().get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
// Begin pipeline statistics query
|
||||
vkCmdBeginQuery(draw_cmd_buffers[i], query_pool, 0, 0);
|
||||
}
|
||||
// Render
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.terrain);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.terrain, 0, 1, &descriptor_sets.terrain, 0, NULL);
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, terrain.vertices->get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], terrain.indices->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], terrain.index_count, 1, 0, 0, 0);
|
||||
if (get_device().get_gpu().get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
// End pipeline statistics query
|
||||
vkCmdEndQuery(draw_cmd_buffers[i], query_pool, 0);
|
||||
}
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a terrain quad patch for feeding to the tessellation control shader
|
||||
void TerrainTessellation::generate_terrain()
|
||||
{
|
||||
const uint32_t patch_size = 64;
|
||||
const float uv_scale = 1.0f;
|
||||
const uint32_t vertex_count = patch_size * patch_size;
|
||||
std::vector<Vertex> vertices(vertex_count);
|
||||
|
||||
const float wx = 2.0f;
|
||||
const float wy = 2.0f;
|
||||
|
||||
for (auto x = 0; x < patch_size; x++)
|
||||
{
|
||||
for (auto y = 0; y < patch_size; y++)
|
||||
{
|
||||
uint32_t index = (x + y * patch_size);
|
||||
vertices[index].pos[0] = x * wx + wx / 2.0f - static_cast<float>(patch_size) * wx / 2.0f;
|
||||
vertices[index].pos[1] = 0.0f;
|
||||
vertices[index].pos[2] = y * wy + wy / 2.0f - static_cast<float>(patch_size) * wy / 2.0f;
|
||||
vertices[index].uv = glm::vec2(static_cast<float>(x) / patch_size, static_cast<float>(y) / patch_size) * uv_scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate normals from height map using a sobel filter
|
||||
vkb::HeightMap heightmap("textures/terrain_heightmap_r16.ktx", patch_size);
|
||||
for (auto x = 0; x < patch_size; x++)
|
||||
{
|
||||
for (auto y = 0; y < patch_size; y++)
|
||||
{
|
||||
// Get height samples centered around current position
|
||||
float heights[3][3];
|
||||
for (auto hx = -1; hx <= 1; hx++)
|
||||
{
|
||||
for (auto hy = -1; hy <= 1; hy++)
|
||||
{
|
||||
heights[hx + 1][hy + 1] = heightmap.get_height(x + hx, y + hy);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the normal
|
||||
glm::vec3 normal;
|
||||
// Gx sobel filter
|
||||
normal.x = heights[0][0] - heights[2][0] + 2.0f * heights[0][1] - 2.0f * heights[2][1] + heights[0][2] - heights[2][2];
|
||||
// Gy sobel filter
|
||||
normal.z = heights[0][0] + 2.0f * heights[1][0] + heights[2][0] - heights[0][2] - 2.0f * heights[1][2] - heights[2][2];
|
||||
// Calculate missing up component of the normal using the filtered x and y axis
|
||||
// The first value controls the bump strength
|
||||
normal.y = 0.25f * sqrt(1.0f - normal.x * normal.x - normal.z * normal.z);
|
||||
|
||||
vertices[x + y * patch_size].normal = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
// Indices
|
||||
const uint32_t w = (patch_size - 1);
|
||||
const uint32_t index_count = w * w * 4;
|
||||
std::vector<uint32_t> indices(index_count);
|
||||
for (auto x = 0; x < w; x++)
|
||||
{
|
||||
for (auto y = 0; y < w; y++)
|
||||
{
|
||||
uint32_t index = (x + y * w) * 4;
|
||||
indices[index] = (x + y * patch_size);
|
||||
indices[index + 1] = indices[index] + patch_size;
|
||||
indices[index + 2] = indices[index + 1] + 1;
|
||||
indices[index + 3] = indices[index] + 1;
|
||||
}
|
||||
}
|
||||
terrain.index_count = index_count;
|
||||
|
||||
uint32_t vertex_buffer_size = vertex_count * sizeof(Vertex);
|
||||
uint32_t index_buffer_size = index_count * sizeof(uint32_t);
|
||||
|
||||
// Create staging buffers
|
||||
|
||||
vkb::core::BufferC vertex_staging = vkb::core::BufferC::create_staging_buffer(get_device(), vertices);
|
||||
vkb::core::BufferC index_staging = vkb::core::BufferC::create_staging_buffer(get_device(), indices);
|
||||
|
||||
terrain.vertices = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
vertex_buffer_size,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
terrain.indices = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
index_buffer_size,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
// Copy from staging buffers
|
||||
VkCommandBuffer copy_command = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
VkBufferCopy copy_region = {};
|
||||
|
||||
copy_region.size = vertex_buffer_size;
|
||||
vkCmdCopyBuffer(
|
||||
copy_command,
|
||||
vertex_staging.get_handle(),
|
||||
terrain.vertices->get_handle(),
|
||||
1,
|
||||
©_region);
|
||||
|
||||
copy_region.size = index_buffer_size;
|
||||
vkCmdCopyBuffer(
|
||||
copy_command,
|
||||
index_staging.get_handle(),
|
||||
terrain.indices->get_handle(),
|
||||
1,
|
||||
©_region);
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
}
|
||||
|
||||
void TerrainTessellation::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes =
|
||||
{
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3)};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
2);
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void TerrainTessellation::setup_descriptor_set_layouts()
|
||||
{
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout;
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info;
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings;
|
||||
|
||||
// Terrain
|
||||
set_layout_bindings =
|
||||
{
|
||||
// Binding 0 : Shared Tessellation shader ubo
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
|
||||
0),
|
||||
// Binding 1 : Height map
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
1),
|
||||
// Binding 3 : Terrain texture array layers
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
2),
|
||||
};
|
||||
|
||||
descriptor_layout = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout, nullptr, &descriptor_set_layouts.terrain));
|
||||
pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.terrain, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.terrain));
|
||||
|
||||
// Skysphere
|
||||
set_layout_bindings =
|
||||
{
|
||||
// Binding 0 : Vertex shader ubo
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0),
|
||||
// Binding 1 : Color map
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
1),
|
||||
};
|
||||
|
||||
descriptor_layout = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings.data(), static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout, nullptr, &descriptor_set_layouts.skysphere));
|
||||
pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.skysphere, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.skysphere));
|
||||
}
|
||||
|
||||
void TerrainTessellation::setup_descriptor_sets()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info;
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets;
|
||||
|
||||
// Terrain
|
||||
alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layouts.terrain, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.terrain));
|
||||
|
||||
VkDescriptorBufferInfo terrain_buffer_descriptor = create_descriptor(*uniform_buffers.terrain_tessellation);
|
||||
VkDescriptorImageInfo heightmap_image_descriptor = create_descriptor(textures.heightmap);
|
||||
VkDescriptorImageInfo terrainmap_image_descriptor = create_descriptor(textures.terrain_array);
|
||||
write_descriptor_sets =
|
||||
{
|
||||
// Binding 0 : Shared tessellation shader ubo
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_sets.terrain,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
0,
|
||||
&terrain_buffer_descriptor),
|
||||
// Binding 1 : Displacement map
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_sets.terrain,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
1,
|
||||
&heightmap_image_descriptor),
|
||||
// Binding 2 : Color map (alpha channel)
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_sets.terrain,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
2,
|
||||
&terrainmap_image_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
|
||||
// Skysphere
|
||||
alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layouts.skysphere, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.skysphere));
|
||||
|
||||
VkDescriptorBufferInfo skysphere_buffer_descriptor = create_descriptor(*uniform_buffers.skysphere_vertex);
|
||||
VkDescriptorImageInfo skysphere_image_descriptor = create_descriptor(textures.skysphere);
|
||||
write_descriptor_sets =
|
||||
{
|
||||
// Binding 0 : Vertex shader ubo
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_sets.skysphere,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
0,
|
||||
&skysphere_buffer_descriptor),
|
||||
// Binding 1 : Fragment shader color map
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_sets.skysphere,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
1,
|
||||
&skysphere_image_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
void TerrainTessellation::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state_create_info =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_BACK_BIT,
|
||||
VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_TRUE,
|
||||
VK_TRUE,
|
||||
VK_COMPARE_OP_GREATER);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_LINE_WIDTH};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
// We render the terrain as a grid of quad patches
|
||||
VkPipelineTessellationStateCreateInfo tessellation_state =
|
||||
vkb::initializers::pipeline_tessellation_state_create_info(4);
|
||||
|
||||
// Vertex bindings an attributes
|
||||
// Binding description
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(TerrainTessellation::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
|
||||
// Attribute descriptions
|
||||
std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Normal
|
||||
vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // UV
|
||||
};
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 4> shader_stages;
|
||||
|
||||
// Terrain tessellation pipeline
|
||||
shader_stages[0] = load_shader("terrain_tessellation", "terrain.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("terrain_tessellation", "terrain.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
shader_stages[2] = load_shader("terrain_tessellation", "terrain.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
|
||||
shader_stages[3] = load_shader("terrain_tessellation", "terrain.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(pipeline_layouts.terrain, render_pass, 0);
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state_create_info;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
pipeline_create_info.pTessellationState = &tessellation_state;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
pipeline_create_info.renderPass = render_pass;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.terrain));
|
||||
|
||||
// Terrain wireframe pipeline
|
||||
if (get_device().get_gpu().get_features().fillModeNonSolid)
|
||||
{
|
||||
rasterization_state.polygonMode = VK_POLYGON_MODE_LINE;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.wireframe));
|
||||
};
|
||||
|
||||
// Skysphere pipeline
|
||||
|
||||
// Stride from glTF model vertex layout
|
||||
vertex_input_bindings[0].stride = sizeof(::Vertex);
|
||||
|
||||
rasterization_state.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
// Revert to triangle list topology
|
||||
input_assembly_state_create_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
// Reset tessellation state
|
||||
pipeline_create_info.pTessellationState = nullptr;
|
||||
// Don't write to depth buffer
|
||||
depth_stencil_state.depthWriteEnable = VK_FALSE;
|
||||
pipeline_create_info.stageCount = 2;
|
||||
pipeline_create_info.layout = pipeline_layouts.skysphere;
|
||||
shader_stages[0] = load_shader("terrain_tessellation", "skysphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("terrain_tessellation", "skysphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.skysphere));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void TerrainTessellation::prepare_uniform_buffers()
|
||||
{
|
||||
// Shared tessellation shader stages uniform buffer
|
||||
uniform_buffers.terrain_tessellation = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_tess),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Skysphere vertex shader uniform buffer
|
||||
uniform_buffers.skysphere_vertex = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_vs),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void TerrainTessellation::update_uniform_buffers()
|
||||
{
|
||||
// Tessellation
|
||||
|
||||
ubo_tess.projection = camera.matrices.perspective;
|
||||
ubo_tess.modelview = camera.matrices.view * glm::mat4(1.0f);
|
||||
ubo_tess.light_pos.y = -0.5f - ubo_tess.displacement_factor; // todo: Not used yet
|
||||
ubo_tess.viewport_dim = glm::vec2(static_cast<float>(width), static_cast<float>(height));
|
||||
|
||||
frustum.update(ubo_tess.projection * ubo_tess.modelview);
|
||||
memcpy(ubo_tess.frustum_planes, frustum.get_planes().data(), sizeof(glm::vec4) * 6);
|
||||
|
||||
float saved_factor = ubo_tess.tessellation_factor;
|
||||
if (!tessellation)
|
||||
{
|
||||
// Setting this to zero sets all tessellation factors to 1.0 in the shader
|
||||
ubo_tess.tessellation_factor = 0.0f;
|
||||
}
|
||||
|
||||
uniform_buffers.terrain_tessellation->convert_and_update(ubo_tess);
|
||||
|
||||
if (!tessellation)
|
||||
{
|
||||
ubo_tess.tessellation_factor = saved_factor;
|
||||
}
|
||||
|
||||
// Skysphere vertex shader
|
||||
ubo_vs.mvp = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view));
|
||||
uniform_buffers.skysphere_vertex->convert_and_update(ubo_vs.mvp);
|
||||
}
|
||||
|
||||
void TerrainTessellation::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
// Submit to queue
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
|
||||
if (get_device().get_gpu().get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
// Read query results for displaying in next frame
|
||||
get_query_results();
|
||||
}
|
||||
|
||||
ApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
bool TerrainTessellation::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped
|
||||
camera.type = vkb::CameraType::FirstPerson;
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 512.0f, 0.1f);
|
||||
camera.set_rotation(glm::vec3(-12.0f, 159.0f, 0.0f));
|
||||
camera.set_translation(glm::vec3(18.0f, 22.5f, 57.5f));
|
||||
camera.translation_speed = 7.5f;
|
||||
|
||||
load_assets();
|
||||
generate_terrain();
|
||||
if (get_device().get_gpu().get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
setup_query_result_buffer();
|
||||
}
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layouts();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_sets();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TerrainTessellation::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
void TerrainTessellation::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void TerrainTessellation::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.checkbox("Tessellation", &tessellation))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
if (drawer.input_float("Factor", &ubo_tess.tessellation_factor, 0.05f, "%.2f"))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
if (get_device().get_gpu().get_features().fillModeNonSolid)
|
||||
{
|
||||
if (drawer.checkbox("Wireframe", &wireframe))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (get_device().get_gpu().get_features().pipelineStatisticsQuery)
|
||||
{
|
||||
if (drawer.header("Pipeline statistics"))
|
||||
{
|
||||
drawer.text("VS invocations: %d", pipeline_stats[0]);
|
||||
drawer.text("TE invocations: %d", pipeline_stats[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_terrain_tessellation()
|
||||
{
|
||||
return std::make_unique<TerrainTessellation>();
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/* Copyright (c) 2019-2024, Sascha Willems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dynamic terrain tessellation
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include "core/buffer.h"
|
||||
#include "geometry/frustum.h"
|
||||
|
||||
class TerrainTessellation : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
bool wireframe = false;
|
||||
bool tessellation = true;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture heightmap;
|
||||
Texture skysphere;
|
||||
Texture terrain_array;
|
||||
} textures;
|
||||
|
||||
std::unique_ptr<vkb::sg::SubMesh> skysphere;
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
glm::vec3 pos;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 uv;
|
||||
};
|
||||
|
||||
struct Terrain
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> vertices;
|
||||
std::unique_ptr<vkb::core::BufferC> indices;
|
||||
uint32_t index_count;
|
||||
} terrain;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> terrain_tessellation;
|
||||
std::unique_ptr<vkb::core::BufferC> skysphere_vertex;
|
||||
} uniform_buffers;
|
||||
|
||||
// Shared values for tessellation control and evaluation stages
|
||||
struct
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::vec4 light_pos = glm::vec4(-48.0f, -40.0f, 46.0f, 0.0f);
|
||||
glm::vec4 frustum_planes[6];
|
||||
float displacement_factor = 32.0f;
|
||||
float tessellation_factor = 0.75f;
|
||||
glm::vec2 viewport_dim;
|
||||
// Desired size of tessellated quad patch edge
|
||||
float tessellated_edge_size = 20.0f;
|
||||
} ubo_tess;
|
||||
|
||||
// Skysphere vertex shader stage
|
||||
struct
|
||||
{
|
||||
glm::mat4 mvp;
|
||||
} ubo_vs;
|
||||
|
||||
struct Pipelines
|
||||
{
|
||||
VkPipeline terrain;
|
||||
VkPipeline wireframe = VK_NULL_HANDLE;
|
||||
VkPipeline skysphere;
|
||||
} pipelines;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSetLayout terrain;
|
||||
VkDescriptorSetLayout skysphere;
|
||||
} descriptor_set_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout terrain;
|
||||
VkPipelineLayout skysphere;
|
||||
} pipeline_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet terrain;
|
||||
VkDescriptorSet skysphere;
|
||||
} descriptor_sets;
|
||||
|
||||
// Pipeline statistics
|
||||
VkQueryPool query_pool = VK_NULL_HANDLE;
|
||||
uint64_t pipeline_stats[2] = {0};
|
||||
|
||||
// View frustum passed to tessellation control shader for culling
|
||||
vkb::Frustum frustum;
|
||||
|
||||
TerrainTessellation();
|
||||
~TerrainTessellation();
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void setup_query_result_buffer();
|
||||
void get_query_results();
|
||||
void load_assets();
|
||||
void build_command_buffers() override;
|
||||
void generate_terrain();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layouts();
|
||||
void setup_descriptor_sets();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void draw();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual void view_changed() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_terrain_tessellation();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2019-2024, Sascha Willems
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Texture mipmap generation"
|
||||
DESCRIPTION "Generate mipmaps for a texture"
|
||||
SHADER_FILES_GLSL
|
||||
"texture_mipmap_generation/glsl/texture.vert"
|
||||
"texture_mipmap_generation/glsl/texture.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"texture_mipmap_generation/hlsl/texture.vert.hlsl"
|
||||
"texture_mipmap_generation/hlsl/texture.frag.hlsl")
|
||||
@@ -1,266 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, Sascha Willems
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
= Run-time mip-map generation
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/texture_mipmap_generation[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
Generates a complete texture mip-chain at runtime from a base image using image blits and proper image barriers.
|
||||
|
||||
This examples demonstrates how to generate a complete texture mip-chain at runtime instead of loading offline generated mip-maps from a texture file.
|
||||
|
||||
While usually not applied for textures stored on the disk (that usually have the mips generated offline and stored in the file) this technique is often used for dynamic textures like cubemaps for reflections or other render-to-texture effects.
|
||||
|
||||
Having mip-maps for runtime generated textures offers lots of benefits, both in terms of image stability and performance.
|
||||
Without mip mapping the image will become noisy, especially with high frequency textures (and texture components like specular) and using mip mapping will result in higher performance due to caching.
|
||||
|
||||
Though this example only generates one mip-chain for a single texture at the beginning this technique can also be used during normal frame rendering to generate mip-chains for dynamic textures.
|
||||
|
||||
Some GPUs also offer `asynchronous transfer queues` that may be used for doing such operations in the background.
|
||||
To detect this, check for queue families with only the `VK_QUEUE_TRANSFER_BIT` set.
|
||||
|
||||
== Comparison
|
||||
|
||||
Without mip mapping:
|
||||
|
||||
image::./images/mip_mapping_off.jpg[Off,512px]
|
||||
|
||||
Using mip mapping with a bilinear filter:
|
||||
|
||||
image:./images/mip_mapping_bilinear.jpg[Bilinear,512px]
|
||||
|
||||
Using mip mapping with an anisotropic filter:
|
||||
|
||||
image:./images/mip_mapping_anisotropic.jpg[Anisotropic,512px]
|
||||
|
||||
== Requirements
|
||||
|
||||
To downsample from one mip level to the next, we will be using https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html[`vkCmdBlitImage`].
|
||||
This requires the format used to support the `BLIT_SRC_BIT` and the `BLIT_DST_BIT` flags.
|
||||
If these are not supported, the image format can't be used to blit and you'd either have to choose a different format or use a custom shader to generate mip levels.
|
||||
The example uses the `VK_FORMAT_R8G8B8A8_SRGB` that should support these flags on most implementations.
|
||||
|
||||
*_Note:_* Use https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkGetPhysicalDeviceFormatProperties.html[`vkGetPhysicalDeviceFormatProperties`] to check if the format supports the blit flags first.
|
||||
|
||||
== Points of interest
|
||||
|
||||
=== Image setup
|
||||
|
||||
Even though we'll only upload the first mip level initially, we create the image with number of desired mip levels.
|
||||
The following formula is used to calculate the number of mip levels based on the max.
|
||||
image extent:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
texture.mip_levels = static_cast<uint32_t>(floor(log2(std::max(texture.width, texture.height))) + 1);
|
||||
----
|
||||
|
||||
This is then passed to the image creat info:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageCreateInfo image_create_info = vkb::initializers::image_create_info();
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = format;
|
||||
image_create_info.mipLevels = texture.mip_levels;
|
||||
----
|
||||
|
||||
Setting the number of desired mip levels is necessary as this is used for allocating the correct amount of memory required the image (`vkAllocateMemory`).
|
||||
|
||||
=== Upload base mip level
|
||||
|
||||
Before generating the mip-chain we need to copy the image data loaded from disk into the newly generated image.
|
||||
This image will be the base for our mip-chain:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkBufferImageCopy buffer_copy_region = {};
|
||||
buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
buffer_copy_region.imageSubresource.mipLevel = 0;
|
||||
buffer_copy_region.imageSubresource.baseArrayLayer = 0;
|
||||
buffer_copy_region.imageSubresource.layerCount = 1;
|
||||
buffer_copy_region.imageExtent.width = texture.width;
|
||||
buffer_copy_region.imageExtent.height = texture.height;
|
||||
buffer_copy_region.imageExtent.depth = 1;
|
||||
vkCmdCopyBufferToImage(copy_command, staging_buffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_copy_region);
|
||||
----
|
||||
|
||||
=== Prepare base mip level
|
||||
|
||||
As we are going to blit *_from_* the base mip-level just uploaded we also need to insert an image memory barrier that transitions the image layout to `TRANSFER_SRC` for the base mip level:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkb::insert_image_memory_barrier(
|
||||
copy_command,
|
||||
texture.image,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1});
|
||||
----
|
||||
|
||||
=== Generating the mip-chain
|
||||
|
||||
There are two different ways of generating the mip-chain.
|
||||
The first one is to blit down the whole mip-chain from level n-1 to n, the other way would be to always use the base image and blit down from that to all levels.
|
||||
This example uses the first one.
|
||||
|
||||
*_Note:_* Blitting (same for copying) images is done inside of a command buffer that has to be submitted and as such has to be synchronized before using the new image with e.g.
|
||||
a `vkFence`.
|
||||
|
||||
We simply loop over all remaining mip levels (level 0 was loaded from disk) and prepare a `VkImageBlit` structure for each blit from mip level i-1 to level i.
|
||||
|
||||
First the source for our blit.
|
||||
This is the previous mip level.
|
||||
The dimensions of the blit source are specified by srcOffset:
|
||||
// {% raw %}
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
for (int32_t i = 1; i < texture.mipLevels; i++)
|
||||
{
|
||||
VkImageBlit image_blit{};
|
||||
|
||||
// Source
|
||||
image_blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_blit.srcSubresource.layerCount = 1;
|
||||
image_blit.srcSubresource.mipLevel = i - 1;
|
||||
image_blit.srcOffsets[1].x = int32_t(texture.width >> (i - 1));
|
||||
image_blit.srcOffsets[1].y = int32_t(texture.height >> (i - 1));
|
||||
image_blit.srcOffsets[1].z = 1;
|
||||
}
|
||||
----
|
||||
|
||||
// {% endraw %}
|
||||
|
||||
Setup for the destination mip level (1), with the dimensions for the blit destination specified in dstOffsets[1]:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Destination
|
||||
image_blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_blit.dstSubresource.layerCount = 1;
|
||||
image_blit.dstSubresource.mipLevel = i;
|
||||
image_blit.dstOffsets[1].x = int32_t(texture.width >> i);
|
||||
image_blit.dstOffsets[1].y = int32_t(texture.height >> i);
|
||||
image_blit.dstOffsets[1].z = 1;
|
||||
----
|
||||
|
||||
Before we can blit to this mip level, we need to transition it's image layout to `TRANSFER_DST`:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Prepare current mip level as image blit destination
|
||||
vkb::insert_image_memory_barrier(
|
||||
blit_command,
|
||||
texture.image,
|
||||
0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, i, 1, 0, 1});
|
||||
----
|
||||
|
||||
Note that we set the `baseMipLevel` of the subresource range to `i`, so the image memory barrier will only affect the one mip level we want to copy to.
|
||||
|
||||
Now that the mip level we want to copy from and the one we'll copy to are in the proper layout (transfer source and destination) we can issue the https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html[`vkCmdBlitImage`] to copy from mip level (i-1) to mip level (i):
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkCmdBlitImage(
|
||||
blit_command,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1,
|
||||
&image_blit,
|
||||
VK_FILTER_LINEAR);
|
||||
----
|
||||
|
||||
`vkCmdBlitImage` does the down sampling from mip level (i-1) to mip level (i) using a linear filter, if you need better or more advanced filtering for this you need to resort to using custom shaders for generating the mip chain instead of blitting.
|
||||
|
||||
After the blit is done we can use this mip level as a base for the next level, so we transition the layout from `TRANSFER_DST_OPTIMAL` to `TRANSFER_SRC_OPTIMAL` so we can use this level as transfer source for the next level:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkb::insert_image_memory_barrier(
|
||||
blit_command,
|
||||
texture.image,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, i, 1, 0, 1});
|
||||
}
|
||||
----
|
||||
|
||||
=== Final image layout transitions
|
||||
|
||||
Once the loop is done we need to transition all mip levels of the image to their actual usage layout, which is `SHADER_READ` for this example.
|
||||
|
||||
Note that after the loop above all levels will be in the `TRANSER_SRC` layout allowing us to transfer the whole image with a single barrier:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkb::insert_image_memory_barrier(
|
||||
blit_command,
|
||||
texture.image,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, 0, texture.mip_levels, 0, 1});
|
||||
----
|
||||
|
||||
Submitting that command buffer will result in an image with a complete mip-chain and all mip levels being transitioned to the proper image layout for shader reads.
|
||||
|
||||
=== Image View creation
|
||||
|
||||
The Image View also requires information about how many Mip Levels are used.
|
||||
This is specified in the `VkImageViewCreateInfo.subresourceRange.levelCount` field.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageViewCreateInfo view = vkb::initializers::image_view_create_info();
|
||||
view.image = texture.image;
|
||||
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
view.format = format;
|
||||
view.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A};
|
||||
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
view.subresourceRange.baseMipLevel = 0;
|
||||
view.subresourceRange.baseArrayLayer = 0;
|
||||
view.subresourceRange.layerCount = 1;
|
||||
view.subresourceRange.levelCount = texture.mip_levels;
|
||||
VK_CHECK(vkCreateImageView(device->get_handle(), &view, nullptr, &texture.view));
|
||||
----
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user