584 lines
29 KiB
C++
584 lines
29 KiB
C++
/* Copyright (c) 2021-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 "descriptor_indexing.h"
|
|
|
|
static constexpr uint32_t NumDescriptorsStreaming = 2048;
|
|
static constexpr uint32_t NumDescriptorsNonUniform = 64;
|
|
|
|
DescriptorIndexing::DescriptorIndexing()
|
|
{
|
|
title = "Descriptor indexing";
|
|
|
|
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
|
add_device_extension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
|
|
add_device_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME);
|
|
|
|
// Works around a validation layer bug with descriptor pool allocation with VARIABLE_COUNT.
|
|
// See: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2350.
|
|
add_device_extension(VK_KHR_MAINTENANCE1_EXTENSION_NAME);
|
|
|
|
#if defined(PLATFORM__MACOS)
|
|
// On Apple use layer setting to enable MoltenVK's Metal argument buffers - needed for descriptor indexing/scaling
|
|
add_instance_extension(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME, /*optional*/ true);
|
|
|
|
VkLayerSettingEXT layerSetting;
|
|
layerSetting.pLayerName = "MoltenVK";
|
|
layerSetting.pSettingName = "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS";
|
|
layerSetting.type = VK_LAYER_SETTING_TYPE_INT32_EXT;
|
|
layerSetting.valueCount = 1;
|
|
|
|
// Make this static so layer setting reference remains valid after leaving constructor scope
|
|
static const int32_t useMetalArgumentBuffers = 1;
|
|
layerSetting.pValues = &useMetalArgumentBuffers;
|
|
|
|
add_layer_setting(layerSetting);
|
|
#endif
|
|
}
|
|
|
|
DescriptorIndexing::~DescriptorIndexing()
|
|
{
|
|
if (has_device())
|
|
{
|
|
VkDevice vk_device = get_device().get_handle();
|
|
vkDestroyPipelineLayout(vk_device, pipelines.pipeline_layout, nullptr);
|
|
vkDestroyPipeline(vk_device, pipelines.non_uniform_indexing, nullptr);
|
|
vkDestroyPipeline(vk_device, pipelines.update_after_bind, nullptr);
|
|
|
|
vkDestroyDescriptorSetLayout(vk_device, descriptors.set_layout, nullptr);
|
|
vkDestroyDescriptorPool(vk_device, descriptors.descriptor_pool, nullptr);
|
|
|
|
vkDestroyDescriptorSetLayout(vk_device, sampler.set_layout, nullptr);
|
|
vkDestroySampler(vk_device, sampler.sampler, nullptr);
|
|
vkDestroyDescriptorPool(vk_device, sampler.descriptor_pool, nullptr);
|
|
|
|
for (auto &image : test_images)
|
|
{
|
|
vkDestroyImageView(vk_device, image.image_view, nullptr);
|
|
vkDestroyImage(vk_device, image.image, nullptr);
|
|
vkFreeMemory(vk_device, image.memory, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DescriptorIndexing::build_command_buffers()
|
|
{
|
|
// We build command buffers every frame in render(), so don't build anything here.
|
|
}
|
|
|
|
void DescriptorIndexing::render(float delta_time)
|
|
{
|
|
ApiVulkanSample::prepare_frame();
|
|
|
|
VK_CHECK(vkWaitForFences(get_device().get_handle(), 1, &wait_fences[current_buffer], VK_TRUE, UINT64_MAX));
|
|
VK_CHECK(vkResetFences(get_device().get_handle(), 1, &wait_fences[current_buffer]));
|
|
|
|
VkViewport viewport = {0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f};
|
|
VkRect2D scissor = {{0, 0}, {width, height}};
|
|
|
|
recreate_current_command_buffer();
|
|
auto cmd = draw_cmd_buffers[current_buffer];
|
|
auto begin_info = vkb::initializers::command_buffer_begin_info();
|
|
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(cmd, &begin_info);
|
|
|
|
VkRenderPassBeginInfo render_pass_begin = vkb::initializers::render_pass_begin_info();
|
|
render_pass_begin.renderPass = render_pass;
|
|
render_pass_begin.renderArea.extent.width = width;
|
|
render_pass_begin.renderArea.extent.height = height;
|
|
render_pass_begin.clearValueCount = 2;
|
|
VkClearValue clears[2] = {};
|
|
clears[0].color.float32[0] = 0.033f;
|
|
clears[0].color.float32[1] = 0.073f;
|
|
clears[0].color.float32[2] = 0.133f;
|
|
render_pass_begin.pClearValues = clears;
|
|
render_pass_begin.framebuffer = framebuffers[current_buffer];
|
|
|
|
vkCmdBeginRenderPass(cmd, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
// First, draw all textures with nonuniform indexing. Each instance will sample from its own texture.
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.non_uniform_indexing);
|
|
|
|
accumulated_time += 0.2f * delta_time;
|
|
accumulated_time = glm::fract(accumulated_time);
|
|
float phase = glm::two_pi<float>() * accumulated_time;
|
|
vkCmdPushConstants(cmd, pipelines.pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(uint32_t), &phase);
|
|
|
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.pipeline_layout, 0, 1, &descriptors.descriptor_set_nonuniform, 0, nullptr);
|
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.pipeline_layout, 1, 1, &sampler.descriptor_set, 0, nullptr);
|
|
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
|
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
|
vkCmdDraw(cmd, 4, NumDescriptorsNonUniform, 0, 0);
|
|
|
|
// The update-after-bind style, i.e. "streamed" descriptors. We bind the descriptor set once, and update descriptors as we go.
|
|
// With update-after-bind we can update the descriptor set from multiple threads, and we can update descriptors while the descriptor set is bound.
|
|
// We can update descriptors at any time, as long as the GPU is not actually accessing the descriptor.
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.update_after_bind);
|
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.pipeline_layout, 0, 1, &descriptors.descriptor_set_update_after_bind, 0, nullptr);
|
|
|
|
for (unsigned i = 0; i < NumDescriptorsNonUniform; i++)
|
|
{
|
|
VkDescriptorImageInfo image_info = vkb::initializers::descriptor_image_info(VK_NULL_HANDLE, test_images[i].image_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
VkWriteDescriptorSet write = vkb::initializers::write_descriptor_set(descriptors.descriptor_set_update_after_bind, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 0, &image_info);
|
|
|
|
// One way we can use VK_EXT_descriptor_indexing is to treat the update-after-bind descriptor set as a ring buffer where we write descriptors,
|
|
// and we use push constants as a way to index into the "bindless" descriptor set.
|
|
write.dstArrayElement = descriptor_offset;
|
|
vkCmdPushConstants(cmd, pipelines.pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(uint32_t), sizeof(uint32_t), &descriptor_offset);
|
|
descriptor_offset = (descriptor_offset + 1) % NumDescriptorsStreaming;
|
|
vkUpdateDescriptorSets(get_device().get_handle(), 1, &write, 0, nullptr);
|
|
|
|
// We can use base instance as a way to offset gl_InstanceIndex in a shader.
|
|
// This can also be a nice way to pass down an offset for bindless purposes in vertex shaders that does not consume a push constant.
|
|
// In this case however, we only use the instance offset to place the textures where we expect
|
|
// and we cannot directly access gl_InstanceIndex in fragment shaders.
|
|
vkCmdDraw(cmd, 4, 1, 0, i);
|
|
}
|
|
|
|
draw_ui(cmd);
|
|
|
|
vkCmdEndRenderPass(cmd);
|
|
|
|
VK_CHECK(vkEndCommandBuffer(cmd));
|
|
submit_info.commandBufferCount = 1;
|
|
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
|
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, wait_fences[current_buffer]));
|
|
ApiVulkanSample::submit_frame();
|
|
}
|
|
|
|
void DescriptorIndexing::on_update_ui_overlay(vkb::Drawer &drawer)
|
|
{
|
|
if (drawer.header("Device properties"))
|
|
{
|
|
// Display some common properties. Only bother with sampled image since that's what we're using here.
|
|
drawer.text("maxDescriptorSetUpdateAfterBindSampledImages: %u", descriptor_indexing_properties.maxDescriptorSetUpdateAfterBindSampledImages);
|
|
drawer.text("maxPerStageUpdateAfterBindResources: %u", descriptor_indexing_properties.maxPerStageUpdateAfterBindResources);
|
|
drawer.text("quadDivergentImplicitLod: %u", descriptor_indexing_properties.quadDivergentImplicitLod);
|
|
drawer.text("shaderSampledImageArrayNonUniformIndexingNative: %u", descriptor_indexing_properties.shaderSampledImageArrayNonUniformIndexingNative);
|
|
drawer.text("maxUpdateAfterBindDescriptorsInAllPools: %u", descriptor_indexing_properties.maxUpdateAfterBindDescriptorsInAllPools);
|
|
}
|
|
}
|
|
|
|
void DescriptorIndexing::create_immutable_sampler_descriptor_set()
|
|
{
|
|
// Calculate valid filter
|
|
VkFilter filter = VK_FILTER_LINEAR;
|
|
vkb::make_filters_valid(get_device().get_gpu().get_handle(), format, &filter);
|
|
|
|
// The common case for bindless is to have an array of sampled images, not combined image sampler.
|
|
// It is more efficient to use a single sampler instead, and we can just use a single immutable sampler for this purpose.
|
|
// Create the sampler, descriptor set layout and allocate an immutable descriptor set.
|
|
VkSamplerCreateInfo create_info = vkb::initializers::sampler_create_info();
|
|
create_info.minFilter = filter;
|
|
create_info.magFilter = filter;
|
|
create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
create_info.maxLod = VK_LOD_CLAMP_NONE;
|
|
|
|
VK_CHECK(vkCreateSampler(get_device().get_handle(), &create_info, nullptr, &sampler.sampler));
|
|
|
|
VkDescriptorSetLayoutBinding binding = vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
|
|
binding.pImmutableSamplers = &sampler.sampler;
|
|
|
|
VkDescriptorSetLayoutCreateInfo set_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(&binding, 1);
|
|
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &set_layout_create_info, nullptr, &sampler.set_layout));
|
|
|
|
VkDescriptorPoolSize pool_size = vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_SAMPLER, 1);
|
|
VkDescriptorPoolCreateInfo pool = vkb::initializers::descriptor_pool_create_info(1, &pool_size, 1);
|
|
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &pool, nullptr, &sampler.descriptor_pool));
|
|
|
|
VkDescriptorSetAllocateInfo allocate_info = vkb::initializers::descriptor_set_allocate_info(sampler.descriptor_pool, &sampler.set_layout, 1);
|
|
|
|
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &allocate_info, &sampler.descriptor_set));
|
|
}
|
|
|
|
void DescriptorIndexing::create_bindless_descriptors()
|
|
{
|
|
uint32_t descriptorCount = descriptor_indexing_properties.maxDescriptorSetUpdateAfterBindSampledImages;
|
|
|
|
#if defined(PLATFORM__MACOS)
|
|
// On Apple Vulkan API <= 1.2.283 variable descriptor counts don't work, use max expected count instead. Fixed in later versions.
|
|
if (get_device().get_gpu().get_properties().apiVersion <= VK_MAKE_API_VERSION(0, 1, 2, 283))
|
|
{
|
|
descriptorCount = std::max(NumDescriptorsStreaming, NumDescriptorsNonUniform);
|
|
}
|
|
#endif
|
|
|
|
VkDescriptorSetLayoutBinding binding = vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT, 0, descriptorCount);
|
|
VkDescriptorSetLayoutCreateInfo set_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(&binding, 1);
|
|
|
|
// We're going to use update-after-bind, so we need to make sure the flag is set correctly in the set layout.
|
|
// These sets need to be allocated with UPDATE_AFTER_BIND pools later.
|
|
set_layout_create_info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT;
|
|
|
|
// We're going to use the full flexibility VK_EXT_descriptor_indexing allows us, in order, these binding flags express that we can:
|
|
// - Use a variable amount of descriptors in an array. This is extremely useful when using VK_EXT_descriptor_indexing, since we do not have to
|
|
// allocate a fixed amount of descriptors for each descriptor set. In many cases, it is far more flexible to use runtime sized descriptor arrays.
|
|
// The descriptorCount in the descriptor set layout now just expresses an upper bound.
|
|
// When we later allocate the descriptor set, we can declare how large we want the array to be.
|
|
// - Partially bound means that we don't have to bind every descriptor. This is critical if we want to make use of descriptor "streaming".
|
|
// A descriptor only has to be bound if it is actually used by a shader.
|
|
// - Update-after-bind is another critical component of descriptor indexing,
|
|
// which allows us to update descriptors after a descriptor set has been bound to a command buffer.
|
|
// This is critical for streaming descriptors, but it also relaxed threading requirements.
|
|
// Multiple threads can update descriptors concurrently on the same descriptor set.
|
|
// - Update-Unused-While-Pending is somewhat subtle, and allows you to update a descriptor while a command buffer is executing.
|
|
// The only restriction is that the descriptor cannot actually be accessed by the GPU.
|
|
|
|
// Typically, if you're using descriptor indexing, you will want to use all four of these, but all of these are separate feature bits.
|
|
const VkDescriptorBindingFlagsEXT flags =
|
|
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT |
|
|
VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT |
|
|
VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT |
|
|
VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT;
|
|
|
|
// In unextended Vulkan, there is no way to pass down flags to a binding, so we're going to do so via a pNext.
|
|
// Each pBinding has a corresponding pBindingFlags.
|
|
VkDescriptorSetLayoutBindingFlagsCreateInfoEXT binding_flags{};
|
|
binding_flags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT;
|
|
binding_flags.bindingCount = 1;
|
|
binding_flags.pBindingFlags = &flags;
|
|
set_layout_create_info.pNext = &binding_flags;
|
|
|
|
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &set_layout_create_info, nullptr, &descriptors.set_layout));
|
|
|
|
// We're going to allocate two separate descriptor sets from the same pool, and here VARIABLE_DESCRIPTOR_COUNT comes in handy!
|
|
// For the non-uniform indexing part, we allocate few descriptors, and for the streaming case, we allocate a fairly large ring buffer of descriptors we can play around with.
|
|
uint32_t poolCount = NumDescriptorsStreaming + NumDescriptorsNonUniform;
|
|
|
|
#if defined(PLATFORM__MACOS)
|
|
// On Apple Vulkan API <= 1.2.283 variable descriptor counts don't work, use pool size of max expected count x 2 (for 2 allocations). Fixed in later versions.
|
|
if (get_device().get_gpu().get_properties().apiVersion <= VK_MAKE_API_VERSION(0, 1, 2, 283))
|
|
{
|
|
poolCount = std::max(NumDescriptorsStreaming, NumDescriptorsNonUniform) * 2;
|
|
}
|
|
#endif
|
|
|
|
VkDescriptorPoolSize pool_size = vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, poolCount);
|
|
VkDescriptorPoolCreateInfo pool = vkb::initializers::descriptor_pool_create_info(1, &pool_size, 2);
|
|
|
|
// The pool is marked update-after-bind. Be aware that there is a global limit to the number of descriptors can be allocated at any one time.
|
|
// UPDATE_AFTER_BIND descriptors is somewhat of a precious resource, but min-spec in Vulkan is at least 500k descriptors, which should be more than enough.
|
|
pool.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT;
|
|
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &pool, nullptr, &descriptors.descriptor_pool));
|
|
|
|
VkDescriptorSetAllocateInfo allocate_info = vkb::initializers::descriptor_set_allocate_info(descriptors.descriptor_pool, &descriptors.set_layout, 1);
|
|
|
|
// Just like descriptor flags, for each descriptor set we allocate, we can describe how large the descriptor array should be.
|
|
VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variable_info{};
|
|
variable_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT;
|
|
variable_info.descriptorSetCount = 1;
|
|
allocate_info.pNext = &variable_info;
|
|
|
|
variable_info.pDescriptorCounts = &NumDescriptorsStreaming;
|
|
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &allocate_info, &descriptors.descriptor_set_update_after_bind));
|
|
variable_info.pDescriptorCounts = &NumDescriptorsNonUniform;
|
|
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &allocate_info, &descriptors.descriptor_set_nonuniform));
|
|
}
|
|
|
|
void DescriptorIndexing::create_pipelines()
|
|
{
|
|
VkDescriptorSetLayout set_layouts[] = {descriptors.set_layout, sampler.set_layout};
|
|
VkPipelineLayoutCreateInfo layout_create_info = vkb::initializers::pipeline_layout_create_info(set_layouts, 2);
|
|
|
|
// To vertex shader we pass a phase to rotate the quads.
|
|
// To fragment shader we pass down an index, which is used to access the descriptor array.
|
|
const std::vector<VkPushConstantRange> ranges = {
|
|
vkb::initializers::push_constant_range(VK_SHADER_STAGE_VERTEX_BIT, sizeof(uint32_t), 0),
|
|
vkb::initializers::push_constant_range(VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(uint32_t), sizeof(uint32_t)),
|
|
};
|
|
layout_create_info.pushConstantRangeCount = static_cast<uint32_t>(ranges.size());
|
|
layout_create_info.pPushConstantRanges = ranges.data();
|
|
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &layout_create_info, nullptr, &pipelines.pipeline_layout));
|
|
|
|
VkGraphicsPipelineCreateInfo info{};
|
|
VkPipelineShaderStageCreateInfo stages[2];
|
|
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
|
|
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_STRIP, 0, VK_FALSE);
|
|
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
|
vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, 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);
|
|
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);
|
|
|
|
info.pVertexInputState = &vertex_input_state;
|
|
info.pInputAssemblyState = &input_assembly_state;
|
|
info.pRasterizationState = &rasterization_state;
|
|
info.pColorBlendState = &color_blend_state;
|
|
info.pDepthStencilState = &depth_stencil_state;
|
|
info.pViewportState = &viewport_state;
|
|
info.pMultisampleState = &multisample_state;
|
|
info.pDynamicState = &dynamic_state;
|
|
info.layout = pipelines.pipeline_layout;
|
|
info.renderPass = render_pass;
|
|
|
|
info.pStages = stages;
|
|
info.stageCount = 2;
|
|
|
|
stages[0] = load_shader("descriptor_indexing", "nonuniform-quads.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
|
stages[1] = load_shader("descriptor_indexing", "nonuniform-quads.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &info, nullptr, &pipelines.non_uniform_indexing));
|
|
|
|
stages[0] = load_shader("descriptor_indexing", "update-after-bind-quads.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
|
stages[1] = load_shader("descriptor_indexing", "update-after-bind-quads.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &info, nullptr, &pipelines.update_after_bind));
|
|
}
|
|
|
|
DescriptorIndexing::TestImage DescriptorIndexing::create_image(const float rgb[3], unsigned image_seed)
|
|
{
|
|
// Fairly basic setup, generate some random textures so we can visualize that we are sampling many different textures.
|
|
// Note: since we're creating the texture data ourselves, it will already be in linear colorspace so we set the format
|
|
// as unorm, not sRGB.
|
|
DescriptorIndexing::TestImage test_image;
|
|
|
|
VkImageCreateInfo image_info = vkb::initializers::image_create_info();
|
|
image_info.format = format;
|
|
image_info.extent = {16, 16, 1};
|
|
image_info.mipLevels = 1;
|
|
image_info.arrayLayers = 1;
|
|
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
|
|
VK_CHECK(vkCreateImage(get_device().get_handle(), &image_info, nullptr, &test_image.image));
|
|
|
|
VkMemoryAllocateInfo memory_allocation_info = vkb::initializers::memory_allocate_info();
|
|
VkMemoryRequirements memory_requirements;
|
|
|
|
vkGetImageMemoryRequirements(get_device().get_handle(), test_image.image, &memory_requirements);
|
|
memory_allocation_info.allocationSize = memory_requirements.size;
|
|
memory_allocation_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_allocation_info, nullptr, &test_image.memory));
|
|
VK_CHECK(vkBindImageMemory(get_device().get_handle(), test_image.image, test_image.memory, 0));
|
|
|
|
VkImageViewCreateInfo image_view = vkb::initializers::image_view_create_info();
|
|
image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
image_view.format = format;
|
|
image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
image_view.subresourceRange.baseMipLevel = 0;
|
|
image_view.subresourceRange.levelCount = 1;
|
|
image_view.subresourceRange.baseArrayLayer = 0;
|
|
image_view.subresourceRange.layerCount = 1;
|
|
image_view.image = test_image.image;
|
|
VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view, nullptr, &test_image.image_view));
|
|
|
|
auto staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), image_info.extent.width * image_info.extent.height * sizeof(uint32_t), nullptr);
|
|
|
|
// Generate a random texture.
|
|
// Fairly simple, create different colors and some different patterns.
|
|
uint8_t *buffer = staging_buffer.map();
|
|
for (uint32_t y = 0; y < image_info.extent.height; y++)
|
|
{
|
|
for (uint32_t x = 0; x < image_info.extent.width; x++)
|
|
{
|
|
uint8_t *rgba = buffer + 4 * (y * image_info.extent.width + x);
|
|
|
|
const auto float_to_unorm8 = [](float v) -> uint8_t {
|
|
v *= 255.0f;
|
|
int rounded = static_cast<int>(v + 0.5f);
|
|
if (rounded < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (rounded > 255)
|
|
{
|
|
return 255;
|
|
}
|
|
else
|
|
{
|
|
return static_cast<uint8_t>(rounded);
|
|
}
|
|
};
|
|
|
|
uint32_t pattern;
|
|
switch (image_seed & 3u)
|
|
{
|
|
default:
|
|
{
|
|
// Checkerboard
|
|
pattern = ((x >> 2u) ^ (y >> 2u)) & 1u;
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
// Horizontal stripes
|
|
pattern = (x >> 2u) & 1u;
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
// Vertical stripes
|
|
pattern = (y >> 2u) & 1u;
|
|
break;
|
|
}
|
|
|
|
case 3:
|
|
{
|
|
// Diagonal stripes
|
|
pattern = ((x + y) >> 2u) & 1u;
|
|
break;
|
|
}
|
|
}
|
|
|
|
float pattern_color = pattern ? 0.25f : 1.0f;
|
|
|
|
for (unsigned i = 0; i < 3; i++)
|
|
{
|
|
// Add in some random noise for good measure so we're sure we're not sampling the exact same texture over and over.
|
|
rgba[i] = float_to_unorm8(pattern_color * rgb[i] + distribution(rnd));
|
|
}
|
|
rgba[3] = 0xff;
|
|
}
|
|
}
|
|
staging_buffer.flush();
|
|
staging_buffer.unmap();
|
|
|
|
auto cmd = get_device().get_command_pool().request_command_buffer();
|
|
cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
|
|
|
|
vkb::image_layout_transition(cmd->get_handle(), test_image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
|
|
VkBufferImageCopy copy_info{};
|
|
copy_info.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
copy_info.imageExtent = image_info.extent;
|
|
vkCmdCopyBufferToImage(cmd->get_handle(), staging_buffer.get_handle(), test_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_info);
|
|
|
|
vkb::image_layout_transition(cmd->get_handle(), test_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
|
|
cmd->end();
|
|
|
|
// Not very optimal, but it's the simplest solution.
|
|
auto const &graphicsQueue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
|
|
graphicsQueue.submit(*cmd, VK_NULL_HANDLE);
|
|
graphicsQueue.wait_idle();
|
|
|
|
return test_image;
|
|
}
|
|
|
|
void DescriptorIndexing::create_images()
|
|
{
|
|
std::uniform_real_distribution<float> color_distribution{0.2f, 0.8f};
|
|
float colors[NumDescriptorsNonUniform][3];
|
|
|
|
for (unsigned i = 0; i < NumDescriptorsNonUniform; i++)
|
|
{
|
|
for (unsigned j = 0; j < 3; j++)
|
|
{
|
|
colors[i][j] = color_distribution(rnd);
|
|
}
|
|
}
|
|
|
|
test_images.reserve(NumDescriptorsNonUniform);
|
|
for (unsigned i = 0; i < NumDescriptorsNonUniform; i++)
|
|
{
|
|
test_images.push_back(create_image(colors[i], i));
|
|
}
|
|
|
|
// For the non-uniform case, we're going to access every texture in a single draw call,
|
|
// prepare a descriptor set with all textures prepared ahead of time.
|
|
for (unsigned i = 0; i < NumDescriptorsNonUniform; i++)
|
|
{
|
|
VkDescriptorImageInfo image_info = vkb::initializers::descriptor_image_info(VK_NULL_HANDLE, test_images[i].image_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
VkWriteDescriptorSet write = vkb::initializers::write_descriptor_set(descriptors.descriptor_set_nonuniform, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 0, &image_info);
|
|
write.dstArrayElement = i;
|
|
vkUpdateDescriptorSets(get_device().get_handle(), 1, &write, 0, nullptr);
|
|
}
|
|
}
|
|
|
|
bool DescriptorIndexing::prepare(const vkb::ApplicationOptions &options)
|
|
{
|
|
if (!ApiVulkanSample::prepare(options))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
create_bindless_descriptors();
|
|
create_immutable_sampler_descriptor_set();
|
|
create_pipelines();
|
|
create_images();
|
|
|
|
prepared = true;
|
|
return true;
|
|
}
|
|
|
|
void DescriptorIndexing::request_gpu_features(vkb::PhysicalDevice &gpu)
|
|
{
|
|
gpu.get_mutable_requested_features().shaderSampledImageArrayDynamicIndexing = VK_TRUE;
|
|
|
|
REQUEST_REQUIRED_FEATURE(gpu,
|
|
VkPhysicalDeviceDescriptorIndexingFeaturesEXT,
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
|
|
shaderSampledImageArrayNonUniformIndexing);
|
|
|
|
// These are required to support the 4 descriptor binding flags we use in this sample.
|
|
REQUEST_REQUIRED_FEATURE(gpu,
|
|
VkPhysicalDeviceDescriptorIndexingFeaturesEXT,
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
|
|
descriptorBindingSampledImageUpdateAfterBind);
|
|
REQUEST_REQUIRED_FEATURE(gpu,
|
|
VkPhysicalDeviceDescriptorIndexingFeaturesEXT,
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
|
|
descriptorBindingPartiallyBound);
|
|
REQUEST_REQUIRED_FEATURE(gpu,
|
|
VkPhysicalDeviceDescriptorIndexingFeaturesEXT,
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
|
|
descriptorBindingUpdateUnusedWhilePending);
|
|
REQUEST_REQUIRED_FEATURE(gpu,
|
|
VkPhysicalDeviceDescriptorIndexingFeaturesEXT,
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
|
|
descriptorBindingVariableDescriptorCount);
|
|
|
|
// Enables use of runtimeDescriptorArrays in SPIR-V shaders.
|
|
REQUEST_REQUIRED_FEATURE(gpu,
|
|
VkPhysicalDeviceDescriptorIndexingFeaturesEXT,
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
|
|
runtimeDescriptorArray);
|
|
|
|
// There are lot of properties associated with descriptor_indexing, grab them here.
|
|
descriptor_indexing_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES_EXT;
|
|
|
|
VkPhysicalDeviceProperties2KHR device_properties{};
|
|
device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
|
|
device_properties.pNext = &descriptor_indexing_properties;
|
|
vkGetPhysicalDeviceProperties2KHR(gpu.get_handle(), &device_properties);
|
|
}
|
|
|
|
std::unique_ptr<vkb::VulkanSampleC> create_descriptor_indexing()
|
|
{
|
|
return std::make_unique<DescriptorIndexing>();
|
|
}
|