964 lines
41 KiB
C++
964 lines
41 KiB
C++
/* Copyright (c) 2021-2025, Holochip Corporation
|
|
*
|
|
* 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 "multi_draw_indirect.h"
|
|
#include "gltf_loader.h"
|
|
#include "ktx.h"
|
|
#include "scene_graph/components/camera.h"
|
|
#include "scene_graph/components/image.h"
|
|
#include "scene_graph/components/material.h"
|
|
#include "scene_graph/components/mesh.h"
|
|
#include "scene_graph/components/pbr_material.h"
|
|
|
|
namespace
|
|
{
|
|
template <typename T>
|
|
struct CopyBuffer
|
|
{
|
|
std::vector<T> operator()(std::unordered_map<std::string, vkb::core::BufferC> &buffers, const char *bufferName)
|
|
{
|
|
auto iter = buffers.find(bufferName);
|
|
if (iter == buffers.cend())
|
|
{
|
|
return {};
|
|
}
|
|
auto &buffer = iter->second;
|
|
std::vector<T> out;
|
|
|
|
const size_t sz = buffer.get_size();
|
|
out.resize(sz / sizeof(T));
|
|
const bool alreadyMapped = buffer.get_data() != nullptr;
|
|
if (!alreadyMapped)
|
|
{
|
|
buffer.map();
|
|
}
|
|
memcpy(&out[0], buffer.get_data(), sz);
|
|
if (!alreadyMapped)
|
|
{
|
|
buffer.unmap();
|
|
}
|
|
return out;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
MultiDrawIndirect::MultiDrawIndirect()
|
|
{
|
|
set_api_version(VK_API_VERSION_1_2);
|
|
add_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, true /* optional */);
|
|
}
|
|
|
|
MultiDrawIndirect::~MultiDrawIndirect()
|
|
{
|
|
if (has_device())
|
|
{
|
|
vertex_buffer.reset();
|
|
index_buffer.reset();
|
|
model_information_buffer.reset();
|
|
scene_uniform_buffer.reset();
|
|
|
|
vkDestroyPipeline(get_device().get_handle(), pipeline, VK_NULL_HANDLE);
|
|
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, VK_NULL_HANDLE);
|
|
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, VK_NULL_HANDLE);
|
|
vkDestroySampler(get_device().get_handle(), sampler_linear, VK_NULL_HANDLE);
|
|
vkDestroySampler(get_device().get_handle(), sampler_nearest, VK_NULL_HANDLE);
|
|
|
|
vkDestroyPipeline(get_device().get_handle(), gpu_cull_pipeline, VK_NULL_HANDLE);
|
|
vkDestroyPipelineLayout(get_device().get_handle(), gpu_cull_pipeline_layout, VK_NULL_HANDLE);
|
|
vkDestroyDescriptorSetLayout(get_device().get_handle(), gpu_cull_descriptor_set_layout, VK_NULL_HANDLE);
|
|
|
|
vkDestroyPipeline(get_device().get_handle(), device_address_pipeline, VK_NULL_HANDLE);
|
|
vkDestroyPipelineLayout(get_device().get_handle(), device_address_pipeline_layout, VK_NULL_HANDLE);
|
|
vkDestroyDescriptorSetLayout(get_device().get_handle(), device_address_descriptor_set_layout, VK_NULL_HANDLE);
|
|
|
|
device_address_buffer.reset();
|
|
|
|
cpu_staging_buffer.reset();
|
|
indirect_call_buffer.reset();
|
|
}
|
|
}
|
|
|
|
void MultiDrawIndirect::request_gpu_features(vkb::PhysicalDevice &gpu)
|
|
{
|
|
if (gpu.get_features().multiDrawIndirect)
|
|
{
|
|
gpu.get_mutable_requested_features().multiDrawIndirect = VK_TRUE;
|
|
m_supports_mdi = true;
|
|
}
|
|
|
|
if (gpu.get_features().drawIndirectFirstInstance)
|
|
{
|
|
gpu.get_mutable_requested_features().drawIndirectFirstInstance = VK_TRUE;
|
|
m_supports_first_instance = true;
|
|
}
|
|
|
|
// Query whether the device supports buffer device addresses
|
|
m_supports_buffer_device =
|
|
REQUEST_OPTIONAL_FEATURE(gpu, VkPhysicalDeviceVulkan12Features, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, bufferDeviceAddress);
|
|
|
|
// This sample references 128 objects. We need to check whether this is supported by the device
|
|
VkPhysicalDeviceProperties physical_device_properties;
|
|
vkGetPhysicalDeviceProperties(gpu.get_handle(), &physical_device_properties);
|
|
|
|
if (physical_device_properties.limits.maxPerStageDescriptorSamplers < 128)
|
|
{
|
|
throw std::runtime_error(fmt::format(FMT_STRING("This sample requires at least 128 descriptor samplers, but device only supports {:d}"), physical_device_properties.limits.maxPerStageDescriptorSamplers));
|
|
}
|
|
}
|
|
|
|
void MultiDrawIndirect::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 = {1.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 (size_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));
|
|
|
|
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);
|
|
|
|
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
|
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, nullptr);
|
|
VkDeviceSize offsets[1] = {0};
|
|
|
|
vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT16);
|
|
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer->get(), offsets);
|
|
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 1, 1, model_information_buffer->get(), offsets);
|
|
|
|
if (m_enable_mdi && m_supports_mdi)
|
|
{
|
|
vkCmdDrawIndexedIndirect(draw_cmd_buffers[i], indirect_call_buffer->get_handle(), 0, static_cast<uint32_t>(cpu_commands.size()), sizeof(cpu_commands[0]));
|
|
}
|
|
else
|
|
{
|
|
for (size_t j = 0; j < cpu_commands.size(); ++j)
|
|
{
|
|
vkCmdDrawIndexedIndirect(draw_cmd_buffers[i], indirect_call_buffer->get_handle(), j * sizeof(cpu_commands[0]), 1, sizeof(cpu_commands[0]));
|
|
}
|
|
}
|
|
|
|
draw_ui(draw_cmd_buffers[i]);
|
|
|
|
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
|
|
|
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
|
}
|
|
}
|
|
|
|
void MultiDrawIndirect::on_update_ui_overlay(vkb::Drawer &drawer)
|
|
{
|
|
if (drawer.header("GPU Rendering"))
|
|
{
|
|
static const std::array<const char *, 2> supported = {"Not supported", "Supported"};
|
|
drawer.text("Multi-Draw Indirect: %s", supported[this->m_supports_mdi]);
|
|
drawer.text("drawIndirectFirstInstance: %s", supported[this->m_supports_first_instance]);
|
|
drawer.text("Device buffer address: %s", supported[this->m_supports_buffer_device]);
|
|
|
|
drawer.text("");
|
|
uint32_t instance_count = 0;
|
|
|
|
if (render_mode == RenderMode::GPU || render_mode == RenderMode::GPU_DEVICE_ADDRESS)
|
|
{
|
|
// copy over the GPU-culled data to the CPU command so that we can count the number of instances
|
|
assert(!!indirect_call_buffer && !!cpu_staging_buffer && indirect_call_buffer->get_size() == cpu_staging_buffer->get_size());
|
|
assert(cpu_commands.size() * sizeof(cpu_commands[0]) == cpu_staging_buffer->get_size());
|
|
|
|
auto cmd = get_device().get_command_pool().request_command_buffer();
|
|
cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
|
|
cmd->copy_buffer(*indirect_call_buffer, *cpu_staging_buffer, cpu_staging_buffer->get_size());
|
|
cmd->end();
|
|
|
|
auto &queue = get_device().get_queue_by_flags(VK_QUEUE_COMPUTE_BIT, 0);
|
|
queue.submit(*cmd, get_device().get_fence_pool().request_fence());
|
|
get_device().get_fence_pool().wait();
|
|
|
|
memcpy(cpu_commands.data(), cpu_staging_buffer->get_data(), cpu_staging_buffer->get_size());
|
|
}
|
|
|
|
for (auto &&cmd : cpu_commands)
|
|
{
|
|
instance_count += cmd.instanceCount;
|
|
}
|
|
drawer.text("Instances: %d / %d", instance_count, 256);
|
|
|
|
m_requires_rebuild |= drawer.checkbox("Enable multi-draw", &m_enable_mdi);
|
|
drawer.checkbox("Freeze culling", &m_freeze_cull);
|
|
|
|
int32_t render_selection = render_mode;
|
|
if (drawer.combo_box("Cull mode", &render_selection, {"CPU", "GPU", "GPU Device Address"}))
|
|
{
|
|
m_requires_rebuild = true;
|
|
render_mode = static_cast<RenderMode>(render_selection);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MultiDrawIndirect::create_samplers()
|
|
{
|
|
VkSamplerCreateInfo sampler_info = vkb::initializers::sampler_create_info();
|
|
sampler_info.magFilter = VK_FILTER_LINEAR;
|
|
sampler_info.minFilter = VK_FILTER_LINEAR;
|
|
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
|
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
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, &sampler_linear));
|
|
|
|
// Some formats don't support linear filtering, so create a nearest filtered sampler as a fallback
|
|
sampler_info.magFilter = VK_FILTER_NEAREST;
|
|
sampler_info.minFilter = VK_FILTER_NEAREST;
|
|
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_info, nullptr, &sampler_nearest));
|
|
}
|
|
|
|
bool MultiDrawIndirect::prepare(const vkb::ApplicationOptions &options)
|
|
{
|
|
if (!ApiVulkanSample::prepare(options))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
camera.type = vkb::CameraType::FirstPerson;
|
|
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 0.001f, 512.0f);
|
|
camera.set_rotation(glm::vec3(-23.5, -45, 0));
|
|
camera.set_translation(glm::vec3(0, 0.5, -0.2));
|
|
|
|
if (!compute_queue)
|
|
{
|
|
compute_queue = &get_device().get_queue_by_flags(VK_QUEUE_COMPUTE_BIT, 0);
|
|
}
|
|
|
|
queue_families.clear();
|
|
for (auto &&queue_bit : {VK_QUEUE_GRAPHICS_BIT, VK_QUEUE_COMPUTE_BIT})
|
|
{
|
|
const auto index = get_device().get_queue_by_flags(queue_bit, 0).get_family_index();
|
|
if (std::find(queue_families.cbegin(), queue_families.cend(), index) == queue_families.cend())
|
|
{
|
|
queue_families.emplace_back(index);
|
|
}
|
|
}
|
|
|
|
create_samplers();
|
|
load_scene();
|
|
initialize_resources();
|
|
update_scene_uniform();
|
|
create_pipeline();
|
|
create_compute_pipeline();
|
|
initialize_descriptors();
|
|
build_command_buffers();
|
|
cpu_cull(); // initialize buffer
|
|
run_cull();
|
|
|
|
prepared = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void MultiDrawIndirect::load_scene()
|
|
{
|
|
const std::string scene_path = "scenes/vokselia/";
|
|
ApiVulkanSample::load_scene(scene_path + "vokselia.gltf");
|
|
|
|
assert(has_scene());
|
|
for (auto &&mesh : get_scene().get_components<vkb::sg::Mesh>())
|
|
{
|
|
const size_t texture_index = textures.size();
|
|
const auto &short_name = mesh->get_name();
|
|
auto image_name = scene_path + short_name + ".ktx";
|
|
auto image = vkb::sg::Image::load(image_name, image_name, vkb::sg::Image::Color);
|
|
|
|
image->create_vk_image(get_device());
|
|
Texture texture;
|
|
texture.n_mip_maps = static_cast<uint32_t>(image->get_mipmaps().size());
|
|
assert(texture.n_mip_maps == 1);
|
|
texture.image = std::make_unique<vkb::core::Image>(get_device(),
|
|
image->get_extent(),
|
|
image->get_format(),
|
|
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
|
VMA_MEMORY_USAGE_GPU_ONLY,
|
|
VK_SAMPLE_COUNT_1_BIT,
|
|
1,
|
|
1,
|
|
VK_IMAGE_TILING_OPTIMAL,
|
|
0);
|
|
|
|
auto data_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), image->get_data());
|
|
|
|
auto texture_cmd = get_device().get_command_pool().request_command_buffer();
|
|
texture_cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, VK_NULL_HANDLE);
|
|
|
|
VkImageSubresourceRange subresource_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
subresource_range.baseMipLevel = 0;
|
|
subresource_range.levelCount = texture.n_mip_maps;
|
|
|
|
vkb::image_layout_transition(
|
|
texture_cmd->get_handle(), texture.image->get_handle(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresource_range);
|
|
|
|
auto offsets = image->get_offsets();
|
|
VkBufferImageCopy region = {};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = 0;
|
|
region.imageSubresource.baseArrayLayer = 0;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = image->get_extent();
|
|
region.bufferOffset = offsets[0][0];
|
|
|
|
texture_cmd->copy_buffer_to_image(data_buffer, *texture.image, {region});
|
|
texture_cmd->end();
|
|
|
|
auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
|
|
queue.submit(*texture_cmd, get_device().get_fence_pool().request_fence());
|
|
get_device().get_fence_pool().wait();
|
|
get_device().get_fence_pool().reset();
|
|
|
|
texture.image_view = std::make_unique<vkb::core::ImageView>(*texture.image, VK_IMAGE_VIEW_TYPE_2D);
|
|
|
|
// Get the properties for the image format. We'll need to check whether a linear sampler is valid.
|
|
const VkFormatProperties fmtProps = get_device().get_gpu().get_format_properties(image->get_format());
|
|
|
|
VkDescriptorImageInfo image_descriptor;
|
|
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
image_descriptor.imageView = texture.image_view->get_handle();
|
|
image_descriptor.sampler = (fmtProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) ?
|
|
sampler_linear :
|
|
sampler_nearest;
|
|
image_descriptors.push_back(image_descriptor);
|
|
textures.emplace_back(std::move(texture));
|
|
|
|
for (auto &&sub_mesh : mesh->get_submeshes())
|
|
{
|
|
SceneModel model;
|
|
model.texture_index = texture_index;
|
|
|
|
auto pts = CopyBuffer<glm::vec3>{}(sub_mesh->vertex_buffers, "position");
|
|
auto uvs = CopyBuffer<glm::vec2>{}(sub_mesh->vertex_buffers, "texcoord_0");
|
|
assert(uvs.size() == pts.size());
|
|
|
|
model.vertices.resize(pts.size());
|
|
for (size_t i = 0; i < pts.size(); ++i)
|
|
{
|
|
model.vertices[i].pt = {pts[i].x, -pts[i].y, pts[i].z};
|
|
model.vertices[i].uv = uvs[i];
|
|
}
|
|
|
|
assert(sub_mesh->index_type == VK_INDEX_TYPE_UINT16);
|
|
auto buffer = sub_mesh->index_buffer.get();
|
|
if (buffer)
|
|
{
|
|
const size_t sz = buffer->get_size();
|
|
const size_t nTriangles = sz / sizeof(uint16_t) / 3;
|
|
model.triangles.resize(nTriangles);
|
|
auto ptr = buffer->get_data();
|
|
assert(!!ptr);
|
|
std::vector<uint16_t> temp_buffer(nTriangles * 3);
|
|
memcpy(temp_buffer.data(), ptr, nTriangles * 3 * sizeof(temp_buffer[0]));
|
|
model.triangles.resize(nTriangles);
|
|
for (size_t i = 0; i < nTriangles; ++i)
|
|
{
|
|
model.triangles[i] = {
|
|
static_cast<uint16_t>(temp_buffer[3 * i]),
|
|
static_cast<uint16_t>(temp_buffer[3 * i + 1]),
|
|
static_cast<uint16_t>(temp_buffer[3 * i + 2])};
|
|
}
|
|
}
|
|
model.bounding_sphere = BoundingSphere(pts);
|
|
models.emplace_back(std::move(model));
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<VkImage, VkImageSubresourceRange>> imagesAndRanges;
|
|
imagesAndRanges.reserve(textures.size());
|
|
for (auto const &texture : textures)
|
|
{
|
|
imagesAndRanges.emplace_back(
|
|
std::make_pair(texture.image->get_handle(), VkImageSubresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, texture.n_mip_maps, 0, 1}));
|
|
}
|
|
|
|
auto cmd = get_device().get_command_pool().request_command_buffer();
|
|
cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, VK_NULL_HANDLE);
|
|
vkb::image_layout_transition(cmd->get_handle(), imagesAndRanges, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
cmd->end();
|
|
auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
|
|
queue.submit(*cmd, get_device().get_fence_pool().request_fence());
|
|
get_device().get_fence_pool().wait();
|
|
}
|
|
|
|
void MultiDrawIndirect::initialize_resources()
|
|
{
|
|
size_t vertex_buffer_size = 0, index_buffer_size = 0;
|
|
const size_t model_buffer_size = models.size() * sizeof(SceneModel);
|
|
for (auto &&model : models)
|
|
{
|
|
model.vertex_buffer_offset = vertex_buffer_size;
|
|
model.index_buffer_offset = index_buffer_size;
|
|
|
|
vertex_buffer_size += model.vertices.size() * sizeof(Vertex);
|
|
index_buffer_size += model.triangles.size() * sizeof(model.triangles[0]);
|
|
}
|
|
|
|
assert(vertex_buffer_size && index_buffer_size && model_buffer_size);
|
|
auto staging_vertex_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), vertex_buffer_size, nullptr);
|
|
auto staging_index_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), index_buffer_size, nullptr);
|
|
auto staging_model_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), model_buffer_size, nullptr);
|
|
|
|
// We will store the GPU commands in the indirect call buffer
|
|
constexpr auto default_indirect_flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
auto indirect_flags = default_indirect_flags;
|
|
if (m_supports_buffer_device)
|
|
{
|
|
indirect_flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
|
}
|
|
indirect_call_buffer = std::make_unique<vkb::core::BufferC>(get_device(), models.size() * sizeof(VkDrawIndexedIndirectCommand), indirect_flags, VMA_MEMORY_USAGE_GPU_ONLY, VMA_ALLOCATION_CREATE_MAPPED_BIT, queue_families);
|
|
|
|
// Create a buffer containing the addresses of the indirect calls.
|
|
// In this sample, the order of the addresses will match that of the other buffers, but in general they could be in any order
|
|
const size_t address_buffer_size = sizeof(VkDeviceAddress);
|
|
auto staging_address_buffer = std::make_unique<vkb::core::BufferC>(get_device(), address_buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
if (m_supports_buffer_device)
|
|
{
|
|
auto *destPtr = (uint64_t *) staging_address_buffer->get_data();
|
|
|
|
VkBufferDeviceAddressInfoKHR address_info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR};
|
|
address_info.buffer = indirect_call_buffer->get_handle();
|
|
VkDeviceAddress srcPtr = vkGetBufferDeviceAddressKHR(get_device().get_handle(), &address_info);
|
|
|
|
*destPtr = srcPtr;
|
|
}
|
|
|
|
for (size_t i = 0; i < models.size(); ++i)
|
|
{
|
|
auto &model = models[i];
|
|
staging_vertex_buffer.update(model.vertices.data(), model.vertices.size() * sizeof(Vertex), model.vertex_buffer_offset);
|
|
staging_index_buffer.update(model.triangles.data(), model.triangles.size() * sizeof(model.triangles[0]), model.index_buffer_offset);
|
|
|
|
GpuModelInformation model_information;
|
|
model_information.bounding_sphere_center = model.bounding_sphere.center;
|
|
model_information.bounding_sphere_radius = model.bounding_sphere.radius;
|
|
model_information.texture_index = static_cast<uint32_t>(model.texture_index);
|
|
model_information.firstIndex = static_cast<uint32_t>(model.index_buffer_offset / (sizeof(model.triangles[0][0])));
|
|
model_information.indexCount = static_cast<uint32_t>(model.triangles.size());
|
|
staging_model_buffer.update(&model_information, sizeof(GpuModelInformation), i * sizeof(GpuModelInformation));
|
|
}
|
|
|
|
staging_vertex_buffer.flush();
|
|
staging_index_buffer.flush();
|
|
staging_model_buffer.flush();
|
|
|
|
auto cmd = get_device().get_command_pool().request_command_buffer();
|
|
cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, VK_NULL_HANDLE);
|
|
auto copy = [this, &cmd](vkb::core::BufferC &staging_buffer, VkBufferUsageFlags buffer_usage_flags) {
|
|
auto output_buffer = std::make_unique<vkb::core::BufferC>(get_device(), staging_buffer.get_size(), buffer_usage_flags | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY, VMA_ALLOCATION_CREATE_MAPPED_BIT, queue_families);
|
|
cmd->copy_buffer(staging_buffer, *output_buffer, staging_buffer.get_size());
|
|
|
|
vkb::BufferMemoryBarrier barrier;
|
|
barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
barrier.dst_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
|
barrier.src_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dst_access_mask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
|
cmd->buffer_memory_barrier(*output_buffer, 0, VK_WHOLE_SIZE, barrier);
|
|
return output_buffer;
|
|
};
|
|
vertex_buffer = copy(staging_vertex_buffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
|
index_buffer = copy(staging_index_buffer, VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
|
|
model_information_buffer = copy(staging_model_buffer, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
|
if (m_supports_buffer_device)
|
|
{
|
|
// In this sample, we use a staging buffer for the device address buffer (i.e. for device exclusive memory).
|
|
// However, since the size of each element (sizeof(uint64_t)) is smaller than the objects it's pointing to, it could instead use host-visible memory
|
|
// for fast referencing of the underlying data
|
|
device_address_buffer = copy(*staging_address_buffer, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
|
}
|
|
|
|
cmd->end();
|
|
auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
|
|
queue.submit(*cmd, get_device().get_fence_pool().request_fence());
|
|
get_device().get_fence_pool().wait();
|
|
}
|
|
|
|
void MultiDrawIndirect::create_pipeline()
|
|
{
|
|
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
|
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 6},
|
|
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 * static_cast<uint32_t>(textures.size())},
|
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 6}};
|
|
VkDescriptorPoolCreateInfo descriptor_pool_create_info = vkb::initializers::descriptor_pool_create_info(pool_sizes, 3);
|
|
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
|
|
|
// The model information will be used to index textures in the fragment shader,
|
|
// as well as perform frustum culling in the compute shader
|
|
VkDescriptorSetLayoutBinding model_information_binding{};
|
|
model_information_binding.binding = 0;
|
|
model_information_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
model_information_binding.descriptorCount = 1;
|
|
model_information_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
|
|
// This array of textures will be accessed via the instance ID
|
|
VkDescriptorSetLayoutBinding image_array_binding{};
|
|
image_array_binding.binding = 1;
|
|
image_array_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
image_array_binding.descriptorCount = static_cast<uint32_t>(textures.size());
|
|
image_array_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
VkDescriptorSetLayoutBinding scene_uniform_binding{};
|
|
scene_uniform_binding.binding = 2;
|
|
scene_uniform_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
scene_uniform_binding.descriptorCount = 1;
|
|
scene_uniform_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
|
|
|
|
VkDescriptorSetLayoutBinding command_buffer_binding{};
|
|
command_buffer_binding.binding = 3;
|
|
command_buffer_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
command_buffer_binding.descriptorCount = 1;
|
|
command_buffer_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
|
|
// Create descriptors
|
|
auto create_descriptors = [this](const std::vector<VkDescriptorSetLayoutBinding> &set_layout_bindings, VkDescriptorSetLayout &_descriptor_set_layout, VkPipelineLayout &_pipeline_layout) {
|
|
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));
|
|
};
|
|
|
|
// Render pipeline
|
|
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings = {model_information_binding, image_array_binding, scene_uniform_binding, command_buffer_binding};
|
|
create_descriptors(set_layout_bindings, descriptor_set_layout, pipeline_layout);
|
|
|
|
// Compute pipeline
|
|
// Note we don't include the texture array
|
|
std::vector<VkDescriptorSetLayoutBinding> gpu_compute_set_layout_bindings = {model_information_binding, scene_uniform_binding, command_buffer_binding};
|
|
create_descriptors(gpu_compute_set_layout_bindings, gpu_cull_descriptor_set_layout, gpu_cull_pipeline_layout);
|
|
|
|
// Device address pipeline
|
|
// Note that we don't bind the command buffer directly; instead, we use the references from the device addresses
|
|
// This will be used in the device address shader (cull_address.comp)
|
|
if (m_supports_buffer_device)
|
|
{
|
|
VkDescriptorSetLayoutBinding device_address_binding{};
|
|
device_address_binding.binding = 4;
|
|
device_address_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
device_address_binding.descriptorCount = 1;
|
|
device_address_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
std::vector<VkDescriptorSetLayoutBinding> device_address_layout_bindings = {model_information_binding, scene_uniform_binding, device_address_binding};
|
|
create_descriptors(device_address_layout_bindings, device_address_descriptor_set_layout, device_address_pipeline_layout);
|
|
}
|
|
|
|
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);
|
|
|
|
VkPipelineDepthStencilStateCreateInfo depth_stencil_state = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS);
|
|
depth_stencil_state.depthBoundsTestEnable = VK_FALSE;
|
|
depth_stencil_state.minDepthBounds = 0.f;
|
|
depth_stencil_state.maxDepthBounds = 1.f;
|
|
|
|
VkPipelineViewportStateCreateInfo viewport_state = vkb::initializers::pipeline_viewport_state_create_info(1, 1, 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);
|
|
|
|
VkPipelineMultisampleStateCreateInfo multisample_state = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT, 0);
|
|
|
|
// 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),
|
|
vkb::initializers::vertex_input_binding_description(1, sizeof(GpuModelInformation), VK_VERTEX_INPUT_RATE_INSTANCE),
|
|
};
|
|
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
|
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pt)),
|
|
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)),
|
|
vkb::initializers::vertex_input_attribute_description(1, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(GpuModelInformation, bounding_sphere_center)),
|
|
vkb::initializers::vertex_input_attribute_description(1, 3, VK_FORMAT_R32_SFLOAT, offsetof(GpuModelInformation, bounding_sphere_radius)),
|
|
vkb::initializers::vertex_input_attribute_description(1, 4, VK_FORMAT_R32_UINT, offsetof(GpuModelInformation, texture_index)),
|
|
};
|
|
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;
|
|
|
|
const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {
|
|
load_shader("multi_draw_indirect/multi_draw_indirect.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
|
|
load_shader("multi_draw_indirect/multi_draw_indirect.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)};
|
|
|
|
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));
|
|
}
|
|
|
|
void MultiDrawIndirect::create_compute_pipeline()
|
|
{
|
|
auto create = [this](VkPipelineLayout &layout, VkPipeline &_pipeline, const char *filename) {
|
|
VkComputePipelineCreateInfo compute_create_info = vkb::initializers::compute_pipeline_create_info(layout, 0);
|
|
compute_create_info.stage = load_shader(filename, VK_SHADER_STAGE_COMPUTE_BIT);
|
|
|
|
VK_CHECK(vkCreateComputePipelines(get_device().get_handle(), pipeline_cache, 1, &compute_create_info, nullptr, &_pipeline));
|
|
};
|
|
|
|
create(gpu_cull_pipeline_layout, gpu_cull_pipeline, "multi_draw_indirect/cull.comp.spv");
|
|
|
|
if (m_supports_buffer_device)
|
|
{
|
|
create(device_address_pipeline_layout, device_address_pipeline, "multi_draw_indirect/cull_address.comp.spv");
|
|
}
|
|
}
|
|
|
|
void MultiDrawIndirect::initialize_descriptors()
|
|
{
|
|
enum class Target
|
|
{
|
|
RenderPipeline,
|
|
ComputePipeline,
|
|
AddressPipeline
|
|
};
|
|
|
|
auto bind = [this](VkDescriptorSet &_descriptor_set, VkDescriptorSetLayout &_descriptor_set_layout, Target target) {
|
|
VkDescriptorSetAllocateInfo descriptor_set_allocate_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &_descriptor_set_layout, 1);
|
|
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_allocate_info, &_descriptor_set));
|
|
|
|
VkDescriptorBufferInfo model_buffer_descriptor = create_descriptor(*model_information_buffer);
|
|
VkWriteDescriptorSet model_write = vkb::initializers::write_descriptor_set(_descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &model_buffer_descriptor, 1);
|
|
|
|
VkWriteDescriptorSet texture_array_write = vkb::initializers::write_descriptor_set(_descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, image_descriptors.data(), static_cast<uint32_t>(image_descriptors.size()));
|
|
|
|
VkDescriptorBufferInfo scene_descriptor = create_descriptor(*scene_uniform_buffer);
|
|
VkWriteDescriptorSet scene_write = vkb::initializers::write_descriptor_set(_descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &scene_descriptor, 1);
|
|
|
|
VkDescriptorBufferInfo draw_command_descriptor = create_descriptor(*indirect_call_buffer);
|
|
VkWriteDescriptorSet draw_command_write = vkb::initializers::write_descriptor_set(_descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &draw_command_descriptor, 1);
|
|
|
|
VkDescriptorBufferInfo device_address_descriptor;
|
|
VkWriteDescriptorSet device_address_write;
|
|
if (m_supports_buffer_device && device_address_buffer)
|
|
{
|
|
device_address_descriptor = create_descriptor(*device_address_buffer);
|
|
device_address_write = vkb::initializers::write_descriptor_set(_descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4, &device_address_descriptor, 1);
|
|
}
|
|
|
|
std::vector<VkWriteDescriptorSet> write_descriptor_sets;
|
|
switch (target)
|
|
{
|
|
case Target::RenderPipeline:
|
|
write_descriptor_sets = {model_write, texture_array_write, scene_write, draw_command_write};
|
|
break;
|
|
case Target::ComputePipeline:
|
|
write_descriptor_sets = {model_write, scene_write, draw_command_write};
|
|
break;
|
|
case Target::AddressPipeline:
|
|
write_descriptor_sets = {model_write, scene_write, device_address_write};
|
|
break;
|
|
}
|
|
|
|
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, VK_NULL_HANDLE);
|
|
};
|
|
|
|
// render pipeline
|
|
bind(descriptor_set, descriptor_set_layout, Target::RenderPipeline);
|
|
|
|
// compute pipeline
|
|
bind(gpu_cull_descriptor_set, gpu_cull_descriptor_set_layout, Target::ComputePipeline);
|
|
|
|
// Device address pipeline
|
|
if (m_supports_buffer_device)
|
|
{
|
|
bind(device_address_descriptor_set, device_address_descriptor_set_layout, Target::AddressPipeline);
|
|
}
|
|
}
|
|
|
|
void MultiDrawIndirect::update_scene_uniform()
|
|
{
|
|
if (!scene_uniform_buffer)
|
|
{
|
|
scene_uniform_buffer = std::make_unique<vkb::core::BufferC>(
|
|
get_device(), sizeof(SceneUniform), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, VMA_ALLOCATION_CREATE_MAPPED_BIT, queue_families);
|
|
}
|
|
scene_uniform.proj = camera.matrices.perspective;
|
|
scene_uniform.view = camera.matrices.view;
|
|
scene_uniform.proj_view = scene_uniform.proj * scene_uniform.view;
|
|
scene_uniform.model_count = static_cast<uint32_t>(models.size());
|
|
|
|
scene_uniform_buffer->update(&scene_uniform, sizeof(scene_uniform), 0);
|
|
|
|
scene_uniform_buffer->flush();
|
|
}
|
|
|
|
void MultiDrawIndirect::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 MultiDrawIndirect::render(float delta_time)
|
|
{
|
|
if (!prepared)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (render_mode == GPU_DEVICE_ADDRESS && !m_supports_buffer_device)
|
|
{
|
|
render_mode = GPU;
|
|
}
|
|
|
|
if (m_requires_rebuild)
|
|
{
|
|
rebuild_command_buffers();
|
|
m_requires_rebuild = false;
|
|
}
|
|
|
|
draw();
|
|
|
|
update_scene_uniform();
|
|
|
|
if (!m_freeze_cull)
|
|
{
|
|
run_cull();
|
|
}
|
|
get_device().get_fence_pool().wait();
|
|
get_device().get_fence_pool().reset();
|
|
}
|
|
|
|
void MultiDrawIndirect::finish()
|
|
{
|
|
}
|
|
|
|
void MultiDrawIndirect::run_cull()
|
|
{
|
|
switch (render_mode)
|
|
{
|
|
case RenderMode::CPU:
|
|
cpu_cull();
|
|
break;
|
|
case RenderMode::GPU:
|
|
case RenderMode::GPU_DEVICE_ADDRESS:
|
|
run_gpu_cull();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MultiDrawIndirect::run_gpu_cull()
|
|
{
|
|
assert(!!gpu_cull_pipeline);
|
|
auto cmd = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY);
|
|
VkCommandBufferBeginInfo begin = vkb::initializers::command_buffer_begin_info();
|
|
|
|
vkBeginCommandBuffer(cmd, &begin);
|
|
auto bind = [&cmd](VkPipeline &_pipeline, VkPipelineLayout &_pipeline_layout, VkDescriptorSet &_descriptor_set) {
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _pipeline);
|
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _pipeline_layout, 0, 1, &_descriptor_set, 0, nullptr);
|
|
};
|
|
|
|
if (render_mode == RenderMode::GPU)
|
|
{
|
|
bind(gpu_cull_pipeline, gpu_cull_pipeline_layout, gpu_cull_descriptor_set);
|
|
}
|
|
else
|
|
{
|
|
bind(device_address_pipeline, device_address_pipeline_layout, device_address_descriptor_set);
|
|
}
|
|
|
|
const uint32_t dispatch_x = !models.empty() ? 1 + static_cast<uint32_t>((models.size() - 1) / 64) : 1;
|
|
vkCmdDispatch(cmd, dispatch_x, 1, 1);
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
VkSubmitInfo submit = vkb::initializers::submit_info();
|
|
submit.commandBufferCount = 1;
|
|
submit.pCommandBuffers = &cmd;
|
|
|
|
vkQueueSubmit(compute_queue->get_handle(), 1, &submit, get_device().get_fence_pool().request_fence());
|
|
get_device().get_fence_pool().wait();
|
|
get_device().get_fence_pool().reset();
|
|
// we're done so dealloc it from the pool.
|
|
vkFreeCommandBuffers(get_device().get_handle(), get_device().get_command_pool().get_handle(), 1, &cmd);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/**
|
|
* @brief Test for visibility using bounding sphere.
|
|
* See https://www.gamedevs.org/uploads/fast-extraction-viewing-frustum-planes-from-world-view-projection-matrix.pdf
|
|
*/
|
|
struct VisibilityTester
|
|
{
|
|
explicit VisibilityTester(glm::mat4x4 view_matrix) :
|
|
planes(get_view_planes(view_matrix))
|
|
{}
|
|
std::array<glm::vec4, 6> planes;
|
|
|
|
static std::array<glm::vec4, 6> get_view_planes(const glm::mat4x4 &mat)
|
|
{
|
|
using namespace glm;
|
|
std::array<vec4, 6> out{};
|
|
for (auto i = 0; i < 3; ++i)
|
|
{
|
|
for (size_t j = 0; j < 2; ++j)
|
|
{
|
|
const float sign = j ? 1.f : -1.f;
|
|
for (auto k = 0; k < 4; ++k)
|
|
{
|
|
out[2 * i + j][k] = mat[k][3] + sign * mat[k][i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// normalize plane; see Appendix A.2
|
|
for (auto &&plane : out)
|
|
{
|
|
plane /= static_cast<float>(length(vec3(plane.xyz)));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
bool is_visible(glm::vec3 origin, float radius) const
|
|
{
|
|
using namespace glm;
|
|
std::array<int, 4> V{0, 1, 4, 5};
|
|
return std::ranges::all_of(V, [this, origin, radius](size_t i) {
|
|
const auto &plane = planes[i];
|
|
return dot(origin, vec3(plane.xyz)) + plane.w + radius >= 0;
|
|
});
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void MultiDrawIndirect::cpu_cull()
|
|
{
|
|
cpu_commands.resize(models.size());
|
|
|
|
VisibilityTester tester(scene_uniform.proj * scene_uniform.view);
|
|
for (size_t i = 0; i < models.size(); ++i)
|
|
{
|
|
// we control visibility by changing the instance count
|
|
auto &model = models[i];
|
|
VkDrawIndexedIndirectCommand cmd{};
|
|
cmd.firstIndex = static_cast<uint32_t>(model.index_buffer_offset / (sizeof(model.triangles[0][0])));
|
|
cmd.indexCount = static_cast<uint32_t>(model.triangles.size()) * 3;
|
|
cmd.vertexOffset = static_cast<int32_t>(model.vertex_buffer_offset / sizeof(Vertex));
|
|
cmd.firstInstance = static_cast<uint32_t>(i);
|
|
cmd.instanceCount = tester.is_visible(model.bounding_sphere.center, model.bounding_sphere.radius);
|
|
cpu_commands[i] = cmd;
|
|
}
|
|
|
|
const auto call_buffer_size = cpu_commands.size() * sizeof(cpu_commands[0]);
|
|
assert(!!indirect_call_buffer && indirect_call_buffer->get_size() == call_buffer_size);
|
|
|
|
if (!cpu_staging_buffer || cpu_staging_buffer->get_size() != call_buffer_size)
|
|
{
|
|
cpu_staging_buffer = std::make_unique<vkb::core::BufferC>(get_device(), models.size() * sizeof(VkDrawIndexedIndirectCommand), VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
}
|
|
|
|
cpu_staging_buffer->update(cpu_commands.data(), call_buffer_size, 0);
|
|
cpu_staging_buffer->flush();
|
|
|
|
auto transfer_cmd = get_device().get_command_pool().request_command_buffer();
|
|
transfer_cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, VK_NULL_HANDLE);
|
|
transfer_cmd->copy_buffer(*cpu_staging_buffer, *indirect_call_buffer, call_buffer_size);
|
|
transfer_cmd->end();
|
|
|
|
auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
|
|
queue.submit(*transfer_cmd, get_device().get_fence_pool().request_fence());
|
|
get_device().get_fence_pool().wait();
|
|
}
|
|
|
|
std::unique_ptr<vkb::VulkanSampleC> create_multi_draw_indirect()
|
|
{
|
|
return std::make_unique<MultiDrawIndirect>();
|
|
}
|
|
|
|
MultiDrawIndirect::BoundingSphere::BoundingSphere(const std::vector<glm::vec3> &pts)
|
|
{
|
|
if (pts.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// This is a simple method of calculating a bounding sphere.
|
|
// For finding an optimal bounding sphere, see Welzl's algorithm
|
|
this->center = {0, 0, 0};
|
|
for (auto &&pt : pts)
|
|
{
|
|
this->center += pt;
|
|
}
|
|
this->center /= static_cast<float>(pts.size());
|
|
this->radius = glm::distance2(pts[0], this->center);
|
|
for (size_t i = 1; i < pts.size(); ++i)
|
|
{
|
|
this->radius = std::max(this->radius, glm::distance2(pts[i], this->center));
|
|
}
|
|
this->radius = std::nextafter(sqrtf(this->radius), std::numeric_limits<float>::max());
|
|
}
|